Skip to content

AgentHound — security model and threat-model commitments

What AgentHound is

AgentHound is a transparent, authorized-assessment offensive security framework for AI agent infrastructure. The collector runs recon, enumeration, credential looting, and reversible exploitation; the server builds the graph and computes attack paths. 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.0 or 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, this release is not that product. The removed pre-launch auth/RBAC implementation is not a supported branch or migration target; introducing multi-tenancy requires a separately maintained security architecture. The project direction is single-user-first.

Origin guard 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 auto-submit a cross-origin POST to 127.0.0.1:8080 and try to run arbitrary Cypher or ingest attacker-chosen data. To shut that drive-by path, agenthound-server runs an Origin allowlist on every mutating endpoint (OriginGuard):

  • Browsers attach Origin: <scheme>://<host>:<port> to every cross-origin POST (and to every same-origin non-GET) per the Fetch spec. The middleware compares the header to the allowlist (default http://localhost:8080 and http://127.0.0.1:8080, configurable via AGENTHOUND_CORS_ORIGINS). A foreign Origin (e.g. a tab on evil.com) → 403 Forbidden. The string Origin: null (sandboxed iframes, data: / file: URLs) is also rejected.
  • Requests with no Origin header pass through. This is the non-browser caller — curl, the agenthound CLI, a cron pipeline. Same-host processes are inside the trust boundary by design: AgentHound is single-user; if an attacker has shell access on the box they can do worse than POST to /ingest.
  • 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.
  • agenthound-server CLI subcommands (ingest, query) call the pipeline / reader directly and do not speak HTTP, so they bypass OriginGuard entirely.

The allowlist is shared with CORS — one env var, one source of truth. CORS still enforces AllowCredentials: false so a hostile origin cannot ride ambient cookies even if one were ever introduced.

Non-loopback binds

Binding agenthound-server to anything other than loopback (0.0.0.0 or a LAN address) puts mutating endpoints in front of anyone who can spoof an Origin header — trivial for a LAN attacker with curl. The server logs a WARN on startup in this case. Don't do it. For remote access use a VPN, an SSH tunnel (ssh -L 8080:localhost:8080 host), or a reverse proxy with mTLS.

Triage state retention

Triage decisions written via PATCH /findings/triage/{fingerprint} live in the Postgres finding_triage table, keyed by the finding fingerprint. This table has no foreign key to the per-scan findings snapshot: an analyst's accepted-risk / false-positive decisions deliberately survive scan deletion and re-detection, so deleting a scan (or re-running a collector) does not silently re-surface a finding that was already adjudicated. Operators who want a clean slate must clear finding_triage explicitly.

Scan deletion is deliberately history-only. It never issues a Neo4j delete or interprets last-writer scan_id as ownership. The API rejects pending/running scans, scans referenced by active coverage heads or coverage limitations, and the currently published posture revision with 409.

Collection provenance and storage pairing

Every ingest-v1 artifact carries an automatically derived collection point and network context. Private-route evidence binds the destination prefix to its observable next hop and stable native profile/link discriminator; if neither path signal is available, only network quality becomes unknown. Raw machine/account/platform/container, adapter, route, and DNS identity evidence is transformed with AgentHound-specific HMACs before it enters the artifact. Bounded hostname, OS, and architecture labels are the deliberate display-only exception: they are emitted in clear, treated as untrusted text, and never affect identity or graph behavior. The server validates the scheme, algorithm version, digest consistency, and evidence classification rules. It cannot prove that a collector actually ran on the claimed machine: provenance is not authentication or attestation.

The server accepts every valid artifact and scopes ambiguous evidence rather than admitting one configured realm. Local files, locally observed config identities and credentials, stdio services, and loopback observations use collection-point scope. Remote endpoint observations and their derived identity or credential children use network-context scope. Weak collection-point artifacts are still analyzed, but their authoritative evidence is artifact-local and additive-only. If only network visibility is incomplete, the collection point remains strong and only its remote/network-scoped evidence becomes artifact-local. Approved value_hash correlation may cross contexts; other processor joins require exact compatible scope. Config-file coverage follows the same boundary: files, agents, configured identities, and credentials reconcile at collection-point scope, while remote services and edges involving them reconcile under an independent network (or artifact-local) variant. Moving between VPNs cannot retire the previous VPN's configured-service observations.

Limited-evidence publication

AgentHound separates collected facts from coverage. A safe incomplete scan can publish confirmed positive facts and findings. Its incomplete scopes have no absence authority: they cannot delete unseen prior evidence, create a comparison key, or justify an empty-findings all-clear. CLI, API, and UI surfaces therefore keep graph/findings access available while warning that missing evidence is unknown, not clean. CI --fail-on gates fail closed when the published scope has an active coverage limitation.

