Skip to main content

IANUA-Broker

Local-first, offline-by-default security posture scanner for MCP / local-agent setups. Find exposed servers, plaintext secrets, over-broad tool scopes, and unpinned packages — then fix the highest-impact issues first.

Status: Stable (v1.x) — safe to run (read-only, offline by default), with a stable CLI surface, JSON schema, and check ids covered by semver. CLI command: mcpscan. License: Apache-2.0.

What it does

  • Discovers MCP servers on the local machine via socket/process enumeration (bind addresses classified by reachability — loopback / private-LAN / public-routable / wildcard) plus a loopback probe of /mcp and /sse.
  • Statically audits Claude-ecosystem (.claude/settings.json, .mcp.json, claude_desktop_config.json), Cursor (~/.cursor/mcp.json, .cursor/mcp.json), Windsurf (~/.codeium/windsurf/mcp_config.json), Cline (VS Code globalStorage/…/cline_mcp_settings.json), VS Code (.vscode/mcp.json, user mcp.json), Zed (.zed/settings.json, user ~/.config/zed/settings.json), and Continue (~/.continue/config.yaml, .continue/config.yaml — needs the [yaml] extra) agent configs — plus .env — for plaintext secrets, auto-approval flags, over-broad tool scopes, unpinned versions, reused credentials, and tool-poisoning signals.
  • Scores each server A–F across four dimensions (exposure, credential hygiene, tool-scope breadth, version pinning).
  • Reports a prioritized, redacted, advise-only remediation in four forms: terminal, a self-contained HTML file, stable JSON, and SARIF 2.1.0 for GitHub code scanning — and can alert (--emit to webhook/syslog/NDJSON).
  • Inventories (mcpscan inventory) the machine's AI infrastructure as a classified, typed asset list — agent hosts, MCP servers, model servers, inference endpoints, LLM gateways, vector DBs — with per-asset evidence and confidence.
  • Validates continuously — a per-agent Trust Score (mcpscan trust), framework mapping (mcpscan atlas), a signed baseline + drift gate (mcpscan baseline / diff), a named-human risk-acceptance ledger, validation-age staleness warnings, and OS-native scheduling (mcpscan schedule) turn a one-shot scan into an ongoing posture program.
  • Goes deeper, opt-in--online OSV dependency-vuln lookups, --inspect-token-stores (OAuth/session tokens at rest), --inspect-process-env (secrets in running agent processes), --inspect-telemetry (agent-host logging health), mcpscan selftest (catches a degraded scanner), and a signed detection data-pack refresh channel (mcpscan update-datapack).

Trust properties (by design)

  • Localhost onlyscan never touches the LAN or third-party systems (mcpscan lan is a separate, signed-manifest-gated command).
  • Offline + zero egress by default — the network is contacted only under an explicit opt-in: --online (OSV dependency lookups) or mcpscan update-datapack; each one says so.
  • Reads nothing extra by default — deeper surfaces (token stores, running process environments, host logging) are read only behind their --inspect-* flag, which discloses what it touches.
  • Secrets never leak — redacted everywhere; --show-secrets reveals only a masked/partial value, with a warning.
  • Advise-only by default — never writes to your config files unless you pass --fix, which applies only safe, reversible tool-scope edits and backs up every file it touches first.
  • Fully stateless — writes only the report you explicitly ask for.
  • Passes its own scan — exposes no port, ships no plaintext secret.

Install

pipx install ianua-broker            # provides the `mcpscan` command
pipx install "ianua-broker[yaml]"    # + audit Continue's config.yaml
pipx install "ianua-broker[crypto]"  # + verify ed25519 LAN manifests / data-packs

The base install is stdlib-only (plus psutil). The optional [yaml] and [crypto] extras enable Continue config auditing and library-based Ed25519 manifest verification, respectively.

Or from source:

git clone https://github.com/IRsoctierDT/IANUA-Broker.git
cd IANUA-Broker && pipx install .

Requires Python 3.11+ (macOS, Linux, Windows).

Usage

