
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.