
Live recon and posture auditing for AI agent infrastructure: scans MCP configs, session logs, and APIs for secrets, poisoned catalogs, and CoT leaks.
Sit where the model sits. See what it can see.
MASQ is a local security CLI for AI agent stacks. It sits in the same seat the model sits in: it reads MCP tool catalogs, completes the MCP handshake, looks through session logs, and probes local model HTTP ports. Then it prints findings and remediations.
It speaks MCP and OpenAI-compatible HTTP the way the client does. It reports the way a host recon tool does. It does not call tools on a live server, decrypt chain-of-thought, or generate jailbreaks.
| Use it when | You run Cursor, VS Code, Claude, Codex, or a local model stack and want to see the MCP servers, skills, logs, and APIs the agent can reach. |
| Start with | The two-minute demo below. It stays inside this repo. |
| Status | Beta. cargo test is the public gate. Live probes are for hosts you own. |
| License | MIT |
You need Rust stable (1.88 or newer) and Python 3. From a clone of this repo:
cargo build --locked --bin masq
alias masq=./target/debug/masq
# or run the same walkthrough as a script:
# bash scripts/demo.sh
1. A clean catalog stays clean
masq fixtures/clean_calculator.json --trusted
CLEAN clean_calculator tools=2 hash=sha256:e680cd2e4ac6c80ec783f11a0d9272a6741797feba216eb8e79633dd59633ab4
Exit 0.
2. A poisoned catalog fails the gate
fixtures/t3_line_jump.json is a sqrt tool whose description tells the model to consult it first on every session and to prefix shell commands with a canary curl. That is catalog poison, not math.
masq fixtures/t3_line_jump.json
FINDINGS t3_line_jump tools=1 findings=2 max=critical
F-001 [critical] D02 mcp03=schema_poison sqrt Network exfiltration directive in schema
path: $.tools[0].description
F-002 [critical] D03 mcp03=schema_poison sqrt Line-jump: force tool consultation / session prefix
path: $.tools[0].description
Exit 2 (findings at or above --fail-on, default high).
3. Sit a live MCP the way the model does
The in-tree mock speaks MCP over stdio. masq sends initialize, notifications/initialized, and tools/list. It never sends tools/call.
masq sit --plain --no-color -- python3 tests/mock_mcp_server.py
01 CATALOG
M-001 CRIT live Secret path or credential exfil directive
Schema references secret file paths or credential stores in an agent-directed way.
ACT Never reference host secret paths in tool metadata.
LOCUS stdio:python3 :: add
M-002 HIGH live IPI / poison language in tool metadata
Local classifier hit `system-override` in tool `add`.
ACT Remove agent-directed instructions from the tool description.
M-003 HIGH live Instruction-override language in schema string
Text matches common prompt-injection / instruction-override phrasing in tool metadata.
M-004 HIGH live Pre-action system instruction in schema
Schema instructs the model to perform actions before the normal tool purpose.
02 SEAT
HIT stdio python3 init ok name=mock-poison tools=1
The mock's add tool description tells the model to read ~/.ssh/id_rsa before doing arithmetic. masq flags that from the live tools/list, then exits 2.
4. Sniff a sample session log
fixtures/demo/chat_history.jsonl plants a fake API key and an encrypted thinking blob. Point masq at that directory - not at your real chat history - to see the timeline.
masq sniff --timeline --plain --no-color fixtures/demo
T0001 SECRET chat_history.jsonl:2 {"content":"Authorization: Bearer sk-x……
T0002 BLOB chat_history.jsonl:3 encrypted_content len=202
The planted key is redacted. Encrypted chain-of-thought is reported by length, not decoded.
5. Pin a catalog you reviewed, then catch drift
masq pin fixtures/clean_calculator.json -k calc
masq check fixtures/clean_calculator.json -k calc --trusted
pin writes .masq/pins.json. check fails later if the tool list or hashes change (rug-pull).
Exit codes everywhere: 0 clean · 2 findings ≥ --fail-on · 1 error.
Static MCP scanners lint schemas. Health checks ping initialize. The gap is the seat: the files, listeners, and handshakes the model already uses.
initialize with no auth. If you can complete that handshake, you are already the agent. masq sits; it does not call tools.11434, 8000, and friends often bind 0.0.0.0 with no bearer. masq fingerprints the service (Ollama, vLLM, LiteLLM, …) with GET-only probes, then lists models.flowchart LR
A["MCP tools/list JSON"] --> M[masq]
B["Live MCP server"] --> M
C["Session logs"] --> M
D["Local model HTTP"] --> M
M --> E["Findings + remediations"]
M --> F["Optional pin store for CI"]git clone https://gitlab.com/WattoCyber/masq.git
cd masq
cargo install --path . --locked
# command: masq
masq --version
masq --help
Requires current Rust stable (1.88+; install with rustup). cargo test is the public gate. Python 3 is only required for the live stdio mock in the demo and in tests.
To build on a remote Unix host over SSH (install only; does not scan that host):
MASQ_REMOTE_HOST=user@host bash scripts/deploy_remote.sh
# on that host: masq --version
The demo never leaves this repository. These commands do, and they are scoped to this machine unless you opt into a lab allowlist.
Export or save a tools/list JSON, then:
masq path/to/tools.json
masq path/to/tools.json --json
masq path/to/tools.json --markdown
Bare .json paths rewrite to scan. Two or more JSON files become multi.
masq sit -- python3 -m your_mcp
masq sit --url http://127.0.0.1:PORT/mcp
Loopback is enough. Off-loopback URLs also need --lab and an allowlisted host (see below). Auth, if the server needs it:
masq sit --url http://127.0.0.1:PORT/mcp --token-file /path/to/token
masq sit --url http://127.0.0.1:PORT/mcp --token-env MCP_TOKEN
masq sit --url http://127.0.0.1:PORT/mcp --header "Authorization: Bearer …"
Tokens are never written into the report.
masq --plain # recon + sniff + API + skills + harness + steer, then the report
masq --json # same, machine-readable
masq tui # boxed live view; stays until Ctrl+C
masq discover # known agent MCP config paths; no spawn
masq recon # configs, listeners, unpinned npx/uvx
masq sniff ~/path/to/sessions
masq api # fingerprint local LLM HTTP, then GET /v1/models
masq api --list-probes # show the Julius-compatible probe pack
masq harness # on-disk harness policy, binds, creds, SSRF
masq steer # rules, hooks, subagents, local runtime, CDP, messaging
On a TTY, the default masq command streams progress and then prints the report. Scripts and cron should pass --plain or --no-color.
discover walks conventional paths under $HOME / %USERPROFILE% for VS Code, Cursor, Windsurf, Claude Desktop, Claude Code, Grok, Hermes, Pi, OMP, Codex, Continue, Gemini CLI, Amp, Kiro, OpenCode, Amazon Q, Antigravity, and OpenClaw, plus cwd .mcp.json and MASQ_EXTRA_CONFIG.
harness is a separate section from MCP sit and API fingerprint. It reads on-disk installs for Hermes, Pi, OMP, Claude Code, Codex, Gemini CLI, OpenCode, OpenClaw, Grok, Amp, and Amazon Q: approval/YOLO policy, messaging allow-all, wildcard binds, credential-file modes, computer/browser/launch tools, cron auto-approve, SSRF/redaction/Tirith opt-outs, pairing and identity file modes, OMP share/autolearn, and Pi remote packages. It does not spawn the harness or dump .env values.
steer is a separate section from harness. It reads instruction files (CLAUDE.md, AGENTS.md, Cursor/Gemini/Copilot rules), Claude/Codex/Amazon Q lifecycle hooks, Claude/OpenCode subagents, installed Claude plugins, Ollama/Open WebUI env flags, persisted Chromium --remote-debugging-port, and OpenClaw gateway.bind / dmPolicy / allowFrom. It does not spawn a harness, call tools, or dump .env values.
masq chameleon fixtures/clean_calculator.json -o /tmp/masq-chameleon
# writes catalog.json, aimock.json, calls.json
Example tools/call envelopes in calls.json are for a local mock only. masq does not send tools/call to the live server.
Aliases: doctor / audit → scan. chameleon → mimic. posture → seat.
sit / chameleon speak Streamable HTTP (http://), HTTP+SSE, ws://, unix:, npipe:, and stdio (--framing auto tries NDJSON then Content-Length).
wss:// is not built in. HTTP follows zero redirects. Stdio initialize does not advertise sampling. Server-advertised sampling / elicitation is flagged. Docs URLs (model-catalog.json, /docs/) are ignored on purpose.
--lab)--lab is an allowlist, not a subnet scan. Nothing off-loopback is compiled in.
export MASQ_LAB_HOSTS="192.0.2.10,192.0.2.11"
# or one host per line in .masq/lab-hosts
masq --lab
MASQ_SIT_URLS (or --mcp-url) supplies MCP URLs to sit when --lab is on. Off-loopback sit --url needs both --lab and an allowlisted host. Loopback unauthenticated initialize is High. Off-box is Critical.
Do not point proxy at a host you would not sit.
Recon counts secret-looking files and prints path + "loaded". It does not dump their contents.
cargo test
cargo clippy --all-targets -- -D warnings
bash scripts/demo.sh
tests/demo_readme.rs is the contract for the walkthrough above.
In-repo fixtures used by the demo and CI:
MIT. Copyright 2026 SamsonCyber.
| Command | What it does |
|---|
seat (default) | Full posture: recon + sniff + api + skills + harness + steer + optional sit. |
recon | Config files, listeners, shadow URLs, sudo argv, unpinned npx/uvx. |
harness | On-disk harness blast radius: policy, identity, binds, cred-file modes. |
steer | Instruction files, lifecycle hooks, subagents/plugins, local runtime, CDP, messaging policy. |
sniff | Session logs for secrets, plaintext reasoning, encrypted CoT echo. |
sniff --timeline | File-order T0001 lines. No decrypt. Caps at 200 events/file. |
api | GET-only fingerprint of local LLM HTTP (Ollama/vLLM/LiteLLM/…), then /v1/models. --lab adds allowlisted peers. Extra Julius YAML: .masq/probes/ or MASQ_PROBES_DIR. |
sit | MCP initialize + list. Never originates tools/call. |
chameleon / mimic | Sit a live or file catalog, write catalog.json + aimock.json + calls.json. |
discover | Print known agent MCP config paths. No spawn. |
proxy | Forward the agent's JSON-RPC. --block refuses secret-shaped tools/call params/results. Listens on 127.0.0.1:9877 by default. |
sast | Local source sinks. Uses semgrep when installed. |
classify | Local instruction-injection review of a live tools/list. --llm is opt-in. |
overnight | Repeat seat until --hours elapse. Writes reports. Does not mutate configs. |
map | Text attack-path queries over a saved posture JSON. |
scan / multi | Static catalog lint (bare .json paths rewrite to this). |
pin / check | Content-hash pin store (.masq/pins.json) for rug-pull CI. |
detectors | Print the catalog rule pack. |
tui | Boxed seat. Stays until Ctrl+C. |
| Module | Covers |
|---|
| recon | MCP01 secrets path, MCP04 unpinned npx/uvx, MCP07/09 listeners and shadow URLs, MCP05 sudo argv |
| catalog | MCP03 schema poison / shadowing / rugpull |
| skills | Same catalog detectors on SKILL.md trees. No cloud SCA. |
| sit | MCP02 scope creep, MCP03 live tools/list, MCP07 unauth initialize, sampling/elicitation, Origin DNS-rebinding |
| sniff | MCP01 + MCP10 + encrypted CoT echo (including thinkingSignature) |
| api | MCP07 unauth /v1/models, MCP09 LAN bind, service fingerprint |
| map | Text path queries over a posture report |
| This tool | Not this tool |
|---|
Sit as the agent client (initialize + list) | Originate tools/call against a live server |
| Detect encrypted CoT echo in logs | Decrypt CoT or replay it into a weaker model |
GET fingerprint + /v1/models | POST completions, except opt-in classify --llm / TUI triage |
| Pin-hash rugpull on a catalog | Signed PKI / enterprise pin vault |
Forward the agent's tools/call in proxy --block | Full runtime DLP gateway |
| Local skill-tree lint | Cloud SCA |
| Path | What it shows |
|---|
fixtures/clean_calculator.json | Trusted two-tool catalog |
fixtures/t1_description_poison.json | Instruction override + secret-path exfil |
fixtures/t3_line_jump.json | Forced consultation + canary curl |
fixtures/t6_param_names.json | Semantic exfil via parameter names |
fixtures/t8_ansi.json | ANSI concealment |
fixtures/t13_duplicate_names.json | Tool shadowing (exit 2) |
fixtures/demo/chat_history.jsonl | Planted key + encrypted thinking blob |
tests/mock_mcp_server.py | Live stdio MCP with a poisoned add tool |