Rotating Secrets Engine
Service-to-service authentication does not rely on a static shared secret.
The BE runs a RotatingSecretEngine (SDK secrets/rotating-secret.ts)
that owns every s2s secret and rotates it on a timer.
The slot model
Each secret lives in Redis as a slot { current, previous, rotated_at }:
- Rotation: every
rotationMs(default 60 min) a fresh secret replacescurrent; the old one becomesprevious. The Redis key expires afterrotationMs + graceMs. - Grace window: for
graceMs(default 5 min) after rotation, verifiers accept bothcurrentandprevious(validClientShieldSecrets) — so an in-flight request signed just before rotation never fails. - Broadcast: each rotation publishes the shielded
secret.rotatedevent; consumers refresh their cached slot immediately instead of waiting for a miss.
Serving secrets — governed, allowlisted
Consumers fetch a secret over the NATS secret.get correlation route. The
route is:
- publisher-verified — only registered services may call it
- allowlisted —
SERVED_SECRETSinmodules/secrets/secret-engine.tsdeclares exactly which(scope, name)refs exist; anything else is refused with an error log. A service can never fetch an arbitrary secret.
Currently served: service_client_shield_secret — the secret stamped on
every s2s call (x-client-shield-secret header) and verified by the
client-shield middleware on every inbound internal route.
Fallbacks and failure modes
- The
config_entriesrowservice_client_shield_secret(typesecret,__ROTATED_BY_SECRET_ENGINE__placeholder) is the static fallback only — it exists so the config loader can boot services before the engine's first rotation. It is never the production value. - If Redis is unavailable the engine logs an explicit error and rotation simply does not happen — consumers keep accepting the last slot.
- The engine also keeps the BE's own outbound slot warm, so BE→US calls
(proxy, NATS auth headers) always stamp
current.
Why rotation matters
A leaked s2s secret expires within the hour by design — no deploy, no config change, no coordination. Combined with the allowlist and the publisher verifier, the blast radius of a compromised service identity is bounded to one rotation window.