AgentHound — security model and threat-model commitments¶
What AgentHound is¶
AgentHound is a transparent, authorized-assessment tool for AI agent infrastructure. It enumerates configuration, queries documented endpoints, and builds a graph of trust relationships. Operators run it against systems they have been authorized to assess.
What AgentHound is not¶
- Not an evasion implant. The collector is a 9 MiB Go binary
literally named
agenthound. EDR products will detect it on sight. We do not ship binary renaming, packing, native compiler chains, or syscall-level evasion. If an engagement requires evasion, the right tools are Sliver, Mythic, or a custom implant — and you can shuttle AgentHound's JSON output through that channel. - Not a C2 framework. There is no server-to-collector control channel. The collector is a one-shot CLI: it runs, emits JSON, exits.
- Not a multi-user team server. The server is single-user and intentionally has no authentication at the application layer.
Single-user server posture¶
agenthound-server binds to 127.0.0.1:8080 by default. This is the
primary security control:
- Anyone with network access to the bound interface can read the graph: there is no login, no RBAC, no per-user data scoping.
- For remote access, use a control mechanism the operator already
trusts: WireGuard / Tailscale / OpenVPN, an SSH tunnel
(
ssh -L 8080:localhost:8080 host), or a reverse proxy with mTLS in front of the application. - Do not expose the server on
0.0.0.0or behind plain HTTP on the public internet. The application is not designed for that threat model and never will be.
If a multi-tenant team server is what you need, fork before this
commit (auth lived in internal/auth/ until then) — but expect to
maintain it yourself. The project direction is single-user-first.
Localhost token on mutating endpoints¶
Although the server is single-user, the operator's browser is not.
A malicious tab open in the same browser session can issue same-origin
POSTs to 127.0.0.1:8080 and run arbitrary Cypher or upload
attacker-chosen ingest data. To shut that drive-by path:
- The server generates a random 32-byte token at first startup and
persists it to
~/.agenthound/server.tokenwith0o600perms. Override the path viaAGENTHOUND_TOKEN_PATH. IfXDG_CONFIG_HOMEis set, the default becomes$XDG_CONFIG_HOME/agenthound/server.token. The token is reused on subsequent restarts (idempotent). - All mutating HTTP routes require
Authorization: Bearer <token>. Specifically:POST /ingest,POST /query,POST /scans,DELETE /scans/{id}, and the threePOST /analysis/*-pathendpoints. - Read endpoints (graph reads, findings, prebuilt queries, rules, health, docs) stay open. Localhost-only reads on a single-user box are fine; gating them would force the UI to plumb auth through every TanStack Query call for no security gain.
GET /api/v1/auth/local-tokenreturns the token to same-origin callers. The embedded UI fetches it once on first load and caches it in memory. Same-origin enforcement is provided by CORS:AllowCredentials: falseandAllowedOriginsis the operator-set allowlist (defaulthttp://localhost:8080), so a third-party tab cannot use a credentialed fetch to read the response.agenthound-serverCLI subcommands (ingest,query) call the pipeline / reader directly and do not speak HTTP, so they bypass the token entirely. No CLI plumbing changes are required.
To revoke the token (e.g. you suspect another user on the box read
the file), delete ~/.agenthound/server.token and restart the
server. The next startup generates a fresh token; any open UI tab
will need a refresh to re-fetch.
Collector network behaviour¶
The collector makes outbound network calls to:
- Targets specified by the operator —
--target,--targets,--config,--url, or paths discovered by--discover. - No one else. No telemetry, no phone-home, no version-check pings, no crash reporting, and no upload to a central server.
Scan output is written to a local file (or to stdout via --output -).
Transport to the operator's analysis box is the operator's
responsibility — typically a file copy, an SSH pipe
(agenthound scan --output - | ssh op-box 'agenthound-server ingest -'),
or a drag-drop into the UI's Scan Manager → Import scan dialog. The
collector does not initiate any connection back to a server.
scripts/deps-check.sh enforces the dependency boundary: the
collector binary cannot link chi, pgx, neo4j-go-driver, or any
server-only code. Reviewers can verify with go list -deps that no
hidden network code crept in via a transitive dep.
Credential handling¶
The Config Collector parses MCP client config files which often contain API keys, OAuth tokens, and database passwords. Default behaviour:
- Credential values are SHA-256 hashed at parse time. The hash is
stored on the
Credentialnode; the raw value never lands in the scan JSON. --include-credential-valuesopts into raw values. Use this only for offline audit work. The output file (containing raw secrets) has no transport-layer protection — protect the file at rest.
Output files are written via atomic temp+rename and chmod'd to
0o600 on POSIX. NTFS does not honor POSIX permission bits. On
Windows, the output file inherits the directory's NTFS ACL, which
typically allows any local user to read it. Treat any AgentHound
output stored on Windows as readable by every local user account.
TLS¶
- All HTTP transports verify certificates by default.
--insecuredisables certificate verification. Use only against self-signed targets in an authorized assessment, never as a default.- A regression test in
modules/{mcp,a2a}/*_test.goasserts strict default verification — a code change that silently weakens this fails CI.
Supply chain¶
- GitHub Actions are SHA-pinned. Major-tag references are not
used. Updates flow through Dependabot in
.github/dependabot.yml. - govulncheck runs on every PR (blocking). Stdlib vulns are
patched by the Go toolchain version pinned in
go.mod. - Go module licenses are checked against an allow-list
(
Apache-2.0, MIT, BSD-2-Clause, BSD-3-Clause, ISC, MPL-2.0, Unlicense, Zlib). Adding a copyleft dep fails CI. - Releases are cosign-signed.
checksums.txtis signed via keyless OIDC; the signature gates every artifact in the release via the checksum chain. - SBOMs are published per archive. Syft generates SPDX-JSON attached to each release.
- Verify install with the cosign one-liner shown in
install.sh's output when cosign is not on the operator's PATH.
OPSEC reminders for operators¶
- The binary's name and contents are a known fingerprint. Renaming
the binary removes one signal; the import table and the
agenthound-ingestJSON it emits are not stealth. - Atomic writes mean a SIGINT mid-scan does not leave a half-written
scan file at the destination — but a temp file in the destination
directory may briefly exist. The scan filename pattern is
.agenthound-*.jsonduring the write window. - The collector logs to stderr. Use
--quietto suppress everything except errors, or--log-jsonto capture structured logs to a file for later review. - Default install path is
$HOME/.local/bin. Override withAGENTHOUND_INSTALL_DIR=/pathif you need a different location. The installer never usessudoand never writes outside of$AGENTHOUND_INSTALL_DIR.
Reporting vulnerabilities¶
See SECURITY.md for the disclosure process.