Dirty-state safety remains separate and fails closed. Every domain that mutates Neo4j is marked dirty before the mutation and is cleared only after graph, analysis, snapshot, and PostgreSQL publication all succeed. A PostgreSQL failure cannot leave Neo4j changes available for an unrelated scan to publish. A later complete scan clears the corresponding limitation, regains absence authority, and restores comparison behavior.

PostgreSQL and Neo4j carry the same server-generated internal storage-pair UUID, so crossed volumes fail closed. Verification remains the first ingest operation. Unverifiable storage receives sanitized 503 STORAGE_BINDING_UNAVAILABLE before lifecycle or graph mutation. Continue to protect the local server boundary and artifact transport with trusted custody, checksums, VPN/SSH isolation, or mTLS as appropriate.

Collector network behaviour

The collector makes outbound network calls to:

  1. Targets specified by the operator--target, --targets, --config, --url, or paths discovered by --discover.
  2. A2A v1 signature key URLs declared by those targets — protected-header jku retrieval is enabled unless --no-verify-jwks is set. It is HTTPS-only across redirects, validates TLS identity even when target collection uses --insecure, refuses link-local/cloud-metadata addresses, rejects fragments, is response- and per-card-work-bounded, and receives no target authorization header.
  3. An analysis server explicitly selected by the operatoragenthound scan --ingest <server-url> uploads the exact scan artifact already saved on disk to that server's /api/v1/ingest endpoint. Redirects are rejected.

There is no telemetry, phone-home, version-check ping, crash reporting, or automatic upload to a central service. Direct ingest is an explicit one-shot operator action, not a server-to-collector control channel.

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 -'), direct ingest, or a drag-drop into the UI's Scan Manager → Import scan dialog. Without --ingest, the collector does not connect to an analysis server.

Direct ingest accepts http:// and https://. Plain HTTP is supported only for the default loopback server or an endpoint already protected by an operator-controlled VPN/SSH tunnel; it adds no application-layer confidentiality and must not cross an untrusted network. Use HTTPS or map the remote server to loopback through a trusted tunnel. HTTPS certificate verification is strict, and the collection-target --insecure flag does not weaken ingest TLS. A scan created with --include-credential-values can contain raw secrets, so its upload requires the same custody and transport protections as the saved artifact.

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 in environment variables, headers, recognized argv flags/positions, and URL user-info/query components are SHA-256 hashed at parse time. The hash is stored on the Credential node; the raw value never lands in the scan JSON by default.
  • Raw stdio argv is never serialized as an MCPServer property. The artifact carries an ordered domain-separated digest per argument plus arg_count, so strict ingest can verify the server ID without the original argv. These deterministic hashes prevent accidental disclosure; they are not encryption and low-entropy arguments remain guessable offline.
  • Persisted HTTP endpoints and collection targets remove URL user-info, query, and fragment bytes and record boolean redaction markers. Transport and HTTP identity still use the untouched URL only in collector memory. URL paths and stdio command names remain visible, so credentials must not be placed in either field.
  • MCP connection and enumeration failures persist a bounded diagnostic class, not raw SDK error text that might repeat a configured URL or command line.
  • MCP configuration aliases are grouped by canonical server identity only when their complete execution and authentication profiles agree. HTTP header names are compared in the same case-insensitive canonical form used on the wire; duplicate canonical names with distinct values, or aliases with different argv, environment, URL, or headers, produce a fixed ambiguity outcome before any subprocess or network transport is constructed.
  • --include-credential-values opts into raw values. Use it only when an authorized workflow explicitly requires them. The output file (containing raw secrets) has no transport-layer protection — protect it at rest. If the artifact is transferred or uploaded with --ingest, use HTTPS or a loopback endpoint protected by an operator-controlled VPN/SSH tunnel as described above; never send it over plaintext on an untrusted network. This opt-in may include values extracted from argv or URL components on Config Collector Credential nodes, but it never restores a raw MCPServer args array or raw public endpoint.
  • Credential identity is not exposure. Nodes record material_status and exposure_status; masked LiteLLM provider references and returned one-way hashes are excluded from exposure counts, entropy, and rotation claims.
  • Missing auth, host scope, sensitivity, or pinning evidence remains unknown. It must not be interpreted as anonymous, public, low sensitivity, unpinned, or clean.

Campaign runner: out-of-band credential material

