0007. Agent → core trust model: HMAC over TLS
- Status: accepted
- Date: 2026-04-26
Context
The inventory probe introduces wanderer agent, a host-side mode
that runs on each observed host and ships Finding records back to
a central wanderer serve instance. That introduces a new trust
question: how does the core know which agent is allowed to write
which findings?
Three options were on the table:
- Bearer tokens. A token per agent, sent in the
Authorizationheader. Easy to deploy; trivially leaked or replayed by anyone who captures one. - Mutual TLS. The agent presents a client certificate from a private PKI; the core verifies it against the CA. Strong, but carries certificate rotation, CRL, and OCSP machinery as ongoing operational cost.
- HMAC-over-TLS. Each agent has a per-host shared secret; every
POST is signed with HMAC-SHA256 over
<timestamp>\n<body>and includes a timestamp header. The core verifies the signature and rejects requests with timestamps outside ±5 minutes.
Decision
The MVP ships option 3: HMAC-SHA256 over HTTPS, with a ±5-minute timestamp window for replay protection.
The wire format:
POST /scans/{id}/findings
X-Wanderer-Agent: <hostname>
X-Wanderer-Timestamp: <RFC 3339>
X-Wanderer-Signature: base64(HMAC-SHA256(secret, timestamp + "\n" + body))
A constant-time HMAC compare (crypto/hmac.Equal) is used. Failures
return a single 401 status without leaking which check failed; that
prevents an attacker from mapping out registered hostnames.
Per-host secrets live behind an AgentSecrets interface; the MVP
ships a StaticAgentSecrets map suitable for ≤10 hosts. A
file-watching or vault-backed implementation can be added later
without touching the verifier.
Consequences
- Operational simplicity for small fleets. A new host needs one
secret in two places (
/etc/wanderer/agent.hmacon the host,AgentSecretson the core). No PKI, no rotation tooling, no CRL. - Replay protection is timestamp-based. A request outside the ±5-minute window is rejected. We do not maintain a per-nonce cache; the timestamp window is the dedup mechanism.
- TLS is still mandatory. HMAC authenticates the request; it does not protect confidentiality. Operators MUST terminate TLS on the core (or at a reverse proxy in front of it). Unencrypted HTTP transport is explicitly out of scope.
- Secret rotation requires a coordinated change. Rotate by generating a new secret, updating the agent, then updating the core within the timestamp window. Bearer-token-style overlap windows are not supported in the MVP — when a fleet outgrows that ergonomic, revisit.
- Information-leak discipline. The 401 response is identical for unknown hostname, bad signature, and timestamp skew. The underlying error is logged on the core but not surfaced.
When to revisit: when the fleet exceeds ~10 hosts, when secret rotation friction becomes noticeable, or when a regulator demands mTLS specifically. At that point a follow-up change introduces either workload identity (SPIFFE-style) or a full PKI.