This page describes the current trust boundaries and security assumptions in the project as it exists today.
Security Goals
Spooky is designed to:
- terminate downstream TLS for HTTP/3 and bootstrap TLS traffic
- validate and forward requests to configured upstreams with explicit trust settings
- bound resource consumption under malformed, slow, or overloaded traffic
- expose a small operator control surface with authentication
Spooky is not yet designed to be:
- a full web application firewall
- a complete authentication gateway
- a general-purpose policy engine
Trust Boundaries
Downstream Client To Spooky
Clients are untrusted. Spooky must:
- parse QUIC and HTTP/3 safely
- validate headers and pseudo-headers strictly
- bound header count and total header bytes
- enforce request-body limits and timeouts
- reject unsupported upgrade-style semantics
- avoid unbounded state growth from malformed packets or connection churn
Spooky To Upstream Backends
Upstreams are trusted only according to explicit configuration.
- HTTPS upstreams are verified by default.
- SNI is sent by default in strict mode.
- Private trust roots can be configured with
ca_fileandca_dir. - Disabling upstream certificate verification is allowed, but should be treated as a break-glass mode rather than a normal production stance.
Operator To Control Plane
The control API is privileged.
- It can expose runtime state.
- It can trigger restart behavior.
- It can trigger certificate reload.
- It must be treated as an admin surface, not a public endpoint.
Admin-plane authentication factors supported today:
- bearer token only
- mTLS only
- mTLS + bearer token
Compatibility mode:
- the legacy
observability.control_api.auth_tokenis still accepted - it is mapped to an admin-scoped static identity to preserve existing behavior during migration
- this is deliberate backward compatibility, not the recommended steady-state production posture
- because the config schema uses
deny_unknown_fields, compatibility is one-way: newer binaries accept legacy configs, but older binaries reject configs that use the newer nested admin-plane fields
Recommended production posture:
observability.control_api.tls.client_auth.mode: required- bearer token or role-bearing mTLS identity
- admin-network IP allowlisting
- dedicated audit stream enabled
Downstream TLS Model
Spooky supports:
- default/fallback certificate identity
- SNI-specific certificates
- bootstrap listener client-auth with optional or required certificate modes
Important scope note:
- current client-auth coverage is centered on the bootstrap TLS listener path
- operators should verify whether their exact ingress shape requires stronger mTLS guarantees on every downstream path before broad rollout
Admin-Plane Authentication And Authorization Model
The control API admin plane is separate from request-path auth.
Authentication and authorization are evaluated in this order:
- source-address policy, if
observability.control_api.ip_allowlistis configured - authentication using bearer token, mTLS identity, or both
- authorization against the route-to-role contract
Role model:
viewer: runtime snapshot and generation history readsoperator:viewerplus validate, preview, activate, rollback, reload, and cert reloadadmin:operatorplus restart and future destructive admin actions
Response and failure contract:
401 Unauthorized: missing or invalid authentication403 Forbidden: authenticated but under-scoped, or denied by source-address policy- TLS handshake rejection: client certificate missing or invalid when control API mTLS is required
Handshake rejection is not an HTTP response. It terminates the TLS connection before routing and should be diagnosed through control-plane TLS logs and audit output.
Admin-plane route contract:
| Route family | Minimum role |
|---|---|
/health, /ready |
unauthenticated or separately configurable |
/admin/runtime |
viewer |
/admin/runtime/history |
viewer |
/admin/runtime/history/{generation} |
viewer |
/admin/runtime/validate |
operator |
/admin/runtime/preview |
operator |
/admin/runtime/activate |
operator |
/admin/runtime/rollback |
operator |
/admin/runtime/reload |
operator |
/admin/runtime/reload-certs |
operator |
/admin/runtime/restart |
admin |
Upstream TLS Model
Upstream trust behavior is controlled by configuration.
Safe posture:
verify_certificates: truestrict_sni: true- explicit custom CA material when using private PKI
Unsafe posture:
verify_certificates: false- public or shared-network upstreams with disabled verification
Resource-Exhaustion Defense Model
Spooky includes multiple defensive layers intended to limit blast radius from abusive or unhealthy traffic:
- new-connection token bucket
- maximum active connection caps
- per-connection stream caps
- global and scoped inflight limits
- route queue caps
- request and response body caps
- body idle and total timeouts
- adaptive admission and brownout controls
These features are part of the project’s security posture because they reduce denial-of-service amplification inside the process.
Request Authentication And Authorization Model
Spooky supports per-upstream request authentication, checked in this order:
- API key: a configured header is compared against a static key list. Local, synchronous, no network call.
- JWT: local signature and claim validation (issuer, audience, clock skew), plus optional scope/role checks against token claims. Supports
HS256with a shared secret, andRS256/ES256against static PEM/JWK public keys or a remote JWKS endpoint. Always local and synchronous on the request path — JWKS keys are served from an in-memory cache refreshed in the background, never fetched during request validation. - External auth: an async HTTP subrequest (generic HTTP or OIDC-shaped) sent to a configured auth endpoint, gated before upstream admission so the request never reaches the backend while auth is pending. Only one external auth provider is supported per upstream, and it cannot be combined with API key or JWT in the current version.
External auth details:
- The auth call runs on a dedicated HTTP client, isolated from upstream backend transport, inflight accounting, and health state — an auth outage cannot degrade backend routing.
- A decision maps to
Allow,Deny,Redirect, orChallenge; only headers on an explicit allowlist are copied from the auth server's response into the response sent to the client. - Failure mode (fail-open or fail-closed) is configured per provider. The default is fail-closed: a timeout or transport error denies the request rather than silently admitting it.
- OIDC mode uses discovery and token introspection to validate bearer tokens. It does not cache the discovery document (refetched per request) and does not implement interactive login or session-cookie flows. For local signature validation against an issuer's published keys, use JWT auth with
jwks_urlinstead.
JWT signature verification details:
- The algorithm allowlist is explicit policy, not inferred from configured key material. A token whose
algheader is absent fromallowed_algorithmsis rejected before any key is resolved, andalg: nonenever maps to a verification mode. - Key type is re-checked at verification time, so an asymmetric public key can never satisfy an
HS256token and vice versa. - RSA keys below 2048 bits are rejected, whether configured statically or published via JWKS.
- Refresh failures never widen access: the last known-good key set keeps validating until the staleness window expires, after which requests are rejected rather than admitted.
Boundary rule:
- request-path auth lives under upstream routing / forwarding policy
- admin-plane auth lives only under control-plane modules
- credentials, failures, and audit events from one plane must not be confused with the other
What Spooky Does Not Currently Provide
Spooky does not currently provide first-class:
- OIDC login flows (interactive/browser SSO) or session-cookie handling
- a generic RBAC/policy engine beyond scope/role checks on JWT claims
- WAF behavior
- deep content inspection
- extensible third-party auth/policy modules
Recommended Deployment Security Posture
- keep the control API bound to loopback or a strongly isolated admin network
- use explicit admin roles rather than a single all-powerful token when possible
- require control API mTLS in production
- use a strong control API token and rotate it as an administrative secret when bearer auth is enabled
- restrict control API source addresses with
ip_allowlist.cidrs - enable the dedicated admin audit stream
- keep upstream certificate verification enabled in production
- run with least privilege after bind
- restrict filesystem write access to the minimum required paths
- monitor handshake failures, overload events, and unexpected restart activity
Future Security Hardening Priorities
- deeper parser fuzzing
- stronger control-plane auditability
- broader documentation of mTLS behavior across all ingress paths
- explicit support boundaries for admin-plane deployment patterns
- stronger auth/policy features where the product direction requires them