Skip to content

fix(security): harden remote-URL & runtime-arg validation; honest keychain notice (M4/M5/I1)#66

Merged
m1ngshum merged 2 commits into
mainfrom
fix/preexisting-hardening
Jun 8, 2026
Merged

fix(security): harden remote-URL & runtime-arg validation; honest keychain notice (M4/M5/I1)#66
m1ngshum merged 2 commits into
mainfrom
fix/preexisting-hardening

Conversation

@m1ngshum

@m1ngshum m1ngshum commented Jun 8, 2026

Copy link
Copy Markdown
Member

Summary

Pre-existing hardening surfaced by the post-ship security review (companion to #65, which fixes the shipped-diff gaps). These are not regressions from #62/#61/#63/#64 — they predate them — but they're worth closing.

Findings fixed

ID Sev What Fix
M4a MED validateRemoteUrl accepted plaintext http:// to any host; once written to an IDE config it's interceptable. The mcpm up URL-server path also wrote stack-file url: servers unvalidated. https always allowed; http only for loopback (localhost / 127.0.0.1 / ::1 / *.localhost). validateRemoteUrl is now also called on the up URL path.
M4b MED validateRuntimeArgs' allowlist permits . and / in values, so a .. traversal segment (e.g. --config=../secret) slipped through to the spawned server. Reject a .. path segment up front — bare, after =, or path-separated. A non-traversal double-dot like --range=1..10 is left alone.
M5 / I1 LOW/INFO The keychain storage notice claimed "protects against other-user/offline access" unconditionally — false when keychain mode silently falls back to the machine-derived key. Notice is now honest about both backends and points to mcpm secrets migrate.

Tests

  • M4a: http:// to non-loopback hosts rejected; loopback http + all https allowed.
  • M4b: bare .., a/../../b, --config=../secret, --config=../../secret rejected; --range=1..10 allowed.
  • Full vitest suite green; tsc --noEmit + tsup clean.

Deliberately NOT changed (with rationale)

  • L2 (macOS argv master key): security(1) has no clean non-interactive stdin path; the maintainer already documented this as an acceptable, narrow same-user window (os-keychain.ts). Overriding a reasoned decision with a breakage-prone change isn't warranted.
  • Guard relay: flagged for a dedicated deep review (out of scope here).

m1ngshum added 2 commits June 8, 2026 23:11
…chain notice (M4/M5/I1)

Pre-existing hardening surfaced by the post-ship security review:

- M4a (MED): validateRemoteUrl accepted plaintext http:// to any host, which is
  interceptable once written to an IDE config. Now https is always allowed; http
  only for loopback (localhost / 127.0.0.1 / ::1 / *.localhost). Also wire
  validateRemoteUrl into the `mcpm up` URL-server path, which previously wrote
  stack-file `url:` servers to client configs unvalidated.
- M4b (MED): validateRuntimeArgs' allowlist permits "." and "/" inside values, so
  a ".." path-traversal segment (e.g. --config=../secret) slipped through. Reject
  ".." segments up front (bare, after "=", or path-separated); a non-traversal
  double dot like --range=1..10 is left untouched.
- M5/I1 (LOW/INFO): the keychain secret-storage notice claimed "protects against
  other-user/offline access" unconditionally — false when keychain mode silently
  falls back to the machine-derived key. Make the notice honest about both
  backends and recommend `mcpm secrets migrate`.

tsc + tsup clean; full vitest suite green.
…L coverage)

Multi-agent review follow-ups:

- MEDIUM dry-run regression: validateRemoteUrl ran before the dryRun guard in
  processUrlServer, so `mcpm up --dry-run` on a bad stack URL threw and exited
  non-zero on a read-only pass. Capture the validation result instead of throwing:
  dry-run now previews it as a "would reject URL …" skip (exit zero), and a real
  run categorizes the bad URL as `blocked` (mirroring trust-policy blocks) rather
  than a generic `failed`.
- HIGH coverage gap: the up-path URL validation had zero integration tests. Add
  four handleUp cases — valid https installs to Cursor, non-loopback http blocked,
  non-http(s) scheme blocked, and the dry-run regression (no throw, "would reject").
- Nits: test IPv6 bracket loopback `http://[::1]:8080` (the bracket-strip is
  load-bearing); document that isLoopbackHost intentionally over-rejects exotic
  loopback spellings (never a bypass).

tsc + tsup clean; full suite green (1302).
@m1ngshum

m1ngshum commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

Multi-agent review pass — addressed (commit 0984949)

Verdict was request-changes (no security bypass, but a real regression + a coverage gap). Both fixed:

  • MEDIUM dry-run regressionvalidateRemoteUrl ran before the dry-run guard in processUrlServer, so mcpm up --dry-run on a bad stack URL threw and exited non-zero on a read-only pass. Now captures the result instead of throwing: dry-run previews it as would reject URL … (exit zero); a real run categorizes the bad URL as blocked (mirroring trust-policy blocks) instead of a generic failed.
  • HIGH coverage gap — the up-path URL validation had zero integration tests. Added four handleUp cases: valid https installs to Cursor, non-loopback http blocked, non-http(s) scheme blocked, and the dry-run regression itself.
  • Nits: added an IPv6 bracket-loopback test (http://[::1]:8080 — the bracket-strip is load-bearing); documented that isLoopbackHost intentionally over-rejects exotic loopback spellings (never a bypass).

Note: the disputed "M4b regex character-class bug" was verified a false positive — the class includes a literal backslash, so a\..\b and ../x both match while 1..10 is correctly allowed.

Full suite green (1302), tsc + tsup clean.

@m1ngshum m1ngshum merged commit 936dd22 into main Jun 8, 2026
7 checks passed
@m1ngshum m1ngshum deleted the fix/preexisting-hardening branch June 8, 2026 15:50
@m1ngshum m1ngshum mentioned this pull request Jun 8, 2026
m1ngshum added a commit that referenced this pull request Jun 8, 2026
Bump version, banners (v0.8.0 -> v0.8.1), and CLAUDE.md; add the 0.8.1
CHANGELOG entry. Release content: mcpm_up MCP tool registration (#64), the
post-ship security review fixes (#65, #66), and the hono/semver dep bumps
(#62, #61). No source changes here beyond the version string.
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.

1 participant