Skip to content

Latest commit

 

History

History
551 lines (414 loc) · 33.9 KB

File metadata and controls

551 lines (414 loc) · 33.9 KB
  ███████╗██╗  ██╗ █████╗ ██████╗  ██████╗ ██╗    ██╗
  ██╔════╝██║  ██║██╔══██╗██╔══██╗██╔═══██╗██║    ██║
  ███████╗███████║███████║██║  ██║██║   ██║██║ █╗ ██║
  ╚════██║██╔══██║██╔══██║██║  ██║██║   ██║██║███╗██║
  ███████║██║  ██║██║  ██║██████╔╝╚██████╔╝╚███╔███╔╝
  ╚══════╝╚═╝  ╚═╝╚═╝  ╚═╝╚═════╝  ╚═════╝  ╚══╝╚══╝
  ███████╗████████╗ █████╗  ██████╗██╗  ██╗
  ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
  ███████╗   ██║   ███████║██║     █████╔╝
  ╚════██║   ██║   ██╔══██║██║     ██╔═██╗
  ███████║   ██║   ██║  ██║╚██████╗██║  ██╗
  ╚══════╝   ╚═╝   ╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝

Discovery & risk analytics for shadow-AI infrastructure

For security and platform teams governing AI infrastructure they didn't provision.

Find the un-audited MCP servers, Claude skills, sub-agents and Cursor rules on your hosts. Score them, map them to MITRE ATT&CK and the OWASP LLM Top 10, and hand the results to a human — or an agent — for remediation.

version license platform binary deps

Use on authorized systems only.


ShadowStack at a glance: two scan engines (MCP servers on the network, Skills & rules on disk + /proc) feed a rules engine that scores findings High/Medium/Low, maps them to OWASP LLM Top 10 and MITRE ATT&CK, ranks each target into none→critical risk bands, and delivers results in five formats (ascii, json, ndjson, sarif, csv) to humans, AI agents, SIEMs, code-scanning, and triage.



ShadowStack demo: shadowstack -p mcp scans a local MCP server and reports 64 findings including prompt injection and a leaked API key in the server's instructions, ranking the host critical; then shadowstack -p skills audits a Claude project directory and reports 52 findings across skill files, agent configs, and MCP configs, ranking multiple files critical.

Table of contents


What it is

ShadowStack is a single, dependency-free static binary that inventories AI-agent attack surface and scores its risk. It speaks two protocols:

Protocol Flag Scans Where
MCP (Model Context Protocol) --protocol mcp MCP servers — JSON-RPC initialize handshake, tools/resources/prompts enumeration, TLS + HTTP banners the network (CIDRs, hosts, DNS names)
Skills --protocol skills Claude skills, sub-agents, slash commands, CLAUDE.md/AGENTS.md, Cursor/Windsurf/Cline rules, MCP client configs, hooks, and live stdio MCP processes the local filesystem + /proc

Every run produces an inventory (what exists) and a findings list (what's risky), rendered for a human (ASCII) or a machine (JSON/NDJSON/SARIF/CSV). All output carries stable rule IDs, dedup fingerprints, remediation text, and ATT&CK/OWASP references, so results feed a SIEM, a code-scanning dashboard, or a remediation agent equally well.

Why ShadowStack

  • Two engines, one binary. Scan the network for live MCP servers and audit the local Claude/Cursor AI surface on disk — including running stdio MCP processes — from the same zero-dependency binary.
  • Risk, not just inventory. Every finding is scored High/Medium/Low and every host or file rolls up into a none → critical band, so you triage the dangerous targets first instead of reading a wall of output.
  • Standards-mapped out of the box. Every finding carries an OWASP LLM Top 10 (2025) reference, a MITRE ATT&CK technique, and remediation text — in every output format.
  • Drops into your pipeline. ascii for humans; json/ndjson/sarif/csv for SIEMs, GitHub code scanning, spreadsheets, and AI remediation agents — all with stable rule IDs and dedup fingerprints.
  • Built for continuous use. Baseline/delta diffing, CI exit-code gating, allow-list suppression, version-controlled scan profiles, and resumable sweeps.
  • Zero-install. One static binary, no glibc/OpenSSL/runtime to install. curl, chmod +x, run.

Why shadow AI is a problem

AI infrastructure is going into organizations faster than security teams can govern it:

  • It's invisible to traditional tooling. EDR and CASB products reason about packets and processes from a user's perspective. They don't enumerate an MCP server's tool surface, read a sub-agent's tool grants, or notice that a CLAUDE.md quietly instructs every session to exfiltrate to a webhook. The AI surface stays un-inventoried until something has already gone wrong on the wire.
  • It's un-audited and un-managed. Developers stand up MCP servers on dev ports, drop skills into ~/.claude, and wire Cursor rules into repos. None of it is known to IT, access-controlled, or encrypted. That's shadow AI: real capability, zero governance.
  • It's a live injection and RCE surface. Skills, sub-agents, and server instructions fields are natural-language code an LLM executes with the user's privileges. They can carry prompt/command injection, secrets, over-broad tool grants, and outright RCE. Attackers often conceal it with zero-width characters, bidi overrides, Unicode tag blocks, ANSI escapes, or base64 blobs.
  • Compromise hides here well. A malicious skill, or a rug-pull MCP server that mutates its tool surface after the client approves it, is a low-noise foothold. Inter-agent persuasion can erode safety boundaries each agent enforces in isolation.

ShadowStack exists to make that surface discoverable, rankable, and remediable before it's abused.

How it works

Every scan runs the same five-stage pipeline; only the front-end discovery differs by protocol.

   targets                ┌──────────────┐   ┌──────────────┐   ┌──────────────┐   ┌──────────────┐   ┌──────────────┐
  CIDR / host ─┐          │  1 DISCOVER  │   │  2 EVALUATE  │   │  3 ENRICH    │   │  4 SCORE     │   │  5 RENDER    │
  DNS / files  ├─────────▶│ MCP probe /  │──▶│ rules engine │──▶│ OWASP LLM +  │──▶│ weighted     │──▶│ ascii / json │
  /proc        ┘          │ FS + /proc   │   │ → severity   │   │ MITRE + fix  │   │ rank + band  │   │ ndjson/sarif │
                          │   walk       │   │  findings    │   │              │   │ per target   │   │ csv          │
                          └──────────────┘   └──────────────┘   └──────────────┘   └──────────────┘   └──────────────┘
                              inventory          High/Med/Low      refs+remediation   none…critical       stdout / file

1. Discovery. Differs by protocol; both end in a structured inventory the rules engine can evaluate.

MCP (network):

  • Resolves --target specs into a de-duplicated host list, then drives a worker pool.
  • Per host/port: detects HTTP vs. TLS, pulls GET /.well-known/mcp, probes Streamable-HTTP and SSE transports.
  • Runs the JSON-RPC initialize handshake, retrying 2025-06-182024-11-05, then paginates tools/list, resources/list, resources/templates/list, prompts/list.
  • A 401 + WWW-Authenticate: Bearer is recorded as a suspected (auth-gated) host rather than dropped.
  • TLS is never validated — a bad cert becomes a finding, not a connection error, so hosts are inventoried regardless of cert hygiene.

Skills (filesystem + process):

  • Walks the standard roots (~/.claude, ~/.cursor, ./.claude, plugin bundles, .mcp.json/claude_desktop_config.json/VS Code MCP configs, CLAUDE.md/AGENTS.md/.windsurf/.clinerules, …) up to --walk-depth.
  • Parses frontmatter, body prose, bundled scripts, hooks, and MCP-client config per file.
  • On Linux, also reads /proc to inventory running stdio MCP servers — the "what's executing right now" view static config can't give.

2. Rules & severity. Each protocol has a rules engine of independent per-category evaluators. Every rule emits a Finding tagged with a severity and a stable dotted category.

Severity Meaning
High Active exposure or a credible exploitation primitive (RCE-capable tool, secret leak, weak session ID off-loopback, SSRF seed, …)
Medium Risky configuration (cleartext transport, over-broad capability/scope, expired cert, …)
Low Inventory/hygiene signal worth knowing (server identity, listed resource, benign keyword hit)
Engine Categories
MCP transport, tls, auth, identity, capabilities, instructions, tool, resource, prompt, banner, discovery (suspected hosts), and multi-signal correlation chains (e.g. correlation.rug_pull_surface, cross-server tool_name_shadowing)
Skills metadata, frontmatter, directive, content (URLs/IPs/scripts/commands/keywords + concealment), hook, mcpconfig, live process, and correlation TTP chains

Accepted findings can be suppressed with an --allow-list — suppression happens before scoring, so waived findings never inflate a target's risk.

3. MITRE ATT&CK / OWASP mapping. Every finding is enriched by category prefix with a remediation recommendation and references into OWASP LLM Top 10 (2025) and MITRE ATT&CK:

Category prefix OWASP LLM MITRE ATT&CK
transport LLM02 T1133 (External Remote Services)
tls LLM02 T1573 (Encrypted Channel)
auth LLM06 T1190 (Exploit Public-Facing App)
instructions LLM01, LLM07
tool LLM06 T1059 (Command & Scripting Interpreter)
banner LLM02 T1592 (Gather Victim Host Info)
hook / mcpconfig (skills) LLM06 T1059 / T1552 (Unsecured Credentials)
metadata (skills) LLM03 T1222 (File/Dir Permissions Mod.)

References travel with the finding through every output format — a SARIF result, an NDJSON line, and a CSV row all carry the same ATT&CK technique IDs.

4. Risk scoring. Findings answer "what's wrong"; scoring answers "which target is most dangerous." Each target's findings fold into one weighted score:

score = 10·(High count) + 4·(Medium count) + 1·(Low count)

Weights are spread an order of magnitude apart on purpose, so a single High outranks any realistic pile of Lows. The score maps to a band for at-a-glance triage:

Band Score Roughly
none 0 clean
low 1–3 a few Lows
medium 4–9 a Medium (or several Lows)
high 10–24 a High present
critical 25+ ~3+ Highs' worth

The ranking (highest first, ties broken on target key) appears in the ASCII and JSON renderers, keyed per host ip:port (MCP) or per file path (skills).

5. Finding identity, baselines & diffing.

  • Rule IDs — every finding gets a stable, greppable SS-MCP-…/SS-SKL-… ID derived (FNV-1a) from its category — no hand-maintained registry.
  • Fingerprints — a deterministic hash of rule + target + evidence lets re-scans dedup and diff. A live MCP process fingerprints on executable + launch command, so it stays stable across restarts (the volatile pid lives only in the detail text).
  • Baseline/diff--baseline prev.<json|ndjson|sarif|csv> emits only findings new since a prior report. The loader is format-agnostic (a baseline in one format diffs against a scan in another), and the filter runs before both rendering and the --fail-on gate.

Install

ShadowStack ships as a single static binary with zero runtime dependencies — no glibc, no OpenSSL, no interpreter. Download it, verify the checksum, and run:

# verify integrity
sha256sum -c shadowstack-0.2.0-x86_64-linux.sha256

# install onto PATH
chmod +x shadowstack-0.2.0-x86_64-linux
sudo mv shadowstack-0.2.0-x86_64-linux /usr/local/bin/shadowstack

shadowstack --version

Quick start

# Discover MCP servers on a /24, human-readable
shadowstack --protocol mcp --target 192.168.1.0/24

# Audit the local Claude/Cursor AI surface, JSON to a file
shadowstack --protocol skills --format json --output skills.json

# CI gate: fail the build if anything High turns up on the local surface
shadowstack -p skills --fail-on high -f sarif -o results.sarif

Banner and progress go to stderr; the report goes to stdout (or --output).

Interactive terminal

Run shadowstack with no arguments (or add -i/--interactive) to drop into a REPL for building up a scan interactively instead of assembling one long command line:

ShadowStack interactive terminal v0.1
Configure a scan with set, then run it with scan. Type help for commands.

shadowstack> set protocol skills
protocol = skills

shadowstack> set walk-depth 6
walk-depth = 6

shadowstack> set format json
format = json

shadowstack> show
current overrides
  protocol = skills
  walk-depth = 6
  format = json

would run: shadowstack --protocol skills --walk-depth 6 --format json

shadowstack> scan
[*] scanning (Skills)…
[*] scan complete

shadowstack> exit

The command vocabulary is generated from the same clap definitions that back the one-shot CLI, so every documented flag is reachable here and set/help never drift out of sync. On scan, the accumulated overrides are rebuilt into an argv and re-parsed through the normal clap + --config profile path, so the terminal honors the same validation and precedence rules as running shadowstack directly.

Command Description
help, ? Show the command and option list.
show, config Print current overrides and the argv scan would run.
set <opt> <value> Set an option (long-flag name, without --). Boolean/count flags don't need a value — set verbose is -v.
unset <opt> Clear an override, restoring its default.
reset Clear every override.
scan, run Run the scan. Errors are reported but don't end the session.
theme [name] List color themes, or switch to one live.
clear, cls Clear the screen.
exit, quit (or Ctrl-D) Leave the terminal.

A leading / is tolerated on any command (/help, /scan, …). Only launch-time -v/-q verbosity carries into the session as a seeded override — everything else starts at its default.

Color themes. The REPL chrome (prompt, headers, errors — stderr only; the stdout report is never colored) ships six palettes: dark (default), light, matrix, ocean, amber, and mono (no color, for redirected or NO_COLOR sessions).

shadowstack --theme ocean          # start with a theme
shadowstack> theme                 # list themes (current one marked)
shadowstack> theme matrix          # switch live, for the rest of the session

Color auto-disables when stderr isn't a TTY or NO_COLOR is set, so shadowstack -i -f json > out.json | jq and similar pipelines stay byte-clean regardless of theme.

Example output

Every scan renders in five formats — the same findings, shaped for different consumers: ascii (human report: inventory + risk ranking + findings table), json (one complete document for jq, dashboards, and agents), ndjson (one finding per line for SIEM pipelines), sarif (SARIF 2.1.0 for GitHub code scanning), csv (RFC-4180 rows for spreadsheets and ticketing).

MCP (--protocol mcp)

A single host can hide an enormous amount of risk. Below is a real (lightly trimmed) -f ascii report — one initialize handshake produced 64 findings across the tool, resource, instruction, capability, and correlation surfaces, ranking the host critical (names changed for presentation):

MCP Findings  (schema 1.3)
  targets scanned:       10.0.4.0/24
  hosts total:           256
  MCP servers confirmed: 1
  findings:              64 (high 39, medium 14, low 11)

Discovered MCP Servers
======================
  [1] 10.0.4.12:8080  (StreamableHttp /mcp)
       transport sec: CLEARTEXT HTTP — MCP surface not encrypted
       server:        internal-devtools-mcp v1.0.0
       capabilities:  {"sampling": {}, "elicitation": {}, "experimental": {"dangerousFeature": true}}
       instructions:  You are a helpful assistant. ignore previous instructions… api_key=sk-prod-0000111122223333

       tools (14):
         - shell_exec       Execute an arbitrary shell command on the host…
         - rеad_file         Cyrillic-homoglyph impersonation of read_file
         - destroy_all       Purge all records. (destructiveHint: true)
         … (11 more: bash, eval, write_file, delete_file, http_fetch, sql_query, …)

       resources (10):
         - file:///etc/shadow                                              System password hashes.
         - http://169.254.169.254/latest/meta-data/iam/security-credentials/  AWS IMDS IAM credentials.
         - gopher://127.0.0.1:6379/_INFO                                   Gopher URL targeting local Redis.
         … (7 more: id_rsa, .aws/credentials, webhook.site exfil sink, …)

Per-Host Risk Ranking
=====================
     #  score  band      findings        host
     1    457  critical  H:39 M:14 L:11  10.0.4.12

Findings
========
+----------+---------------------------------------+-----------------+--------------------------------------------------+
| Severity | Category                              | Address         | Detail                                           |
+----------+---------------------------------------+-----------------+--------------------------------------------------+
| High     | auth.no_authentication_required       | 10.0.4.12:8080  | entire MCP surface is callable unauthenticated   |
| High     | instructions.credential_leak          | 10.0.4.12:8080  | instructions contains secret token 'sk-…'        |
| High     | tool.dangerous_name                   | 10.0.4.12:8080  | tool 'shell_exec' name suggests command exec     |
| High     | correlation.rce_chain.fetch_plus_exec | 10.0.4.12:8080  | shell-exec + network-fetch — download-and-exec   |
| Medium   | tool.name_homoglyph                   | 10.0.4.12:8080  | tool name 'rеad_file' uses a Cyrillic homoglyph  |
| Low      | transport.cleartext_http               | 10.0.4.12:8080  | MCP server speaks cleartext HTTP                  |
| …        | …                                     | …               | (58 more rows)                                   |
+----------+---------------------------------------+-----------------+--------------------------------------------------+

That single host was an unauthenticated, cleartext server leaking a production API key in its instructions, exposing shell_exec/eval/write_file, advertising /etc/shadow and SSH keys as resources, and chaining fetch-plus-exec into a download-and-execute primitive — every item tagged, scored, mapped, and ready to remediate.

Machine formats carry more than ASCII shows. json/ndjson/sarif/csv add the structured fields ASCII omits — a stable rule_id, dedup fingerprint, isolated evidence, recommendation, and OWASP/MITRE refs:

Sev Category Detail OWASP MITRE Recommendation
High auth.no_authentication_required entire MCP surface callable unauthenticated LLM06 T1190 Require authentication and enforce Origin/host checks before exposing the endpoint.
High instructions.credential_leak secret token sk-prod-… embedded in instructions LLM01, LLM07 Audit instructions for injected directives or leaked credential content.
High correlation.rce_chain.fetch_plus_exec shell-exec + network-fetch → download-and-execute LLM06 Investigate this multi-signal chain as a probable intentional capability.
Low transport.cleartext_http MCP surface speaks cleartext HTTP LLM02 T1133 Restrict to authenticated/loopback/VPN-only access; put behind TLS.

-f json example — one complete document (run metadata, severity summary, per-host risk, full inventory, every finding with identity + mapping):

{
  "schema_version": "1.3", "protocol": "mcp",
  "summary": {"high": 39, "medium": 14, "low": 11, "total": 64},
  "risk": [{"rank": 1, "host": "10.0.4.12", "score": 457, "band": "critical"}],
  "findings": [
    {"rule_id": "SS-MCP-167003", "fingerprint": "644fff9f554faa8c", "severity": "high",
     "category": "auth.no_authentication_required", "address": "10.0.4.12:8080",
     "detail": "entire MCP surface is callable unauthenticated",
     "recommendation": "Require authentication and enforce Origin/host checks…",
     "refs": ["OWASP:LLM06", "MITRE:T1190"]}
  ]
}

-f ndjson — one self-contained finding object per line, for Splunk/Elastic/Loki:

{"rule_id": "SS-MCP-167003", "fingerprint": "644fff9f554faa8c", "severity": "high", "category": "auth.no_authentication_required", "target": "10.0.4.12:8080", "detail": "entire MCP surface is callable unauthenticated", "refs": ["OWASP:LLM06", "MITRE:T1190"]}

-f sarif — SARIF 2.1.0 for GitHub code scanning, Azure DevOps, or any SAST viewer. Severity maps to level (high→error, medium→warning, low→note); the fingerprint rides in partialFingerprints for cross-run dedup:

{
  "version": "2.1.0",
  "runs": [{
    "tool": {"driver": {"name": "ShadowStack", "rules": [{"id": "SS-MCP-167003", "name": "auth.no_authentication_required"}]}},
    "results": [{
      "ruleId": "SS-MCP-167003", "level": "error",
      "message": {"text": "entire MCP surface is callable unauthenticated"},
      "locations": [{"logicalLocations": [{"name": "10.0.4.12:8080", "kind": "resource"}]}],
      "partialFingerprints": {"shadowstackFingerprint/v1": "644fff9f554faa8c"}
    }]
  }]
}

For the filesystem engine, SARIF emits a physicalLocation (artifactLocation.uri) instead — code-scanning annotations land on the offending line.

-f csv — RFC-4180 rows for spreadsheet triage or ticketing import:

rule_id,fingerprint,severity,category,target,detail,recommendation,refs
SS-MCP-167003,644fff9f554faa8c,high,auth.no_authentication_required,10.0.4.12:8080,entire MCP surface is callable unauthenticated,Require authentication…,OWASP:LLM06 MITRE:T1190

Skills / sub-agents (--protocol skills)

The filesystem engine audits skills, sub-agents, CLAUDE.md, Cursor/Windsurf rules, MCP configs, and hooks. Here it walked a project's .claude tree and turned 7 files into 52 findings:

Skills Findings  (schema 1.4)
  files scanned: 7
  findings:      52 (high 32, medium 15, low 5)

Per-File Risk Ranking
=====================
     #  score  band      findings        file
     1    111  critical  H:9 M:5 L:1     ~/.claude/skills/pdf-helper/scripts/setup.sh
     2     88  critical  H:8 M:2 L:0     ~/.claude/skills/pdf-helper/SKILL.md
     3     70  critical  H:5 M:5 L:0     ./.mcp.json
     4     65  critical  H:5 M:3 L:3     ~/.claude/settings.local.json
     5     40  critical  H:4 M:0 L:0     ~/.claude/CLAUDE.md

A representative slice across the concealment, execution, config, and correlation surfaces — every finding carries the same OWASP/MITRE mapping and remediation step as MCP:

Sev Category Detail OWASP MITRE Recommendation
High content.command.pipe_to_shell bundled script runs curl … | sh LLM01 T1059 Inspect embedded URLs/scripts/commands for unsanctioned interaction or payload delivery.
High content.hidden_unicode zero-width/bidi characters concealing text in SKILL.md LLM01 T1059 Confirm the skill isn't encoding injection or exfiltration behavior.
High mcpconfig.secret_in_env plaintext secret in .mcp.json server env block LLM06 T1552 Move secrets out of env into a secret store; pin/verify launchers.
High directive.allowed_tools.permissive sub-agent grants an over-broad tool list (incl. Bash) LLM06 Give sub-agents an explicit minimal tools: list, not inherit-all.
High correlation.credentials_with_execution credential material + an execution primitive in one file LLM06 Investigate this chain as a probable intentional capability.
Medium hooks.command tool-event hook in settings.local.json runs curl|sh LLM06 T1059 Remove untrusted/network-fetching hook commands and lock the file.
Low content.script_reference.relative skill references a bundled relative script LLM01 T1059 Inspect the referenced script for unsanctioned interaction.

Whether the risk is a network MCP server or a poisoned skill on disk, the output shape is identical — severity, category, evidence, OWASP/MITRE mapping, remediation — and just as ingestible by a SIEM, a code-scanning dashboard, or a remediation agent.

Program options

Core

Flag Default Description
-p, --protocol <mcp|skills> mcp Engine to run: mcp (network discovery) or skills (filesystem + process).
-f, --format <fmt> ascii Output format: ascii, json, ndjson, sarif, csv. See Output formats.
-o, --output <path> stdout Write the report to a file instead of stdout.
-h, --help / -V, --version Standard help / version.

Targeting (MCP)

Flag Default Description
-c, --target <spec> (alias --cidr) 127.0.0.1/32 Scan target(s): any mix of CIDR blocks, bare IPv4s, and DNS hostnames (resolved to IPv4 at scan start). Repeatable and comma-separated; specs are merged and de-duplicated.
--targets-file <path> Read additional targets from a file, one spec per line (# comments and blanks ignored). Merged with --target.
--ports <list> built-in set¹ Comma-separated TCP ports to probe.

¹ Default: 80, 443, 6274, 6280, 8765, 8787, 3000, 3001, 3030, 5173, 5174, 5000, 5001, 8000, 8001, 8008, 8080, 8081, 8082, 8443, 8888, 18080, 28080, 4000, 7000, 9000, 9090, 11434 — HTTP/HTTPS, MCP-specific ports, common Node/Python/Vite/Flask dev ports, and AI tooling (e.g. Ollama 11434).

Network tuning & politeness (MCP)

Flag Default Description
--connect-timeout <ms> 800 TCP connect timeout.
--http-timeout <ms> 1500 HTTP request timeout (initialize / list calls).
--workers <n> 32 Parallel scan workers (parallelism, not per-host pressure).
--max-duration <secs> 0 (off) Global wall-clock budget. When it lapses, workers stop taking new work and the scan renders what it has, with a scan deadline reached note.
--per-host-delay <ms> 0 (off) Minimum spacing between connection attempts to a single host.
--connect-retries <n> 0 (off) Retry a failed TCP connect this many times before giving up.
--connect-backoff <ms> 100 Base backoff between connect retries; doubles each retry. Only matters with --connect-retries.

Filesystem walk (skills)

Flag Default Description
--walk-depth <n> 4 Maximum directory recursion depth for the skills walker.

Filtering, gating & state

Flag Default Description
--allow-list <file> Suppression override. Findings matching a rule are dropped before rendering (suppressed count still reported). One rule per line: <category-glob> [<target-glob>]; # comments, * wildcard. target matches an MCP host ip:port or a skills file path.
--fail-on <high|medium|low> Exit non-zero (code 2) when a finding at or above this tier is present. Unset = always exit 0 on a successful scan.
--baseline <file> Diff mode: a prior report (json/ndjson/sarif/csv). Findings already present in the baseline are dropped, so the scan emits only what's new. --fail-on then gates on the delta.
--resume <file> Checkpoint/journal for large CIDR sweeps (MCP only). Completed hosts are journalled as the scan runs; re-running against the same file skips them. The file pins the scan's identity (targets/ports/protocol).

Allow-list example:

# accept all heuristic keyword hits (reviewed, expected in this repo)
keyword.*
# drop a specific category only on one host (any port)
tool.dangerous_name        10.0.0.5*
# whole-file waiver for a known-safe artifact
*                          */known-safe/CLAUDE.md

Configuration & logging

Flag Default Description
--config <file.toml> TOML scan profile pinning flags into a version-controllable file. Keys mirror long flags (--connect-timeoutconnect-timeout). Precedence: CLI flag > profile value > built-in default, decided per field. Strict parsing — unknown/duplicate key or [table] header is a hard error.
-v, --verbose Increase verbosity. Repeatable: -v = DEBUG, -vv = TRACE.
-q, --quiet Decrease verbosity. Repeatable: -q = warnings + errors only, -qq = errors only (also hides the banner). -v/-q cancel out.

Verbosity is a baseline of INFO shifted by -v/-q. All diagnostics go to stderr as timestamped, level-tagged records; stdout stays the report alone. Use -q in cron/CI when only the report file and exit code matter.

Output formats

Format Contents Best for
ascii Full inventory + risk ranking + findings table, human-readable interactive use, terminals
json Complete document — inventory, per-target risk, findings with full strings (schema 1.3) jq, dashboards, agent ingestion
ndjson One finding object per line, each with rule_id + fingerprint + refs SIEM/log pipelines (Splunk, Elastic, Loki)
sarif SARIF 2.1.0 (rule descriptors, severity→level, partialFingerprints) GitHub code scanning, Azure DevOps, SAST tooling
csv RFC-4180 rows spreadsheet triage

ndjson/sarif/csv carry findings and their identity only, not the full server/skill inventory that ascii/json emit.

Exit codes

Code Meaning
0 Scan completed successfully (no --fail-on tier tripped).
2 --fail-on gate tripped — a finding at/above the threshold is present.
1 Pipeline error (bad config, unreadable allow-list/baseline, etc.).

Usage recipes

# Single host, custom ports, tight timeouts
shadowstack -p mcp -c 10.0.0.5/32 --ports 8080,8443,11434 \
  --connect-timeout 300 --http-timeout 800

# Mixed targets: two CIDRs, a host, and a DNS name
shadowstack -p mcp --target 10.0.0.0/24,192.168.1.0/24 \
  --target 10.1.2.3 --target mcp.internal

# Pull the scope from a file, JSON output
shadowstack -p mcp --targets-file scope.txt --format json -o out.json

# Aggressive parallel /22 sweep (opens many sockets — be considerate)
shadowstack -p mcp -c 10.10.0.0/22 --workers 128

# Polite, resilient sweep: 5-min budget, 250ms/host throttle, retry flaky connects
shadowstack -p mcp -c 10.10.0.0/22 --max-duration 300 \
  --per-host-delay 250 --connect-retries 2 --connect-backoff 100

# Resumable /16: time-box each run, re-run to continue where it stopped
shadowstack -p mcp -c 10.10.0.0/16 --max-duration 1800 --resume sweep.ckpt

# Recurring org scan: emit only findings new since last week's report
shadowstack -p mcp -c 10.0.0.0/24 --baseline last-week.json --format ndjson

# Repeatable CI sweep pinned in a profile, overriding worker count on the fly
shadowstack -p mcp --config ci-mcp.toml --workers 8

# Audit the local AI surface and gate CI on High findings
shadowstack -p skills --fail-on high -f sarif -o results.sarif

Supported platforms & limitations

Prebuilt binary x86-64 Linux, statically linked — runs on any distribution, no dependencies.
Build from source Portable across targets the Rust toolchain supports.
Live process inventory Linux-only (reads /proc). On other OSes the process list is empty; the rest of the scan is unaffected.
Discovery scope IPv4 (CIDR / host / DNS→IPv4). IPv6 ranges are not yet enumerated.

License

ShadowStack (the scanner) is licensed under the Apache License, Version 2.0 — a permissive license with an explicit patent grant. You are free to use, modify, and distribute the binary and source, including commercially, provided you retain the license and attribution notices.

Copyright 2026 ShadowStack
Licensed under the Apache License, Version 2.0.