mcpscan scan                          # scan localhost + cwd project configs
mcpscan scan --root ~/project         # scan a specific project root (repeatable)
mcpscan scan --json report.json       # also write a stable JSON report (0600)
mcpscan scan --html report.html       # also write a self-contained HTML report
mcpscan scan --sarif results.sarif    # also write SARIF 2.1.0 for code scanning
mcpscan scan --fail-on critical       # CI: exit non-zero only on Critical
mcpscan scan --online                 # opt-in OSV dep-vuln lookups (discloses egress)
mcpscan scan --emit webhook --emit-url  # emit findings/gate as an alert (opt-in)
mcpscan scan --inspect-token-stores   # opt-in: OAuth/session tokens at rest
mcpscan scan --inspect-process-env    # opt-in: secrets in running agent processes
mcpscan scan --inspect-telemetry      # opt-in: agent-host logging health
mcpscan scan --show-secrets           # reveal masked (first-2/last-2) values
mcpscan scan --fix                    # apply safe tool-scope fixes (backs up first)
mcpscan inventory                     # classified AI/MCP asset list (see below)
mcpscan atlas                         # findings mapped to security frameworks
mcpscan trust                         # per-agent Trust Score + risk relationships
mcpscan trust --min-grade B           # CI: fail if any tool grades below B
mcpscan graph                         # cross-server AI attack-path graph (see below)
mcpscan graph --graph-format dot      # Graphviz DOT export for visualization
mcpscan baseline --out base.json      # snapshot current posture (digest-signed)
mcpscan diff --baseline base.json --fail-on-regression   # drift gate for CI
mcpscan diff --baseline base.json --max-age-days 30      # + warn if the baseline is stale
mcpscan schedule --cadence daily      # generate an OS-native scheduled scan+diff
mcpscan selftest                      # verify the scanner's own detections still fire
mcpscan update-datapack --pack p.json --signature p.sig --allowed-signers s  # signed catalog refresh
mcpscan lan  --manifest auth.toml ... # authorized network assessment (see below)

.mcpscan-accept.json in a scanned root lets a named human risk-accept a specific tool-scope finding until a stated expiry — the finding still lowers the grade but stops failing the gate until it lapses (then it re-arms, loudly).

Exit code is non-zero when a finding meets --fail-on (default: high), so it drops straight into CI.

Example output

AI Agentic MCPscan — overall posture: F
  dimensions: credential=D, exposure=A, pinning=A, tool_scope=C
  findings: 1 critical, 1 high, 2 medium

▶ ~/.mcp.json#weather  [grade F]
  [CRITICAL] Plaintext OpenAI API key in config
             where: ~/.mcp.json
             secret: [redacted len=37 sha256:c0cc596e]
             fix:   Remove the literal value from the file. Reference it from a
                    secret manager … and rotate the exposed credential.
  [MEDIUM  ] Server 'weather' runs an unpinned package via npx
             fix:   Pin the package to an exact version (e.g. npx some-pkg@1.2.3).

Fixing findings (--fix)

The tool is advise-only by default. --fix is the one explicit exception that writes to your configs, and it stays deliberately conservative:

  • Scope: removes over-broad tool-scope grants only — dangerous (shell/exec-class) and wildcard entries from permissions.allow and each server's autoApprove, using the exact predicates the scanner flags with, so a fixed config re-scans clean.
  • Reversible: every modified file is copied to <path>.mcpscan.bak before the edit, and the file's permissions are preserved.
  • Nothing invented: credential and pinning findings are not auto-fixed — a safe rewrite would need a new home for the secret or a specific version the tool can't know offline, so those stay manual (the report tells you what to do).
$ mcpscan scan --root . --fix
note: --fix modifies config files in place (backup written to <path>.mcpscan.bak) …
fixed ./.mcp.json (1 change(s); backup: ./.mcp.json.mcpscan.bak)
    removed 'Bash(*)' from permissions.allow [SCOPE-DANGEROUS-ALLOW]
applied 1 fix(es). Re-run mcpscan to confirm.

AI/MCP asset inventory (mcpscan inventory)

Where scan judges posture, inventory answers what AI systems exist here — it classifies what the scanner discovers (host configs, declared servers, listening sockets) into a typed asset list: agent hosts, MCP servers, model servers (Ollama, vLLM, LM Studio, llama.cpp), OpenAI-compatible inference endpoints, LLM gateways (LiteLLM), and vector databases (Qdrant, Chroma, Weaviate, Milvus). Three evidence tiers set the confidence: exact process name or a product endpoint fingerprint (high), a generic OpenAI-compatible or MCP transport surface (medium), a default-port hint alone (low).