The agenthound campaign --scenario cred-reach runner needs an executable credential (the raw secret) to send the authed probe, but it must never persist or log it. Its handling is deliberately stricter than --include-credential-values:

  • Out of band only. The material is supplied via an environment variable (AGENTHOUND_CAMPAIGN_CREDENTIAL, the default) or stdin (--credential-stdin). It is never a CLI flag — flags leak into process listings (ps), shell history, and container inspect output. Do not use --include-credential-values for the campaign path.
  • Local hash-match, never serialized. The runner hashes the material locally with AgentHound's SHA-256 credential contract and requires an exact match to the witness value_hash. The raw value is never written to the graph, the emitted evidence, the scan output, or logs. A hash-only credential (no executable material, or a synthetic merge_key=identity reference) is a precondition failure — not runnable, distinct from an indeterminate probe outcome.
  • Endpoint binding before networking. Surrounding whitespace is trimmed once, but the untouched trimmed spelling—including any query bytes—is hashed through ResolveMCPServerIdentity("http", input) and must equal the witness server ID. Only absolute HTTP(S) URLs with valid authority/hostname and no userinfo or fragment are accepted. The clear endpoint is never stored in witness V1.
  • Query handling is defense-in-depth, not universal detection. Fixed known-sensitive decoded keys and decoded values exactly equal to the supplied campaign credential are rejected. Other arbitrary query bytes remain accepted identity input; AgentHound does not claim they are non-secret. Every accepted query is omitted from reports, witnesses, evidence, graph properties, errors, and logs.
  • Exact-origin forwarding. Credential headers are attached only when lowercased scheme + hostname + effective port matches the original endpoint. Path/query do not affect origin; scheme downgrade, host/port change, malformed authority, or missing authority fails closed on every redirect request.
  • Typed denial and bounded close. Only an actually observed typed HTTP 401/403 is a definitive auth denial; target-controlled text and JSON-RPC auth-like messages remain indeterminate. The MCP SDK's exact-endpoint close DELETE inherits the original absolute scenario deadline and is counted even when it times out. No blanket client timeout is applied to SSE.
  • Read-only. The cred-reach scenario issues resources/read for the exact predicted resource only (unauth control + authed probe). It mutates nothing, so it needs no receipts or rollback.
  • Anonymous access is a fact, not a finding. When the resource reads successfully without a credential the runner emits PUBLIC_ACCESS_OBSERVED (a raw fact). Findings derive only from composite edges, so this never auto-creates a finding; treat it as a policy concern only where authentication was expected.
  • Exact per-agent witness contract. Witness V1 names one source AgentInstance, is exported only for HTTP-backed resources, and carries the actual ordered current CAN_REACH evidence node IDs with normalized concrete kinds. Promotion recomputes the unkeyed fingerprint and validates every identity/hash/kind/stage/outcome/topology field against current graph state. The positive publication revision is provenance only; equality is not proof or a promotion gate. The digest is a consistency checksum, not authenticity.
  • Prevalidation before canonical state. Positive and negative campaign artifacts are checked immediately after generic ingest validation and before normalization, BeginScan, graph writes, or reconciliation. A rejection cannot overwrite evidence or retire coverage. Postgres retains only a random rejection ID plus bounded sanitized run/scenario/outcome/reason codes—never the artifact, witness, digest, endpoint, or secret.

Campaign runner: reversible mutation round-trip

