What it detects
An offline, standard-library-only Python 3 CLI that audits MCP (Model Context Protocol) client configuration files for least-privilege and supply-chain risk: plaintext secrets, unpinned npx/uvx packages, privileged Docker launches, over-broad filesystem roots, shell-capable servers, plaintext or credential-bearing remote URLs, and more. It prints masked findings, a per-server risk summary and an overall A–F grade.
Honesty note. mcpposture is a static config review only. It never launches servers, resolves packages or touches the network. It cannot see what a server actually does at runtime (a perfectly pinned package can still be malicious), it cannot verify token scopes or whether a key is live, and every rule is a heuristic with false positives and false negatives (e.g. MP07 "shell-capable" is name-based). A grade of A means "no smells found by these rules", not "safe".
Rules
| ID | Severity | Name | What it flags | Fix hint |
|---|---|---|---|---|
| MP01 | high | inline-secret | Secret value is stored in plaintext in the config file (env, header, or arg); config files get synced, shared, committed and read by other tools. | Reference it instead: ${VAR}/${env:VAR}, VS Code ${input:id} with password:true, or an OS keychain/secret manager. |
| MP02 | high | inline-bearer-token | Authorization header carries a literal Bearer/Basic credential. | Use ${input:...}/${env:...} substitution or the client's OAuth flow instead of a pasted token. |
| MP03 | medium | unpinned-package | Package/image is fetched at launch without an exact version (or @latest); a compromised or hijacked release runs with your privileges on next start. | Pin an exact version (pkg@1.2.3, pkg==1.2.3, image@sha256:...) and review upgrades deliberately. |
| MP04 | low | auto-install-no-prompt | npx -y/--yes installs whatever resolves without confirmation. | Pin the version; preferably install once to a vetted location and run the local binary. |
| MP05 | high | docker-privileged | Container launched with privileges that weaken isolation (privileged, host network/pid/ipc, added caps, host root or docker.sock mounts, unconfined seccomp/apparmor). | Drop the flag; mount only the specific directories needed, read-only where possible; never mount docker.sock. |
| MP06 | high | broad-filesystem-root | Filesystem-type server is granted an over-broad root (/, ~, $HOME, a whole user profile, drive root, /etc ...), exposing SSH keys, browser profiles, cloud creds. | Grant only the project directories the agent needs. |
| MP07 | medium | shell-capable-server | Server is launched through a shell or looks like a shell/terminal/exec MCP server (heuristic, by name). Such servers give the model arbitrary command execution. | Prefer narrowly-scoped servers; require per-call confirmation; run in a sandbox/container with no secrets. |
| MP08 | high | plaintext-remote | Remote MCP server reached over plain http:// / ws:// to a non-localhost host; traffic and tokens can be read or modified in transit. | Use https:// (wss://). |
| MP09 | high | credential-in-url | Remote URL embeds credentials in userinfo or a secret-looking query parameter; URLs leak into logs, history and proxies. | Move the credential to a header populated from ${input:...}/${env:...} or use OAuth. |
| MP10 | high | untrusted-exec-path | Command or script runs from a world-writable/temp/download location that other users or processes can replace. | Install the binary/script to a user-owned, non-writable-by-others location. |
| MP11 | critical | pipe-to-shell | Launch arguments download and execute remote code (curl\ | sh, wget\ |
| MP12 | medium | broad-scope-credential | Credential type cannot be narrowly scoped or is long-lived (GitHub classic PAT ghp_, AWS long-term AKIA key, live payment key). Actual scopes cannot be verified offline. | Use fine-grained/short-lived credentials (github_pat_ fine-grained, AWS SSO/STS role, restricted keys). |
| MP13 | low | secret-concentration | Many secret-bearing variables are handed to a single server, enlarging blast radius if it is compromised. | Split responsibilities; give each server only the credential it needs. |
| MP14 | high | tls-verification-disabled | Environment disables TLS certificate verification (NODE_TLS_REJECT_UNAUTHORIZED=0, PYTHONHTTPSVERIFY=0, ...). | Remove the override; add a proper CA bundle if needed (NODE_EXTRA_CA_CERTS / SSL_CERT_FILE). |
| MP15 | medium | code-injection-env | Environment sets loader/interpreter injection variables (LD_PRELOAD, DYLD_INSERT_LIBRARIES, NODE_OPTIONS --require, PYTHONSTARTUP...). | Remove unless strictly required and the referenced code is trusted. |
| MP16 | low | unmasked-secret-input | VS Code inputs entry looks like a secret but is not declared with "password": true, so it is echoed when typed. | Add "password": true to the input definition. |
| MP17 | info | duplicate-server-name | Same server name defined in multiple files; which one wins depends on the client and precedence can surprise you. | Rename or remove one definition. |
| MP18 | info | duplicate-package | Same package/image launched by more than one server entry (possibly with different versions/credentials). | Consolidate into a single entry. |
| MP19 | high | auto-approve-privileged | autoApprove / alwaysAllow / trust: true covers a privileged tool (shell, write, filesystem, …). | Require confirmation for privileged tools; keep allow-lists narrow. |
| MP20 | high | credential-dir-mount | Container mounts a credential directory (~/.ssh, ~/.aws, …), even when the image is digest-pinned. | Mount only the project directories needed; never mount secret homes. |
| MP21 | high | registry-override | Package-registry env or --registry flag points at a non-official host. | Use the official registry, or pin a vetted mirror over HTTPS. |
| MP22 | high | known-vulnerable-pin | Pinned version is in a known-vulnerable range (for example mcp-remote ≤ 0.1.15, CVE-2025-6514). | Upgrade past the advisory range. |
| MP23 | critical | remote-code-bootstrap | Launch command is python -c / node -e that fetches and execs remote code. | Install a pinned local binary; do not eval remote scripts at start. |
Usage
python3 mcpposture.py ~/Library/Application\ Support/Claude/claude_desktop_config.json
python3 mcpposture.py . # discover configs under the project
python3 mcpposture.py .cursor/mcp.json --json
python3 mcpposture.py . --sarif > mcpposture.sarif
python3 mcpposture.py . --min-severity high # only high/critical (and exit on them)
python3 mcpposture.py . --select MP01,MP02,MP09 # only secret rules
python3 mcpposture.py . --ignore MP07 --exit-zero
python3 mcpposture.py --list-rulesAfter pip install of the kit, the same options work as agentsec mcpposture …. Every tool also accepts --help and almost every tool accepts --json.
Example output
examples/vscode-project (exit code 1):
examples/risky_claude_desktop_config.json triggers MP01–MP15 (34 findings, grade F; all secrets in it are fake). Excerpt with --select MP01,MP05,MP06,MP08:
examples/clean_mcp.json (pinned versions, digest-pinned image, ${VAR} references, narrow filesystem root, https remote) → no findings, grade A, exit 0. Scanning the whole examples/ dir at --min-severity info also shows MP17/MP18 duplicate notices.
mcpposture 0.1.0 - static MCP config posture review (offline; heuristics, not runtime analysis)
== examples/vscode-project/.vscode/mcp.json
MEDIUM MP03 L13 [perplexity] package 'server-perplexity-ask' launched via npx with no exact version
evidence: server-perplexity-ask
fix: Pin an exact version (pkg@1.2.3, pkg==1.2.3, image@sha256:...) and review upgrades deliberately.
LOW MP16 L5 [input:perplexity-key] input 'perplexity-key' looks secret but lacks "password": true
evidence: perplexity-key
fix: Add "password": true to the input definition.
LOW MP04 L13 [perplexity] npx -y auto-installs without confirmation
evidence: npx -y
fix: Pin the version; preferably install once to a vetted location and run the local binary.
Per-server summary:
SERVER TRANSPORT LAUNCHER PACKAGE PINNED SECRETS FINDINGS SCORE GRADE
---------- --------- -------- --------------------- ------ ------- -------- ----- -----
github http remote - - 0 0 0 A
perplexity stdio npx server-perplexity-ask NO 0 2 11 C
local-dev sse remote - - 0 0 0 A
Overall grade: C (score 14 = worst server + input findings) | servers: 3 | files: 1 | critical: 0, high: 0, medium: 1, low: 2, info: 0
SARIF and CI
agentsec mcpposture path/to/input --sarif > mcpposture.sarif
# GitHub Actions (composite action ships with the repo)
- uses: dunkleschitz/agentsec@v0
with:
tools: mcpposture
paths: path/to/input
fail-on: high
SARIF 2.1.0 can be uploaded to GitHub code scanning. The composite action writes per-tool reports under agentsec-sarif/ by default.
OWASP mapping
Heuristic mapping of what this tool helps a reviewer look for. Not an official OWASP endorsement, and not complete coverage of any item. Sources: OWASP Top 10 for LLM Applications 2025 and OWASP Top 10 for Agentic Applications 2026.
- LLM03 Supply Chain — OWASP LLM Top 10 (2025)
- LLM06 Excessive Agency — OWASP LLM Top 10 (2025)
- ASI03 Identity and Privilege Abuse — OWASP Agentic Top 10 (2026)
- ASI04 Agentic Supply Chain Vulnerabilities — OWASP Agentic Top 10 (2026)
Known limits
- Static config review only. No server is started and no package is
downloaded; what a server *does* (tools it exposes, files it reads, network calls it makes) is invisible. Pair with runtime controls, sandboxing and tool-description review (see toolpoison in this kit).
- Token scopes cannot be verified offline. MP12 only reasons about token
*type* by prefix (classic ghp_ PATs can't be repo-scoped; AKIA keys are long-lived and might be root/IAM-user keys). A fine-grained token may still be over-privileged; a flagged one may be tightly scoped.
- Heuristics have false positives and negatives. Secret detection relies on
key names and known token shapes; custom secret formats or oddly named keys are missed, and long non-secret values under secret-like names may be flagged. MP07 matches names like shell, terminal, exec, ssh, desktop-commander. MP06 only inspects positional args of filesystem-looking servers.
- Pinning to an exact version reduces surprise upgrades but does not prove the
pinned release is benign; lockfiles/digests and provenance checks are out of scope.
- Client-specific precedence (global vs. project configs, env inheritance,
envFile, keychain integrations) is not modelled.
- Line numbers point at the server's name key, not the exact offending value.
Full source, tests, and fixtures: tools/mcpposture/ in the repository. Install the kit with pip install git+https://github.com/dunkleschitz/agentsec.git.