Skip to content

Reject path-breaking ids in MCP tool inputs - #8843

Merged
andypalmi merged 2 commits into
mainfrom
fix/mcp-tool-id-path-guard
Oct 9, 2026
Merged

andypalmi merged 2 commits into
mainfrom
fix/mcp-tool-id-path-guard

Conversation

@andypalmi

Copy link
Copy Markdown
Contributor

Description

MCP tools build the app.inject URL by interpolating args.* ids into the path, and most of those ids were plain z.string(). app.inject resolves dot segments, so an id such as ../applications/<id> reached a different route than the tool claims to act on.

This adds a shared guard rather than per-tool checks.

  1. schemas.js gains pathId, a string restricted to ^[A-Za-z0-9_-]+$ (hashids, UUIDs and team-broker). The raw *Id: z.string() parameters across the tools use it, so the published input schema carries the pattern and the gateway enforces it.
  2. invokeTool in toolLoader.js is now the only way a tool handler is called, and platformAutomation.js uses it. It rejects any top level string argument whose key ends in Id unless it matches the pattern, with Input validation error: <field> must contain only letters, digits, hyphen and underscore. This runs even when schema parsing falls back to the raw input.
  3. invokeTool also hands the handler a wrapped inject that refuses any URL path segment that is . or .. (including once percent-decoded) or contains control characters. File paths keep working because they are percent-encoded into one segment.
  4. Free-text path values that were not encoded, the broker client username and the team slug, are now encoded.

Behaviour change

  1. Ids that are not a single plain path segment are now rejected with a 400 style input validation error. Every real id format passes, and the only other *Id arguments are UUIDs.
  2. The published JSON schema for these inputs gains a pattern.

Reviewer notes

  1. The existing per tool checks (for example the one added in Add MCP tools to delete an instance and roll back a hosted instance #8693 for instance deletion) are now redundant and are left in place to keep this diff to the guard.
  2. All 119 URL building sites in tools/*.js were checked: ids are covered by the id key check and pathId, and everything else (usernames, slugs, file paths, table names) by the inject wrapper plus encoding.

Tests

  1. New pathGuard_spec.js with 21 tests: ../applications/abc and ../teams/abc style ids are rejected for instances, applications, pipelines, remote instances, brokers, snapshots and teams, as are ?, #, backslash, %2e%2e, newlines and .. file paths. Normal ids, UUIDs, team-broker and logs/archive are accepted, and a sweep checks every *Id parameter across all tools.
  2. 14 of the new cases fail on main and pass here.
  3. The MCP specs and platformAutomation_spec.js pass.

Related Issue(s)

Closes #8716

Part of #8629

Id parameters are interpolated into URL paths passed to app.inject(), which
resolves dot segments, so an id such as ../applications/<id> could reach a
different route. Declare ids with a shared pattern-bearing schema, validate
every id argument and refuse dot-segment paths centrally when a tool is
invoked, and encode the remaining free-text path values.
@andypalmi
andypalmi requested a review from cstns October 7, 2026 14:38
@codecov

codecov Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.66667% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.52%. Comparing base (90eed71) to head (e864dc5).

Files with missing lines Patch % Lines
forge/ee/lib/mcp/utils.js 93.54% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8843      +/-   ##
==========================================
+ Coverage   78.49%   78.52%   +0.02%     
==========================================
  Files         475      475              
  Lines       25721    25760      +39     
  Branches     6849     6857       +8     
==========================================
+ Hits        20189    20227      +38     
- Misses       5532     5533       +1     
Flag Coverage Δ
backend 78.52% <96.66%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…guard

# Conflicts:
#	forge/ee/lib/mcp/tools/snapshots.js
@andypalmi
andypalmi enabled auto-merge (squash) October 9, 2026 11:02
@andypalmi
andypalmi merged commit 6d8cde6 into main Oct 9, 2026
29 checks passed
@andypalmi
andypalmi deleted the fix/mcp-tool-id-path-guard branch October 9, 2026 11:24

This branch was successfully deployed

1 active deployment
staging — e864dc5b Deployed Oct 9, 2026 by andypalmi via Remove application #12325
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP tools pass ids straight into inject URLs, so ../ can reach a different route

2 participants