Scout Authentication & Key Rotation
Scouts authenticate with a per-relationship Ed25519 keypair, not a shared secret: the Scout holds the private key, Snitcher only ever stores the public key. Nothing that could be captured and replayed travels on the wire on an ongoing basis. This page covers why that design was chosen, how the enrolment handshake and steady-state signing work, and how keys rotate over time without ever dropping a connection.
For the day-to-day task of enrolling a Scout, see Scouts & Provisioning.
Why per-relationship keys instead of a shared secret
A shared secret (API key, static token) has to travel on the wire on every connect, which means every connection is a chance for it to be captured and replayed — and a single leaked secret compromises every Scout using it. An asymmetric keypair generated locally by the Scout means the private key never leaves the machine it was generated on, and compromising the transport (or even the server's database, which only ever holds public keys) doesn't hand an attacker anything they can use to impersonate a Scout.
Enrolment handshake
Snitcher mints a one-time (or, for config-managed Scouts, reusable) registration key with an admin-set expiry (max 1 hour), consumed on first use regardless of success or failure. The Scout uses it exactly once to authenticate the handshake that hands its freshly generated public key to Snitcher, then never needs it again:
- The Scout generates an Ed25519 keypair locally.
- It presents the registration key alongside its new public key over the initial connection.
- Snitcher validates the registration key, stores the public key against the Scout's row, and the registration key is consumed — a UI-minted key additionally binds the Scout to this single Snitcher instance from this point on.
Steady-state connect
Every connect and reconnect after enrolment, the Scout signs {scoutId}|{timestampMs}|{nonce} with
its private key and presents {scoutId}.{timestampMs}.{nonce}.{signature} as the SignalR
credential. Snitcher verifies the signature against the Scout's trusted public key, rejects
timestamps more than 30s old, and rejects a nonce it has already seen in the last 60s — so a
captured credential cannot be replayed even within its own validity window.
The Scout's private key itself is encrypted at rest (AES-GCM-256, SCOUT__KeyEncryptionKey or the
Docker-secret form SCOUT__KeyEncryptionKey_FILE) in its own local database (SQLite by default,
PostgreSQL opt-in).
Key rotation
A trusted Scout's Ed25519 key is rotated automatically, without ever dropping its connection:
- Once the Scout's current key is older than the configured rotation interval (Settings → Scout Key Rotation, default 7 days), Snitcher pushes a rotate request over the Scout's existing, already-authenticated connection.
- The Scout generates a new keypair locally, persists it, and hands the new public key back over the same connection. Snitcher stores it immediately — the old key is untrusted from that instant for any new connection attempt, but the live connection keeps running under its original identity. The new key only takes effect the next time the Scout (re)connects.
- If the Scout doesn't acknowledge (it's mid-check, briefly unreachable, etc.), Snitcher retries after the configured retry delay (default 1 hour) as long as the Scout is still online — and, independent of that timer, always re-attempts the moment the Scout reconnects. Rotation is never retried while a Scout is offline; there's nothing to push to.
You can also trigger a rotation on demand from a Scout's detail page (Regenerate key) — it uses the exact same push, just on request instead of on a schedule.
If rotation keeps failing (the Scout stays reachable but never completes it) a KeyRotationFailed alert fires once the retry window elapses, so you notice a stuck Scout instead of it silently drifting toward Untrusted.
If a Scout's key ages past a configurable maximum without a successful rotation, it flips to Untrusted and its connections are refused until it's re-enrolled — see Untrusted Scouts for the recovery steps.