Skip to content

fix(mcp): the ops role publishes 60 tools with unconfined path parameters #3300

Closed

Description

@dan-petty

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

  1. For each tool, decide: drop the option (_PATH_PARAM_RESTRICTIONS), or confine it with PATH_CONFINEMENTS (added by fix(mcp): read-only tools take paths that reach the server file system #3236). Do the write paths first.
  2. 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_CONFINEMENTS or in NOT_A_FILE_PATH with a reason, so the exempt list never grows in silence.
  3. Check vault_get.path and vault_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_TABLE publishes a write-path parameter that matches _PATH_PARAM_KEYWORDS.
  • THEN The audit fails unless the parameter has a PATH_CONFINEMENTS entry.

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_TABLE publishes a read-path parameter that matches _PATH_PARAM_KEYWORDS.
  • THEN The audit fails unless the parameter has a PATH_CONFINEMENTS entry.

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_TABLE is 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_PATH entry 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_PATH entry 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.path or vault_sync.path.
  • THEN The audit accepts the parameter only through its NOT_A_FILE_PATH reason 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_CONFINEMENTS entry.

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_CONFINEMENTS to confine a parameter that the command must keep. Use _PATH_PARAM_RESTRICTIONS to 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.
  • Does the audit cover every exposed tool?: The audit MUST read CONST_MCP_EXPOSURE_TABLE and cover every tool, including the ops role. It MUST fail on a path-like parameter with no PATH_CONFINEMENTS or NOT_A_FILE_PATH entry. (best practice) [sources: src/devops_cli/ai/mcp/generator.py, openspec/changes/3236-read-only-tools-take-paths-that/design.md]
  • Which values are not file paths?: vault_get.path and vault_sync.path name a secret in Vault, not a filesystem path. Record them in NOT_A_FILE_PATH with 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_PATH entry 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_CONFINEMENTS and 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. pathlib and safe_resolve_subpath cover containment. They lack MCP parameter classification and the full audit. (best practice) [sources: src/devops_cli/core/paths.py, SECURITY.md]
    • Chosen: Use pathlib and safe_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.

Fit

Fits in one pull request.

Suspected block

None.

Dependencies

#3236

Activity

  1. added
    type/bugDefect, regression, or incorrect runtime behavior
    scope/mcpModel Context Protocol tools, system resources, and prompts
    status/newStatus New: the item is not ready yet
    value/highValue High: the delivery of the item matters much
    effort/highEffort High: much work, in one pull request
    on Nov 2, 2026
  2. dan-petty commented on Nov 2, 2026

    @dan-petty
    OwnerAuthor

    Intake placed this item to the backlog: intake puts every new item in the backlog. A person plans the milestones.

    • Type: type/bug. The fix prefix of the title gives the type.
    • Scope: scope/mcp. The (mcp) scope of the title gives the scope.
    • Priority: P1-High. 60 tools expose unconfined file paths to a model, violating SECURITY.md rule 11; the ops role is the broadest exposure.
    • Value: High. 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.
    • Effort: High. 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.
    • Related: fix(mcp): read-only tools take paths that reach the server file system #3236, which asks for a different change. 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.
  3. added
    priority/p1-highPriority P1: high-impact work for the next planned milestone
    on Nov 2, 2026
  4. added
    status/readyStatus Ready: problem, acceptance criteria and an answer to each key question
    and removed
    status/newStatus New: the item is not ready yet
    on Nov 3, 2026
  5. devops-cli-service commented on Nov 3, 2026

    @devops-cli-service
    Contributor

    Status set to Ready at b1a6c8e (see proposed design in issue body).

  6. added 3 commits that reference this issue on Nov 3, 2026
  7. added
    status/in-progressActively being designed or implemented by developer or agent
    status/in-reviewPull request open and undergoing multi-persona review or CI gates
    and removed
    status/readyStatus Ready: problem, acceptance criteria and an answer to each key question
    status/in-progressActively being designed or implemented by developer or agent
    on Nov 4, 2026
  8. removed
    status/in-reviewPull request open and undergoing multi-persona review or CI gates
    on Nov 4, 2026
  9. devops-cli-service commented on Nov 4, 2026

    @devops-cli-service
    Contributor
  10. devops-cli-service commented on Nov 4, 2026

    @devops-cli-service
    Contributor

    Live since 2026-11-04 15:54 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    effort/highEffort High: much work, in one pull requestpriority/p1-highPriority P1: high-impact work for the next planned milestonescope/mcpModel Context Protocol tools, system resources, and promptssource/agentFiled by an agenttype/bugDefect, regression, or incorrect runtime behaviorvalue/highValue High: the delivery of the item matters much

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions