███████╗██╗ ██╗ █████╗ ██████╗ ██████╗ ██╗ ██╗
██╔════╝██║ ██║██╔══██╗██╔══██╗██╔═══██╗██║ ██║
███████╗███████║███████║██║ ██║██║ ██║██║ █╗ ██║
╚════██║██╔══██║██╔══██║██║ ██║██║ ██║██║███╗██║
███████║██║ ██║██║ ██║██████╔╝╚██████╔╝╚███╔███╔╝
╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚══╝╚══╝
███████╗████████╗ █████╗ ██████╗██╗ ██╗
██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
███████╗ ██║ ███████║██║ █████╔╝
╚════██║ ██║ ██╔══██║██║ ██╔═██╗
███████║ ██║ ██║ ██║╚██████╗██║ ██╗
╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
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.
Use on authorized systems only.
- What it is
- Why ShadowStack
- Why shadow AI is a problem
- How it works
- Install
- Quick start
- Interactive terminal
- Example output
- Program options
- Output formats
- Exit codes
- Usage recipes
- Supported platforms & limitations
- License
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.
- 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 → criticalband, 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.
asciifor humans;json/ndjson/sarif/csvfor 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.
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.mdquietly 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
instructionsfields 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.
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
--targetspecs 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
initializehandshake, retrying2025-06-18→2024-11-05, then paginatestools/list,resources/list,resources/templates/list,prompts/list. - A
401+WWW-Authenticate: Beareris 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
/procto 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 + evidencelets 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-ongate.
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# 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.sarifBanner and progress go to stderr; the report goes to stdout (or
--output).
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 sessionColor 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.
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).
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
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.
| 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. |
| 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).
| 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. |
| Flag | Default | Description |
|---|---|---|
--walk-depth <n> |
4 |
Maximum directory recursion depth for the skills walker. |
| 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| Flag | Default | Description |
|---|---|---|
--config <file.toml> |
— | TOML scan profile pinning flags into a version-controllable file. Keys mirror long flags (--connect-timeout → connect-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.
| 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.
| 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.). |
# 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| 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. |
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.