Repository navigation
Description
Problem or Motivation
The ops role holds every tool, and 60 tools publish a text parameter with a path-like name that the server opens or writes. SECURITY.md rule 11 says a tool publishes no option for a path that the command writes or reads, unless a path helper confines it. #3236 (PR #3248) confines three tools and audits the tools of the job roles. The rest stay open.
Census on main: a script lists the published schema of every tool and matches _PATH_PARAM_KEYWORDS (src/devops_cli/core/paths.py). Write paths first:
ai_ingest_library.output_dir,scan_sbom.output_file,scan_aibom.output_file,tf_apply.plan_file,tf_apply.var_file,k8s_create_tls_secret.cert_path,k8s_create_tls_secret.key_path- Reads of the model's choice:
ai_read.target_path,ai_pack_context.target_path,ai_ast_parse.file_path,ai_repomap.target_dir,ai_diagram.target_dir,review_branch.repo_path,scan_*.target,tf_*.directory,vault_get.path,tls_inspect.cert_path,ssh_audit.key_dir
Proposed Solution & Architectural Design
- For each tool, decide: drop the option (
_PATH_PARAM_RESTRICTIONS), or confine it withPATH_CONFINEMENTS(added by fix(mcp): read-only tools take paths that reach the server file system #3236). Do the write paths first. - Extend the audit test of fix(mcp): read-only tools take paths that reach the server file system #3236 from the job roles to the whole exposure table. Each parameter has an entry in
PATH_CONFINEMENTSor inNOT_A_FILE_PATHwith a reason, so the exempt list never grows in silence. - Check
vault_get.pathandvault_sync.path: they may be a secret path and not a file path (see feat(security): decide the free-text MCP parameters that can carry a secret #3230).
Target Subsystem
src/devops_cli/ai/mcp/generator.py, src/devops_cli/ai/mcp/path_confinement.py, tests/ai/mcp/.
Source: #3248
Proposed design
Problem statement
The ops role publishes 60 tools whose path-like text parameters a model can set. SECURITY.md rule 11 requires removal or confinement for every path that a command reads or writes. #3236 confines three tools and audits the job roles only. This change confines or removes the remaining parameters and extends the audit to the full exposure table.
Acceptance criteria
Requirement: Write path confinement
Every MCP tool that writes a file SHALL remove each path parameter from its schema or SHALL confine it with PATH_CONFINEMENTS.
Scenario: Unconfined write path
- WHEN A tool in
CONST_MCP_EXPOSURE_TABLEpublishes a write-path parameter that matches_PATH_PARAM_KEYWORDS. - THEN The audit fails unless the parameter has a
PATH_CONFINEMENTSentry.
Requirement: Read path confinement
Every MCP tool that reads a file SHALL remove each path parameter from its schema or SHALL confine it with PATH_CONFINEMENTS.
Scenario: Unconfined read path
- WHEN A tool in
CONST_MCP_EXPOSURE_TABLEpublishes a read-path parameter that matches_PATH_PARAM_KEYWORDS. - THEN The audit fails unless the parameter has a
PATH_CONFINEMENTSentry.
Requirement: Full exposure audit
The path-parameter audit SHALL inspect every tool in CONST_MCP_EXPOSURE_TABLE, including the ops role.
Scenario: Tool outside the job roles
- WHEN A tool in
CONST_MCP_EXPOSURE_TABLEis not in a job role and publishes a path-like parameter. - THEN The audit checks that parameter and fails when no entry exists.
Requirement: Reasoned non-file exemption
Each NOT_A_FILE_PATH entry SHALL hold a reason of at least 30 characters, and the audit MUST fail on a missing or short reason.
Scenario: Bare or short exemption
- WHEN A path-like parameter has a
NOT_A_FILE_PATHentry with no reason or a reason shorter than 30 characters. - THEN The audit fails and names the tool and parameter.
Requirement: Stale exemption
The audit MUST fail when a NOT_A_FILE_PATH entry names no published path-like parameter.
Scenario: Stale non-file entry
- WHEN A
NOT_A_FILE_PATHentry matches no published parameter. - THEN The audit fails and names the stale entry.
Requirement: Vault secret paths
The vault_get.path and vault_sync.path parameters SHALL be recorded in NOT_A_FILE_PATH as Vault secret paths, not filesystem paths.
Scenario: Vault path exemption
- WHEN The audit reads
vault_get.pathorvault_sync.path. - THEN The audit accepts the parameter only through its
NOT_A_FILE_PATHreason and not through a filesystem confinement.
Requirement: Listed write tools
The ai_ingest_library, scan_sbom, scan_aibom, tf_apply, and k8s_create_tls_secret tools SHALL publish no unconfined write-path option.
Scenario: Listed write tool schema
- WHEN The test reads the published schema of each listed write tool.
- THEN Each path-like write parameter is absent or has a
PATH_CONFINEMENTSentry.
Existing Tool Considered
No alternatives check has run. The open, maintained pathlib standard library and the repository helper safe_resolve_subpath in src/devops_cli/core/paths.py cover path containment. They lack MCP parameter classification and the full exposure-table audit. This change adds only that classification and audit, so it builds no path-confinement library.
Key questions
- Which mechanism removes or confines each path-like parameter?: Use
PATH_CONFINEMENTSto confine a parameter that the command must keep. Use_PATH_PARAM_RESTRICTIONSto remove a parameter that has no safe base. Apply this rule to write paths first. (best practice) [sources: SECURITY.md, src/devops_cli/core/paths.py, src/devops_cli/ai/mcp/generator.py, openspec/changes/3236-read-only-tools-take-paths-that/design.md]- Chosen: Confine with
PATH_CONFINEMENTS. It reuses the path helper and keeps the command usable. - Rejected: Remove every path option. It removes read and output functions that the tools need.
- Rejected: Validate in each tool without a registry. The audit cannot prove full coverage.
- Chosen: Confine with
- Does the audit cover every exposed tool?: The audit MUST read
CONST_MCP_EXPOSURE_TABLEand cover every tool, including theopsrole. It MUST fail on a path-like parameter with noPATH_CONFINEMENTSorNOT_A_FILE_PATHentry. (best practice) [sources: src/devops_cli/ai/mcp/generator.py, openspec/changes/3236-read-only-tools-take-paths-that/design.md]- Chosen: Full exposure table. The issue covers all 60 tools.
- Rejected: Job roles only. fix(mcp): read-only tools take paths that reach the server file system #3236 already covers the job roles.
- Rejected: Opt-in list. A new tool could escape the audit.
- Which values are not file paths?:
vault_get.pathandvault_sync.pathname a secret in Vault, not a filesystem path. Record them inNOT_A_FILE_PATHwith that reason. (best practice) [sources: SECURITY.md, src/devops_cli/ai/mcp/generator.py]- Chosen: Vault secret path. The value selects a Vault secret, and the command does not open a file.
- Rejected: Filesystem path. A Vault path does not resolve under a filesystem base.
- Rejected: Remove the option. The tool needs the Vault path to select the secret.
- What reason does an exemption need?: Each
NOT_A_FILE_PATHentry MUST hold a reason of at least 30 characters. The audit MUST fail on a missing, short, or stale entry. (best practice) [sources: openspec/changes/3236-read-only-tools-take-paths-that/design.md]- Chosen: Reasoned allowlist. It matches the secret-parameter allowlist pattern.
- Rejected: Bare allowlist. A bare entry hides why the parameter is safe.
- Rejected: Comment only. A comment cannot fail the test.
- Does this issue depend on fix(mcp): read-only tools take paths that reach the server file system #3236?: Yes. fix(mcp): read-only tools take paths that reach the server file system #3236 adds
PATH_CONFINEMENTSand the job-role audit. This issue extends both to the full exposure table. [sources: openspec/changes/3236-read-only-tools-take-paths-that/design.md, src/devops_cli/ai/mcp/generator.py] - Which open tool or library covers path confinement?: No alternatives check has run.
pathlibandsafe_resolve_subpathcover containment. They lack MCP parameter classification and the full audit. (best practice) [sources: src/devops_cli/core/paths.py, SECURITY.md]- Chosen: Use
pathlibandsafe_resolve_subpath. They are maintained and already used by the repository. - Rejected: Add an external path library. It adds a dependency and still needs MCP classification.
- Chosen: Use
Fit
Fits in one pull request.
Suspected block
None.
Activity
dan-petty commentedon Nov 2, 2026
Intake placed this item to the backlog: intake puts every new item in the backlog. A person plans the milestones.
fixprefix of the title gives the type.(mcp)scope of the title gives the scope.60 tools expose unconfined file paths to a model, violating SECURITY.md rule 11; the ops role is the broadest exposure.An unconfined path lets a model read or write any server file, a direct data-exfiltration and integrity risk for every user of the ops role.60 tools need individual confinement-or-drop decisions, the audit test must extend from job roles to the full table, and vault paths need a separate check.Both use PATH_CONFINEMENTS and _PATH_PARAM_RESTRICTIONS to guard MCP path parameters, but #3236 covers three tools and job roles while the candidate covers the remaining 60 ops-role tools and widens the audit scope.devops-cli-service commentedon Nov 3, 2026
Status set to Ready at b1a6c8e (see proposed design in issue body).
docs(openspec): fix plan #3236 scope to role tools and command names
docs(openspec): fix plan #3236 scope to role tools and command names
docs(openspec): plan #3236 revise scope to role tools (#3317)
devops-cli-service commentedon Nov 4, 2026
Released in v0.55.4: https://github.com/dan-petty/devops-cli/releases/tag/v0.55.4
devops-cli-service commentedon Nov 4, 2026
Live since 2026-11-04 15:54 UTC