$ mcpscan inventory
AI Agentic MCPscan — inventory: 3 asset(s)

▶ MCP servers (1)
  MCP server (HTTP transport)  [medium confidence]
    where:    127.0.0.1:40239
    process:  claude (pid 552)
    evidence: responded on /mcp (HTTP 405)

▶ Model servers (1)
  Ollama  [high confidence]
    where:    127.0.0.1:11434
    process:  ollama (pid 903)
    evidence: process name 'ollama'
…

Inventory observes, never judges: it carries no severities, always exits 0, and --json gives the stable machine-readable form. Fingerprinting stays inside the trust boundary — loopback-only bare GETs (--no-probe disables even that), response bodies are treated as hostile and never reach the output. Unrecognized services are deliberately not listed: a plain web server is scan's exposure concern, not an AI asset.

Agent trust analysis (mcpscan trust)

Where scan grades hygiene, trust asks what each agent tool is trusted to do and access — and, crucially, which combinations make it a lateral-movement risk. Every MCP server gets a Trust Score (0–100) across five factors — secret access, tool privilege, autonomy (auto-approval), code provenance, and network reach (a bind hint beyond loopback) — and the dangerous factor combinations are surfaced as risk relationships that no single hygiene check sees:

$ mcpscan trust
▶ 'db' [claude]  Trust 25/100 (grade F)
    · secret_access: holds 1 credential in its environment (+25 risk)
    · tool_privilege: auto-approves 1 dangerous tool(s) (+25 risk)
    · autonomy: auto-approves 1 tool(s) with no human in the loop (+15 risk)
    · code_provenance: runs an unpinned / remotely-fetched package (+10 risk)
    ⚠ PRIVILEGED-SECRET-HOLDER — a single compromise leaks the secrets and the
      power to use them.
    ⚠ AUTONOMOUS-PRIVILEGED — dangerous tools auto-approved, no human in the loop.

The relationships are the differentiator: PRIVILEGED-SECRET-HOLDER (secrets + dangerous tools), AUTONOMOUS-PRIVILEGED (auto-approves dangerous tools), AUTONOMOUS-SECRET-HOLDER, UNVETTED-PRIVILEGED (unpinned code + dangerous tools), AUTONOMOUS-EXFIL-PATH (autonomy × privilege × secrets — an unattended exfiltration path), EXPOSED-PRIVILEGED (network-reachable × dangerous tools), and SHARED-CREDENTIAL (one secret spanning several tools — a cross-tool blast radius). Scoring reuses the exact predicates scan trusts, so the two never diverge. --min-grade makes it a CI gate; --json emits the full analysis; a profile is secretless (a credential count, never a value). Read-only and offline.

AI attack-path graph (mcpscan graph)

Where trust scores each server on its own, graph chains them: an AI attack-path graph (not a network graph) that reasons about tool and trust chaining — how an attacker who lands on an exposed surface pivots, via a shared credential and a privileged/autonomous tool, to a high-value target.

$ mcpscan graph
AI Agentic MCPscan — attack paths: 1 path(s) (1 critical, 0 high); overall grade F

[CRITICAL] exposed 'db' (wildcard / public bind) -> shared credential GITHUB_TOKEN
           -> 'shell' (autonomous, dangerous tools) -> GitHub
    why: a credential shared across servers lets the attacker pivot from the
         exposed server to another that holds the same secret; 'shell'
         auto-approves dangerous tools, so it acts with no human in the loop.

It composes over what the tool already collects — trust factors, reachability tiers, shared-credential fingerprints, and the inventory — plus one safe inference: a credential's key name (GITHUB_TOKEN, never its value) maps to the target it unlocks. Path severity follows the entry's reachability (wildcard/public → Critical, private-LAN → High), with a shared-credential cross-server pivot escalating to Critical. --graph-format dot exports Graphviz for visualization; --json emits the full node/edge/path model; --fail-on makes it a CI gate. Pure, offline, read-only, and secretless (no raw value reaches the terminal, JSON, or DOT).

Drift detection (mcpscan baseline / mcpscan diff)

Turn the one-shot scan into continuous posture. mcpscan baseline writes a normalized, byte-stable snapshot of the current posture (findings, server exposure, and the AI/MCP inventory) with an integrity digest; mcpscan diff compares a fresh scan against it and reports what drifted — regressions first:

$ mcpscan diff --baseline base.json --fail-on-regression
AI Agentic MCPscan — drift: 4 change(s) (2 regression(s), 0 improvement(s))
  + [REGRESSION ] SCOPE-DANGEROUS-ALLOW — Dangerous tool auto-allowed: 'Bash(*)'
  + [REGRESSION ] PIN-UNPINNED — Server 'db' runs an unpinned package via npx
  ~ [REGRESSION ] socket://…:8000   exposure: local → exposed

The direction is the point: a new finding or a newly-exposed server is a regression; a resolved finding or a server that stopped being exposed is an improvement; new/removed assets are informational. A disappearing security control surfaces as a new finding (the check that the control was present now fires). --fail-on-regression exits non-zero only on regressions, so diff drops into CI to block posture backsliding — commit a signed baseline, then diff every change against it. The baseline's digest is re-verified on load, so an edited or corrupted baseline is refused rather than trusted. --json emits the full machine-readable drift; --no-inventory snapshots posture only.

Framework mapping (mcpscan atlas)

atlas renders the same findings scan produces, each annotated with its security-framework citations — MITRE ATT&CK, MITRE ATLAS, OWASP LLM Top 10, NIST AI RMF (function level), and CIS Controls v8 (control level) — so a finding drops straight into an assessment report or a GRC tool.

$ mcpscan atlas
  [CRITICAL] CRED-PLAINTEXT: Plaintext High-entropy secret in config
             ↳ MITRE ATT&CK T1552.001 — Unsecured Credentials: Credentials In Files
             ↳ MITRE ATLAS AML.T0055 — Unsecured Credentials
             ↳ OWASP LLM Top 10 LLM02 — Sensitive Information Disclosure
             ↳ NIST AI RMF GOVERN — Govern function
             ↳ CIS Controls v8 Control 3 — Data Protection

--matrix prints the full static check-id → framework table without scanning; --json emits mapped findings plus the matrix. The mapping table is deliberately conservative — a citation appears only where the technique/control match is direct, NIST AI RMF stays at function level and CIS at control level — and it lives in one auditable data file (src/mcpscan/atlas/model.py), with CI gating that every check id the scanner can emit has a mapping and no mapping outlives its check. Exit-code semantics match scan (--fail-on).

GitHub code scanning (SARIF)

--sarif writes a SARIF 2.1.0 log that GitHub ingests as code-scanning alerts on the Security tab, with per-finding severity (security-severity) and stable fingerprints so alerts track across commits. Paths inside the scanned repo are emitted repo-relative so alerts annotate the offending line; secrets are never present (only the redacted fingerprint). SARIF covers config-file findings; running-socket exposure (no source file) stays in the terminal/JSON/HTML views. Drop this into a workflow:

permissions:
  contents: read
  security-events: write
steps:
  - uses: actions/checkout@v7
  - uses: actions/setup-python@v5
    with: { python-version: "3.11" }
  - run: pip install ianua-broker
  - run: mcpscan scan --sarif results.sarif --fail-on critical
    continue-on-error: true
  - uses: github/codeql-action/upload-sarif@v4
    with: { sarif_file: results.sarif }

This repo dogfoods it in .github/workflows/mcpscan.yml.

Authorized network assessment (mcpscan lan)

mcpscan scan is localhost-only. mcpscan lan is a separate, gated command for assessing MCP exposure on hosts you are authorized to test — and it is inert without a signed authorization manifest. Governing principle: discovery never converts into authority. It is exposure-only (never reads a remote config), private-address by default, and bounded by immutable budgets.

mcpscan lan --manifest auth.toml \
            --signature auth.toml.sig \
            --allowed-signers allowed_signers \
            --invoker human \
            --dry-run            # verify + print the plan; send no packets

The manifest is a signed TOML file naming exact targets, ports, operator, and expiry:

authorization_id = "ENG-2026-0710"
operator         = "you@example.com"
expires_at       = "2026-07-10T23:59:59Z"
targets          = ["192.168.10.20/32"]   # exact hosts / /32 (a human may use a capped CIDR)
ports            = [3000, 8000]