The agenthound campaign --scenario mcp-poison-roundtrip scenario is a STANDALONE ContextForge target-mutation validation. Unlike cred-reach it mutates the target (reusing the mcp.poison module), so it inherits the destructive-primitive posture:

  • Both gates are required. --commit is off by default and a mutating run requires the campaign acknowledgement plus the distinct poison/destructive acknowledgement. The round-trip generates its own inert, run-specific marker; it accepts no operator-supplied mutation text.
  • Explicit provider boundary. MCP defines tool observation, not metadata mutation. The required --adapter contextforge selects one fixed provider contract: a server-scoped MCP URL ending in /servers/<server-uuid>/mcp, fixed ContextForge /v1 association/tool reads, and one fixed tool-description PUT. --management-url overrides only the deployment root. There is no generic endpoint, method, path, status, management-row ID, or request-body adapter.
  • Independent, least-privilege credentials. MCP observation uses AGENTHOUND_MCP_TOKEN or one unambiguous exact-positional-URL Authorization header from local MCP client configuration. AGENTHOUND_CONTEXTFORGE_TOKEN independently overrides management authentication. Without it, management reuses the resolved MCP bearer only on the same origin; cross-origin management requires the override. ContextForge v1.0.5 token permission claims are only a ceiling, so AgentHound accepts session tokens or API tokens with an empty/wildcard permission ceiling. It resolves identity through /v1/auth/email/me and proves database RBAC through team-context /v1/rbac/my/permissions?team_id=<uuid> reads. Effective servers.read, tools.read, and tools.update plus exact server/tool reads are required. A non-admin must be the direct ownerEmail of both server and tool; team membership alone does not prove ownership. Exact non-wildcard API-token ceilings fail closed because they block the provider RBAC proof. Credentials remain origin-bound and never enter logs, reports, or receipts.
  • One forward write; receipts retained. A typed ContextForge receipt is persisted before the single PUT. It records the provider contract, separate MCP/management identities and URLs, original/updated descriptions, original ContextForge version, and unique forward/restore operation User-Agents, but no credentials. Unknown or missing receipt type, version, or provider profile is rejected before networking. Bounded read-only polling verifies both the ContextForge row and MCP SDK projection; it never repeats the write. Both the forward and restoration bodies must fit the 256 KiB request limit, and the measured exact tool record plus projected update must retain safety headroom inside the separate 1 MiB response limit before mutation.
  • Fail closed after ambiguous or normalized writes. A lost write response is accepted as forward success only when management subsequently shows the exact intended text, version V+1, and pre-recorded forward operation User-Agent. Normalized landed text C or failed MCP projection makes the forward result fail, but the exact tool UUID plus V+1 and forward User-Agent permit one inline restore of C; AgentHound never repeats the forward write. Management rows whose description is null or non-string are rejected before mutation: MCP projects provider null as empty text, while ContextForge's update contract cannot restore null byte-for-byte.
  • Conflict-aware recovery. Revert may restore any landed description only when the exact receipt tool UUID remains at V+1 with the forward User-Agent; equality with the outbound text is not required. A different UUID, version, or User-Agent is a conflict, and a failed exact-row read is indeterminate. A row already at the recorded original description is a safe no-op only when it is not this receipt's V+1 forward-attributed state. An original-looking normalized forward write must complete the restore transition to V+2 and the recorded restore User-Agent before another operation is allowed. Association drift does not block restoration of the attributed row, but MCP verification is then reported unavailable. After the one restore, management must show original text, V+2, and the restore User-Agent; MCP must show the original when association remains intact.
  • No live AgentHound write stacking. A row that still carries an AgentHound forward-operation User-Agent cannot be mutated again, even under another engagement, until recovery restores it. This prevents an immutable receipt chain whose provider versions could not be unwound safely.
  • Operation User-Agent is a deployment requirement. ContextForge and every intervening proxy must preserve AgentHound's unique forward/restore User-Agents. If a proxy overwrites them, write ownership and safe automated recovery cannot be established.
  • No atomic compare-and-swap. ContextForge does not expose a conditional update primitive used by this contract. Version and User-Agent checks are post-hoc ownership evidence, not CAS: a concurrent writer can still land between the final read and unconditional PUT during either forward mutation or restoration. Run only in an exclusive operation window; strict safety requires server-side preconditions.
  • No non-mutating restorability probe. ContextForge v1.0.5 can read a description created under an older or more permissive validation policy even when the current ToolUpdate or content-security validators reject that same text. It exposes no validation-only update endpoint, and a no-op PUT would itself change version and audit attribution. AgentHound therefore cannot prove byte-exact updateability from ToolRead; pre-engagement validator-policy drift can make the restore PUT fail. Operators must verify current-policy acceptance before committing against deployments whose validation configuration changed.
  • Run-scoped cleanup. Active cleanup is globally reverse-sequenced, bounded, non-cancellable, and fail-stop; engagement recovery is the intentionally different per-file-LIFO, continue-on-error fallback. Oracle and cleanup remain independent, and unsafe or unconfirmed cleanup emits its report before a nonzero exit. Forward request/mutation/elapsed accounting is frozen before separately timed cleanup begins.
  • Bounded, secret-free reporting. Both fixed scenarios use the same versioned RunReport: fixed steps, sanitized target reference, per-step RFC3339Nano start/end timestamps, typed operation classes, applicable sanitized evidence/witness fingerprint, opaque receipt IDs, typed oracle/cleanup, and explicit actual HTTP-request/mutation/elapsed limits and usage. Redirect/retry dispatches count. Reports cannot carry receipt paths or hashes, request/response bodies, resource contents, target error text, original/injected mutation state, credentials, or auth tokens.
  • Protected immutable receipts. Receipt directories/files are enforced at exactly 0700/0600, including tightening existing permissive paths. Rollback-required original/injected mutation state is allowed only there. Receipts remain unchanged after success/failure; raw credentials/tokens are forbidden. Conflict-aware live-state checks enable partial retry, but completed stacked rollback replay is not promised universally idempotent.
  • Minimized config receipts. Every new receipt has a random opaque ID. mcp.config.implant stores the target path plus only the servers key/name, canonical named-entry hash, and original file existence/mode required for safe reversion; it stores no injected JSON plaintext or whole-file hash. Missing, malformed, plaintext, or unknown receipt fields fail closed.
  • Not an attack finding. It emits no graph edge and makes no claim about a predicted credential path — the round-trip evidence stays in the campaign transport. It validates only that the mutation/rollback machinery works.

