agentsec · Tools · mcpposture

Offline · stdlib-only · MIT

mcpposture: detect least-privilege issues in MCP client configs in AI agents


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.

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

IDSeverityNameWhat it flagsFix hint
MP01highinline-secretSecret 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.
MP02highinline-bearer-tokenAuthorization header carries a literal Bearer/Basic credential.Use ${input:...}/${env:...} substitution or the client's OAuth flow instead of a pasted token.
MP03mediumunpinned-packagePackage/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.
MP04lowauto-install-no-promptnpx -y/--yes installs whatever resolves without confirmation.Pin the version; preferably install once to a vetted location and run the local binary.
MP05highdocker-privilegedContainer 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.
MP06highbroad-filesystem-rootFilesystem-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.
MP07mediumshell-capable-serverServer 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.
MP08highplaintext-remoteRemote 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://).
MP09highcredential-in-urlRemote 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.
MP10highuntrusted-exec-pathCommand 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.
MP11criticalpipe-to-shellLaunch arguments download and execute remote code (curl\sh, wget\
MP12mediumbroad-scope-credentialCredential 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).
MP13lowsecret-concentrationMany 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.
MP14hightls-verification-disabledEnvironment 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).
MP15mediumcode-injection-envEnvironment sets loader/interpreter injection variables (LD_PRELOAD, DYLD_INSERT_LIBRARIES, NODE_OPTIONS --require, PYTHONSTARTUP...).Remove unless strictly required and the referenced code is trusted.
MP16lowunmasked-secret-inputVS 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.
MP17infoduplicate-server-nameSame server name defined in multiple files; which one wins depends on the client and precedence can surprise you.Rename or remove one definition.
MP18infoduplicate-packageSame package/image launched by more than one server entry (possibly with different versions/credentials).Consolidate into a single entry.
MP19highauto-approve-privilegedautoApprove / alwaysAllow / trust: true covers a privileged tool (shell, write, filesystem, …).Require confirmation for privileged tools; keep allow-lists narrow.
MP20highcredential-dir-mountContainer mounts a credential directory (~/.ssh, ~/.aws, …), even when the image is digest-pinned.Mount only the project directories needed; never mount secret homes.
MP21highregistry-overridePackage-registry env or --registry flag points at a non-official host.Use the official registry, or pin a vetted mirror over HTTPS.
MP22highknown-vulnerable-pinPinned version is in a known-vulnerable range (for example mcp-remote ≤ 0.1.15, CVE-2025-6514).Upgrade past the advisory range.
MP23criticalremote-code-bootstrapLaunch 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-rules

After 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.

Known limits

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).

*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.

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.

pinned release is benign; lockfiles/digests and provenance checks are out of scope.

envFile, keychain integrations) is not modelled.

Full source, tests, and fixtures: tools/mcpposture/ in the repository. Install the kit with pip install git+https://github.com/dunkleschitz/agentsec.git.