Sign it with your SSH key (ssh-keygen -Y sign -n mcpscan-lan -f key auth.toml). --invoker agent gets tighter budgets and exact-hosts-only. Public targets are refused unless named in an --enterprise-policy file. --json and --sarif both work: because a LAN finding's location is a network endpoint (not a source file), --sarif emits it as a SARIF logical location (kind: resource, fullyQualifiedName: lan://host:port) — standards-valid for generic SARIF and SIEM/audit consumers, not GitHub code scanning (which needs a checkout file to raise an alert). No synthetic file path is ever invented; see ADR-16. Step-by-step: docs/LAN_OPERATOR_GUIDE.md; full design and threat model: docs/proposals/LAN_SCANNING.md.

Documentation

Doc What it is
docs/SPEC.md Full product & technical specification (testable requirements, scoring rubric, threat model, DoD).
docs/DECISIONS.md 15 architecture decision records.
docs/ARCHITECTURE.md Component model, dependency direction, trust boundaries.
docs/BACKLOG.md Sprint-tagged tickets + requirement→ticket traceability.
docs/SECURITY_SIGNOFF.md Threat-model verification matrix (security sign-off).
docs/agents/ MCP Sentinel agent suite — governed agent roles, registry, and operating model.
SECURITY.md · CONTRIBUTING.md Reporting policy · contributor guide.

Status & roadmap

Released on PyPI as ianua-broker (the mcpscan command) — stable and production-ready, behind a green CI gate (ruff, mypy --strict, bandit, pytest with a 90% branch-coverage floor, on macOS/Linux/Windows × Python 3.11–3.13), with SBOM + checksums on every release. It ships seven host adapters (Claude, Cursor, Windsurf, Cline, VS Code, Zed, Continue), SARIF 2.1.0 + a GitHub code-scanning workflow, opt-in --fix, mcpscan lan (authorized, signed-manifest network assessment), and a dogfood harness that gates every check against a clean+messy corpus across all hosts (0 false positives / 0 false negatives, run in CI).

The CLI surface, JSON report schema, and check ids are covered by semver: breaking changes to any of them mean a major version bump.

Since 1.0, three hardening waves have landed on top of the platform tiers in docs/proposals/VISION.md (inventory, atlas, trust, and baseline/diff drift): continuous-validation foundations (validation-age staleness, a named-human risk-acceptance ledger, drift-cause tags, reused-credential detection); detection reach & quiet-read surfaces (the --emit alert layer, mcpscan schedule, token-store and running-process credential inspection, and an autonomous-exfiltration trust composite); and hardening & extensibility (reachability tiering, --online dependency-vuln lookups, tool-integrity heuristics, agent-host telemetry checks, mcpscan selftest, and a signed detection data-pack refresh channel). graph (Tier 3 — the cross-server AI attack-path graph) has now landed, completing the platform tiers. Next: real-lab dogfooding (stakeholder configs + a pfSense/Suricata network lab).

License

Apache-2.0.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ianua_broker-1.3.0.tar.gz (331.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ianua_broker-1.3.0-py3-none-any.whl (193.3 kB view details)

Uploaded Python 3

File details

Details for the file ianua_broker-1.3.0.tar.gz.

File metadata

  • Download URL: ianua_broker-1.3.0.tar.gz
  • Upload date:
  • Size: 331.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ianua_broker-1.3.0.tar.gz
Algorithm Hash digest
SHA256 ea00b4ee957f5bf4730de08b1a95e118edd78b92ac65828ef86fc7dadce122cd
MD5 30fae9e2b66ad8d843a27776b50fb404
BLAKE2b-256 d5f70f3c61de57bd72b2f33360abb704dab64f6c5cc853dc1899c1e5ccc5ef47

See more details on using hashes here.

Provenance

The following attestation bundles were made for ianua_broker-1.3.0.tar.gz:

Publisher: release-please.yml on IRsoctierDT/IANUA-Broker

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ianua_broker-1.3.0-py3-none-any.whl.

File metadata

  • Download URL: ianua_broker-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 193.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ianua_broker-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 211f8b0581758680ea8e3ebc8270fc3bb6a8a5a236c099bbb352445a138d3293
MD5 3c5a744c4ef6243a03c371dfb65fbac6
BLAKE2b-256 1435d606e3f10cb24e24e2949ca32a01446f69fb65edbb031ae23f431d1c750e

See more details on using hashes here.

Provenance

The following attestation bundles were made for ianua_broker-1.3.0-py3-none-any.whl:

Publisher: release-please.yml on IRsoctierDT/IANUA-Broker

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page