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_file and ca_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_token is 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:

  1. source-address policy, if observability.control_api.ip_allowlist is configured
  2. authentication using bearer token, mTLS identity, or both
  3. authorization against the route-to-role contract

Role model:

  • viewer: runtime snapshot and generation history reads
  • operator: viewer plus validate, preview, activate, rollback, reload, and cert reload
  • admin: operator plus restart and future destructive admin actions

Response and failure contract:

  • 401 Unauthorized: missing or invalid authentication
  • 403 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: true
  • strict_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 HS256 with a shared secret, and RS256/ES256 against 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, or Challenge; 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_url instead.

JWT signature verification details:

  • The algorithm allowlist is explicit policy, not inferred from configured key material. A token whose alg header is absent from allowed_algorithms is rejected before any key is resolved, and alg: none never maps to a verification mode.
  • Key type is re-checked at verification time, so an asymmetric public key can never satisfy an HS256 token 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
  • 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