Reported issues for mcpm
Pod holds 15 of 15 problems reported by people outside the maintainer team. Issues filed by the project's own owners, members and collaborators are excluded entirely — a maintainer's release checklist is not a warning to a prospective user.
Back to mcpm.
Most discussed
[security][LOW] guard cleanup compares sanitized installed names vs raw pin keys (prune mismatch)
Severity: LOW
Location
src/guard/cli.ts:263,270-271 (cleanup)
Summary
cleanup builds the installed-server set by pushing names through sanitize() (strips control chars, truncates to 256), but compares them against raw pin keys when deciding which pins are orphans:
for (const s of c.servers) installedServerNames.add(sanitize(s.name)); // sanitized
...
for (const serverName of Object.keys(pins.servers))
if (!installedServerNames.has(serverName)) orphanPinned.push(se
[Read the thread](https://github.com/getmcpm/cli/issues/28) · 2026-06-01 · closed · 1 comment
### [security][LOW] guard disable reconstructs original command from unauthenticated wrapped args
## Severity: LOW
## Location
`src/guard/wrap.ts:146-170` (`unwrapEntry`), `src/guard/orchestrator.ts:233,260`
## Summary
`mcpm guard disable` reconstructs the original server command by scanning the wrapped args for the `--server-name <name> --` marker and treating whatever follows as the original `command`/`args`. The marker is not authenticated, so a doctored wrapped entry (or a buggy manual edit) can steer `unwrapEntry` into writing an arbitrary `command` into the client config. Lower sever
[Read the thread](https://github.com/getmcpm/cli/issues/29) · 2026-06-01 · closed · 0 comments
### [security][LOW] guard pattern engine has no per-match regex timeout / leaf-length cap
## Severity: LOW
## Location
`src/guard/patterns.ts:119-160` (`normalizeForMatch`, `inspectAgainstSignatures`)
## Summary
The pattern engine matches attacker-controlled leaves up to `MAX_LEAF_BYTES` (1 MB) synchronously on the relay hot path, with no per-match wall-clock budget.
**Note:** the *currently shipped* signatures are **not** vulnerable to catastrophic backtracking — I benchmarked them (1k → 0.13 ms, 50k → 0.13 ms, 200k → 0.58 ms; linear), because the optional alternation groups are
[Read the thread](https://github.com/getmcpm/cli/issues/27) · 2026-06-01 · closed · 0 comments
### [security][LOW] Config writes follow symlinks (.tmp/.bak/config) — add O_NOFOLLOW
## Severity: LOW
## Location
`src/config/adapters/base.ts:55-76` (`writeAtomic`)
## Summary
`writeAtomic` writes `${configPath}.tmp` and `${configPath}.bak` with plain `writeFile`, which follows existing symlinks. On a shared/multi-user machine (or via another MCP server with filesystem write access), an attacker can pre-create `<config>.bak` or `<config>.tmp` as a symlink to a sensitive file (e.g. a shell rc file) so mcpm's write lands on the symlink target. The final `rename` of `.tmp` is co
[Read the thread](https://github.com/getmcpm/cli/issues/26) · 2026-06-01 · closed · 0 comments
### [security][LOW] Config backup (.bak) stores re-serialized JSON and is overwritten on every write
## Severity: LOW
## Location
`src/config/adapters/base.ts:63-68`
## Summary
The `.bak` written before each config mutation is a re-serialized copy of the **parsed** object (`JSON.stringify(previousContent)`), not the original file bytes — so formatting, key order, and any JSONC/comments are lost. It is also overwritten on **every** write, so after two mcpm operations the user's original pre-mcpm config state is gone.
## Recommended fix
Copy the raw original file bytes, and write the backup on
[Read the thread](https://github.com/getmcpm/cli/issues/25) · 2026-06-01 · closed · 0 comments
### [security][MEDIUM] Trust gate blind to medium severities; normaliseSeverity fails open; minTrustScore:0 overridable
## Severity: MEDIUM
## Location
`src/commands/publish/check.ts:49-55` (`assertTrustGate`), `src/scanner/trust-score.ts:133-159`, `src/scanner/tier2.ts:97` (`normaliseSeverity`), `src/server/tools.ts:124` + `src/server/handlers.ts:129-150`
## Summary
Three related gaps let low-quality/abusive servers slip through:
1. **Medium blind spot.** The publish gate and the score's cap-to-0 rule key only on `critical`/`high`. Every `detectExfilArgs` finding is `medium` (e.g. exfil-shaped args `url`/`end
[Read the thread](https://github.com/getmcpm/cli/issues/24) · 2026-06-01 · closed · 0 comments
### [security][MEDIUM] Config adapters: prototype-pollution gap on server names; import runs no trust scan
## Severity: MEDIUM
## Location
`src/config/adapters/base.ts` (read/addServer/removeServer/setServerDisabled), `src/commands/import.ts`
## Summary
Server names read from untrusted IDE configs and via `mcpm import` are never validated against `__proto__` / `constructor` / `prototype`, and the server maps are plain objects rather than null-prototype. Object-literal assignment (`{ [name]: ... }`) does not pollute `Object.prototype` directly, but index reads/destructures keyed on `__proto__` (`exi
[Read the thread](https://github.com/getmcpm/cli/issues/23) · 2026-06-01 · closed · 0 comments
### [security][MEDIUM] mcpm_up MCP tool: documented guards are dead code; auto-confirms install/removal if revived
## Severity: MEDIUM
## Location
`src/server/index.ts` (registration absent), `src/server/handlers.ts:375-441` (`handleMcpUp`), `src/server/tools.ts:101-150` (`UpInput`, TOOL_DEFINITIONS entry)
## Summary
`CLAUDE.md` V1.3 claims `[x] mcpm_up MCP server tool (destructiveHint: true)` and `[x] Path traversal protection on mcpm_up MCP tool input`. Both are effectively false at runtime:
- The tool is **never registered** in `src/server/index.ts` (only 8 tools are wired; registration ends at `mcpm_s
[Read the thread](https://github.com/getmcpm/cli/issues/22) · 2026-06-01 · closed · 0 comments
## Most recent
### [security][MEDIUM] Registry client follows redirects (SSRF) and reads unbounded response bodies
## Severity: MEDIUM
## Location
`src/registry/client.ts:145-173` (`get`), `src/registry/publish-client.ts:23-45`, `src/registry/pagination.ts`
## Summary
Two issues in the shared HTTP path:
1. **SSRF / redirect following.** `baseUrl` is fully overridable and `fetch` is called without `redirect: "manual"`, so the default redirect-follow behavior lets a registry response (or an attacker-controlled `baseUrl`) 30x-redirect the client to internal hosts (`http://169.254.169.254/...`, `http://localh
[Read the thread](https://github.com/getmcpm/cli/issues/21) · 2026-06-01 · closed · 0 comments
### [security][MEDIUM] guard relay forwards full process.env to wrapped server; buildSafeEnv is dead code
## Severity: MEDIUM
## Location
`src/guard/run-inner.ts:160-176`, `src/guard/relay.ts:79-85,131`
## Summary
`startRelay` defaults its child env to `buildSafeEnv()` — an allowlist designed (per its own doc comment) to avoid "leaking unrelated parent secrets (`OPENAI_API_KEY`, `AWS_*`, `GITHUB_TOKEN`) to a server we are wrapping precisely because we don't fully trust it." But the production caller always supplies `env`:
```ts
// run-inner.ts
childEnv = await resolveEnvPlaceholders(process.env);
[Read the thread](https://github.com/getmcpm/cli/issues/20) · 2026-06-01 · closed · 0 comments
### [security][MEDIUM] Integrity sidecars are unkeyed SHA-256 — no protection vs same-user/postinstall tampering
## Severity: MEDIUM
## Location
`src/guard/pins.ts` (`fileSha`, `*.integrity` read/write), `src/guard/policy.ts` (same pattern for `guard-policy.yaml.integrity`)
## Summary
The `.integrity` sidecars are a plain `sha256:<hex>` of the file, stored next to the file with the same (writable) permissions. There is **no key**, so any process that can modify `pins.json` / `guard-policy.yaml` can recompute and rewrite the sidecar to match. There is no asymmetry between the attacker and the legitimate w
[Read the thread](https://github.com/getmcpm/cli/issues/19) · 2026-06-01 · closed · 0 comments
### [security][HIGH] health-check leaks env secrets to untrusted server (denylist instead of allowlist)
## Severity: HIGH
## Location
`src/scanner/health-check.ts:27-95` (`buildHealthCheckEnv`), spawn at `:188`
## Summary
The post-install health check spawns the **freshly-installed, untrusted** MCP server and passes it an environment built by copying **all** of `process.env` and subtracting a hardcoded denylist (`SENSITIVE_ENV_NAMES` / `SENSITIVE_ENV_PREFIXES`). CLAUDE.md's 2026-05-17 decision states env should be **allowlisted, not process.env passthrough**; this is the inverse, and the denylis
[Read the thread](https://github.com/getmcpm/cli/issues/18) · 2026-06-01 · closed · 0 comments
### [security][HIGH] mcpm publish sends GitHub token to arbitrary --registry host (token exfiltration)
## Severity: HIGH
## Location
`src/registry/publish-client.ts:20-31`, `src/commands/publish/submit.ts:39`
```ts
const url = `${registryUrl}/v0.1/servers`;
headers: { Authorization: `Bearer ${token}` } // token = GITHUB_TOKEN / MCPM_TOKEN
Summary
mcpm publish --registry <url> flows unvalidated into the Authorization: Bearer header. There is no scheme check, no host allowlist, and the default fetch follows redirects. The token source is correctly env-only (never a CLI flag), but
Read the thread · 2026-06-01 · closed · 0 comments
[security][HIGH] guard detection bypass: signatures don't scan structuredContent, error, or inputSchema
Severity: HIGH
Location
src/guard/patterns.ts:56-94 (targetSubtree), src/guard/signatures.ts
Summary
The pattern engine narrows each signature target to a single JSON path, leaving standard MCP fields completely unscanned. A malicious server evades the headline OWASP signatures by relocating its payload:
tool_responsewalks onlyresult.content(patterns.ts:60). Injection placed inresult.structuredContent(a standard MCP result field) or in a JSON-RPCerrorobje
Read the thread · 2026-06-01 · closed · 0 comments
[security][HIGH] Encrypted secret store uses a non-secret encryption key (hostname + username)
Severity: HIGH
Location
src/store/keychain.ts:26-28
const MACHINE_PASSPHRASE = new TextEncoder().encode(
`mcpm:${os.hostname()}:${os.userInfo().username}`
);
Summary
The AES-GCM key protecting ~/.mcpm/secrets.enc.json is derived (PBKDF2, 600k iterations, per-value salt) from hostname + username only. Neither value is secret — both are trivially recoverable by anyone who obtains the encrypted file (process listings, file paths, shell history, backups, cloud sync, o
Read the thread · 2026-06-01 · closed · 0 comments
The remaining reports are on the project's issue tracker.