A2A card evidence and key trust

A2A v1.0.1 signature verification separates cryptographic validity from identity trust. Operator-pinned JWKS keys produce valid_trusted. A key retrieved from a protected-header jku can produce only valid_untrusted: HTTPS authenticates the key host, not the claimed agent/provider. Inline jwks and top-level jwks_uri are recorded as nonstandard inactive evidence.

Remote jku requires HTTPS on the initial request and every redirect, validates the certificate identity even when --insecure is used for card retrieval, blocks link-local/metadata destinations, rejects fragments, and bounds redirects, bytes, key count, signatures, unique remote sources, and aggregate verification time. Invalid, expired, or explicitly revoked key evidence is rejected. Operators needing private/self-signed key infrastructure must pin the key locally with --a2a-trusted-keys; --insecure is not a bypass.

Bearer-authenticated Agent Card retrieval permits redirects only within the original HTTP origin: scheme, hostname, and effective port must all match. HTTPS-to-HTTP redirects are rejected even without a bearer credential. Redirect failures never include the credential in errors or logs.

Card conformance and signature state qualify functional A2A edges. Invalid cards are retained for visibility, but declarations alone do not create active authentication identities or edges. A v1.0.1 empty security requirement is a valid anonymous alternative under the protocol/OpenAPI model, so it remains conformant and does not suppress a valid skill. Its auth_method=none evidence is a declaration, not proof that a credential-free runtime request succeeded. ProtoJSON null scope lists are empty scopes; optional skill repeated fields are absent/null or arrays of strings. Other shapes make the skill nonconformant and prevent it from emitting a functional ADVERTISES_SKILL edge. An absent, null, or empty signatures field is unsigned and remains eligible for the unsigned-card finding. A null optional unprotected signature header is absent; non-null wrong-shaped signature fields remain malformed.

openwebui.loot authenticated mode

agenthound loot --type openwebui --api-key <key> reads an operator-supplied Open WebUI admin API key (or session JWT) and uses it to enumerate the upstream provider keys an admin has configured (GET /openai/config). Properties of this path:

  • Read-only by contract. The Looter issues GET requests only — it reads /api/config (anonymous posture) and /openai/config (authenticated). It never touches Open WebUI's POST /openai/config/update mutator; that would be a Poisoner-class operation. A get_only regression test asserts the looter has no non-GET call site.
  • value_hash redaction. Each emitted upstream Credential carries a SHA-256 value_hash (always populated, the cross-collector merge primitive). The raw upstream key is omitted by default and only stored on the node when --include-credential-values is set — same gating as the Config Collector and LiteLLM Looter.
  • Operator-key hygiene. The supplied --api-key is never written to the scan output and appears only as an 8-char prefix in slog. The anonymous posture mode (no --api-key) emits no credentials at all.
  • Ollama URLs read from Open WebUI admin configuration are marked configured references. They do not assert backend availability or anonymous auth until a direct Ollama probe verifies the same endpoint ID.

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.
  • --insecure disables certificate verification. Use only against self-signed targets in an authorized assessment, never as a default.
  • For ContextForge poisoning, round-trip, and receipt recovery, --insecure applies to both the MCP and management transports. It does not relax endpoint shape, redirect, origin-binding, authorization, response, or ownership checks.
  • --insecure applies only to the selected collection target. A card-controlled A2A jku always requires HTTPS with certificate and server identity validation. Operators assessing internal/self-signed issuers must pin their keys with --a2a-trusted-keys instead.
  • --insecure does not apply to scan --ingest. Direct-ingest HTTPS always validates the server certificate and refuses redirects.
  • A regression test in modules/{mcp,a2a}/*_test.go asserts 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.txt is 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-ingest JSON 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-*.json during the write window.
  • The collector logs to stderr. Use --quiet to suppress everything except errors, or --log-json to capture structured logs to a file for later review.
  • Default install path is $HOME/.local/bin. Override with AGENTHOUND_INSTALL_DIR=/path if you need a different location. The installer never uses sudo and never writes outside of $AGENTHOUND_INSTALL_DIR.

Reporting vulnerabilities

See SECURITY.md for the disclosure process.