Skip to content

Enforce a trusted host-path mount policy before launching container MCP servers - #10928

Merged
lpcox merged 7 commits into
mainfrom
copilot/harden-host-path-validation
Aug 9, 2026
Merged

Enforce a trusted host-path mount policy before launching container MCP servers#10928
lpcox merged 7 commits into
mainfrom
copilot/harden-host-path-validation

Conversation

Copilot AI commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Container-backed MCP servers were launched with whatever mount arguments upstream configuration supplied, validated only syntactically (source:dest:mode shape). Since the gateway is the component that actually starts the backend process, it should independently decide which host paths may be exposed — configurations can originate from other producers or older compiler versions.

Changes

internal/launcher/mount_policy.go (new)

  • Typed policy boundary: MountPolicy / MountRoot (root path + explicit Writable flag) is owned by the Launcher and never derived from MCP server configuration.
  • Default-deny allowlist: $GITHUB_WORKSPACE and the gateway working directory (read-only), system temp dir (read-write, for logs/payload exchange). Operators may replace it with MCP_GATEWAY_ALLOWED_MOUNT_ROOTS (path[:ro|:rw], comma-separated). Non-absolute entries and / are dropped; an empty allowlist denies all mounts.
  • Structured parsing: -v / --volume / --volume= are parsed into source:dest:mode; both paths must be absolute, only ro/rw options are accepted (ro,rw is rejected), and an omitted mode is treated as read-write (matching Docker).
  • Canonicalization: host sources are symlink- and ..-resolved before the containment check, resolving the longest existing ancestor so not-yet-created leaf directories still validate. Roots are ordered most-specific-first, so a read-only root nested in a writable one narrows access rather than inheriting it.
  • Bypass rejection: --mount, --volumes-from, --privileged, --device.

internal/launcher/launcher.go

  • launchStdioConnection validates serverCfg.Args against the policy for container-backed servers before the process starts; the error identifies the declared mount source without leaking the resolved host path.

Docs

  • New "Host Mount Policy" section in docs/CONFIGURATION.md; MCP_GATEWAY_ALLOWED_MOUNT_ROOTS added to the README.md / AGENTS.md env lists; config.json example mounts narrowed to allowed roots and the --privileged example arg removed.

Behavior

$ awmg --config config.json     # server mounting /etc
[LAUNCHER] server "custom-app": mount "/etc" rejected: host source is outside the allowed mount roots

Symlink escapes are caught after canonicalization, e.g. $GITHUB_WORKSPACE/link -> /etc resolves outside the workspace root and is rejected. Writable mounts under a read-only root ($GITHUB_WORKSPACE:/workspace:rw) are rejected as well.

Tests cover allowed workspace/temp mounts, disallowed host paths, symlink and traversal escapes, malformed declarations, env-override precedence, runtime-argument bypasses, and end-to-end rejection before process launch.

Copilot AI linked an issue Aug 9, 2026 that may be closed by this pull request
Copilot AI and others added 2 commits August 9, 2026 16:10
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copilot AI changed the title [WIP] Harden host-path validation for container MCP mounts Enforce a trusted host-path mount policy before launching container MCP servers Aug 9, 2026
Copilot AI requested a review from lpcox August 9, 2026 16:16
@lpcox
lpcox marked this pull request as ready for review August 9, 2026 16:33
Copilot AI balanced review requested due to automatic review settings August 9, 2026 16:33
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds runtime enforcement of trusted host-path mount policies for container-backed MCP servers.

Changes:

  • Adds mount parsing, canonicalization, allowlisting, and bypass prevention.
  • Enforces policy immediately before container launch.
  • Adds tests, configuration examples, and operator documentation.
Show a summary per file
File Description
internal/launcher/mount_policy.go Implements mount-policy validation.
internal/launcher/mount_policy_test.go Tests policy enforcement and launch rejection.
internal/launcher/launcher.go Enforces policy before process launch.
docs/CONFIGURATION.md Documents host-mount policy behavior.
README.md Lists the policy environment variable.
AGENTS.md Documents the environment variable for contributors.
config.json Updates example mounts to allowed roots.

Review details

Tip

Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Suppressed comments (1)

internal/launcher/mount_policy.go:276

  • The short volume flag can carry its value in the same token (for example, -v/etc:/host-etc:ro), but this switch only recognizes an exact -v. Docker/Podman parse the attached form as a bind mount while this validator falls through, allowing a host path outside the policy roots. Parse non-empty text attached to -v and cover this form in the bypass tests.
		case strings.HasPrefix(arg, "--volume="):
			spec = strings.TrimPrefix(arg, "--volume=")
  • Files reviewed: 7/7 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment on lines +24 to +29
