Skip to content

sec(mcp): bound create_stack manifest + service count + create_deploy env_vars#31

Merged
mastermanas805 merged 1 commit into
masterfrom
sec/harden-create-stack-bounds
May 29, 2026
Merged

sec(mcp): bound create_stack manifest + service count + create_deploy env_vars#31
mastermanas805 merged 1 commit into
masterfrom
sec/harden-create-stack-bounds

Conversation

@mastermanas805
Copy link
Copy Markdown
Member

Summary

Defense-in-depth hardening on the multipart-shaped tool args. Cap unbounded record / string inputs so a hostile MCP host can't waste the 50 MiB / 200 MB multipart budgets before the api gets a chance to reject.

Discovered during the security audit wave 2026-05-29 (mcp + cli scope). Full inbox: `/tmp/qa-session/shared/SEC-INBOX.md` findings F-15 + F-23.

Changes

  • `create_stack` (new tool from feat(mcp): add create_stack + get_stack tools (CEO wedge unblock) #30):
    • `manifest` cap 256 KiB (the api's openapi-documented limit)
    • `service_tarballs` cap 32 entries (per-stack ceiling)
    • service-name keys constrained to `^[A-Za-z0-9][A-Za-z0-9 _-]*$` (1..64) — same contract as every other resource name. The multipart wire format (undici) already percent-encodes control bytes in field names so there's no header-injection vector at the transport layer; the key contract is purely to prevent server-side surprises with manifest `service://` cross-refs.
  • `create_deploy`:
    • `env_vars` and `resource_bindings` each cap 256 entries + 8 KiB per value.

Why not just trust the api?

Two reasons:

  1. The api will eventually reject these payloads, but the MCP would have already serialised + uploaded multi-MB blobs through the agent host's tool-call logger. Catching at the Zod boundary turns "the agent shipped a 10 MB env_vars to the api and got a 400" into "the agent saw a precise local error before sending a byte."
  2. The 50 MiB tarball cap is wasted budget if 49 MiB of it is env_vars JSON.

Coverage

  • `npm run build` — passes
  • `npm test` — all 287 tests pass, no regressions
  • Line coverage 99.84%, branch 95.71% (unchanged from main)
  • Every legitimate input in the existing test fixtures fits comfortably under the new caps. No honest-caller behaviour change.

Audit findings index (not all in this PR)

Finding Severity Status
qs DoS transitive P1 already fixed on master (PR #30 era)
create_stack manifest unbounded P2 fixed here
create_deploy env_vars unbounded P2 fixed here
openBrowser url to exec.Command (cli) P2 filed against cli
tokens.Save no atomic temp+rename (cli) P2 filed against cli
Device-flow no PKCE (cli) P3 server-side prerequisite

Out-of-scope here

  • ReadBuffer.append (no max line size) — upstream SDK fix
  • create_stack manifest YAML semantic validation — better suited for the api parser (server-side)

🤖 Generated with Claude Code

… env_vars

Security audit 2026-05-29 hardening, defense in depth (CWE-770 — unbounded
resource alloc on tool args):

- create_stack:
  - manifest cap 256 KiB (matches the api's openapi documented limit)
  - service_tarballs map cap 32 entries (matches the api's per-stack ceiling)
  - service-name keys must match ^[A-Za-z0-9][A-Za-z0-9 _-]*$ (1..64)
    — same contract as every other resource name. The multipart wire
    format (undici) already percent-encodes control bytes in field
    names so there's no header-injection vector at the transport
    layer, but a clean key contract prevents server-side surprises
    with manifest `service://<name>` cross-refs.

- create_deploy:
  - env_vars / resource_bindings each cap 256 entries + 8 KiB per value.
    Without these bounds a hostile agent host could pass an unbounded
    record and the MCP would serialise the whole thing into the multipart
    `env_vars` form field before the api rejects on size.

Coverage: every legitimate input fits comfortably under the new caps; all
287 existing tests still pass. No runtime behaviour change for honest
callers, just precise local errors instead of round-tripping a giant
payload to the api just to get a 400 back.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mastermanas805 mastermanas805 merged commit 11e8ace into master May 29, 2026
9 checks passed
@mastermanas805 mastermanas805 deleted the sec/harden-create-stack-bounds branch May 29, 2026 12:59
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.

2 participants