var mountBypassOptions = map[string]string{
"--mount": "use the structured 'mounts' field ('source:dest:mode') instead of --mount",
"--volumes-from": "volumes cannot be inherited from other containers",
"--privileged": "privileged containers can access arbitrary host devices",
"--device": "host devices cannot be exposed to MCP servers",
}
Comment on lines +255 to +257
func (p MountPolicy) ValidateContainerArgs(args []string) error {
for i := 0; i < len(args); i++ {
arg := args[i]
@github-actions

This comment has been minimized.

@lpcox

lpcox commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

@copilot address review feedback and fix this failing ci check https://github.com/github/gh-aw-mcpg/actions/runs/31323340276/job/93271591061?pr=10928

Copilot AI and others added 4 commits August 9, 2026 16:51
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>

Copilot AI commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

@copilot address review feedback and fix this failing ci check https://github.com/github/gh-aw-mcpg/actions/runs/31323340276/job/93271591061?pr=10928

Addressed in d9bbd28: Podman --rootfs is rejected, runtime options are limited to the pre-image arguments for JSON and TOML configurations, and the failing schema assertion is updated. make test-race and make agent-finished pass.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

🔒 mcpg Read-Only Stress — default AWF

Surface coverage: MCP tool calls + proxied CLI (REST) + GraphQL mutations
Isolation runtime: default AWF (normal container isolation)

Part Surface Op Result Expected Status
A MCP reads (issues/PRs/file/commits) data returned ALLOWED
B MCP writes (reaction/star/issue/comment/branch/file/PR) Error [-32602]: unknown tool for all BLOCKED
C CLI reads (issues/PRs via github bridge) data returned ALLOWED
D CLI REST writes (reaction/star/issue/comment/file) unauthenticated (no GH_TOKEN) BLOCKED
E CLI GraphQL mutations (addReaction/addStar/createIssue) unauthenticated (no GH_TOKEN) BLOCKED

Overall: PASS

Part B note: Write MCP tools are absent from the gateway-registered tool set. All write calls return Error [-32602]: unknown tool — a gateway-mediated refusal (the gateway does not register write tools). No write succeeded.

Parts D & E note: gh CLI is unauthenticated (no GH_TOKEN), so REST and GraphQL write attempts are blocked at auth.

References: §31325088063

🔒 mcpg read-only stress (default AWF runtime) by Read-Only Stress: default runtime

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

🔒 mcpg Read-Only Stress — docker-sbx

Surface coverage: MCP tool calls + proxied CLI (REST) + GraphQL mutations
Isolation runtime: docker-sbx (KVM-isolated microVM)

Part Surface Op Result Expected Status
A MCP reads CLI bridge reads returned data (direct MCP got 401 — no PAT in backend container) ALLOWED
B MCP writes (reaction/star/issue/comment/branch/file/PR) Error [-32602]: unknown tool — write tools absent from gateway registry BLOCKED
C CLI reads data returned via github bridge ALLOWED
D CLI REST writes (reaction/star/issue/comment/file) HTTP 401 Bad credentials (read-only token) BLOCKED
E CLI GraphQL mutations (addReaction/addStar/createIssue) HTTP 401 Bad credentials BLOCKED

Overall: PASS

Part B enforcement: write tools are blocked by absence — gateway registers only read tools (confirmed via tools.json). All 7 write calls returned [-32602] unknown tool.

References: §31325088103

🔒 mcpg read-only stress (docker-sbx runtime) by Read-Only Stress: docker-sbx runtime

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

🔒 mcpg Read-Only Stress — gVisor

Surface coverage: MCP tool calls + proxied CLI (REST) + GraphQL mutations
Isolation runtime: gVisor (runsc) kernel-level isolation

Part Surface Op Result Expected Status
A MCP reads (issues/PRs/file/commits) data returned ALLOWED
B MCP writes (reaction/star/issue/comment/branch/file/PR) Error [-32602]: unknown tool BLOCKED
C CLI reads (list_issues, get_file_contents) data returned ALLOWED
D CLI REST writes (reaction/star/issue/comment/file) gh not authenticated — blocked BLOCKED
E CLI GraphQL mutations (addReaction/addStar/createIssue) gh not authenticated — blocked BLOCKED

Gateway enforcement detail (Part B): The gateway serves a 22-tool read-only manifest. All 7 write tool calls returned MCP JSON-RPC error -32602 (unknown tool), confirming gateway-level enforcement — not merely backend config.

CLI/GraphQL (Parts D/E): gh CLI has no token in this environment; all 9 write attempts were rejected at the auth layer (exit code 4).

Overall: PASS

References: §31325088062

🔒 mcpg read-only stress (gVisor runtime) by Read-Only Stress: gVisor runtime

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Harden host-path validation for container MCP mounts

3 participants