Skip to main content
This page summarizes the security design decisions in theauth-go and the threats it is built to resist. It also documents what is explicitly out of scope.
Full STRIDE analysis. The per-subsystem STRIDE (Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege) tables and the threat catalog are further down this page. This is a design document, not an audit report or a certification.

Trust boundaries

theauth-go trusts the reverse proxy layer only when Config.TrustedProxies is explicitly set. By default, X-Forwarded-For is ignored and r.RemoteAddr is used for all rate-limiting and audit IP capture.

Session tokens

  • Raw session tokens are opaque (cryptographically random, crypto/rand).
  • Only a SHA-256 hash is stored in the database. A full database dump does not yield usable session tokens.
  • Cookies are set with HttpOnly, Secure (when Config.SecureCookie is true), and SameSite=Lax.
  • Revocation is a single UPDATE that marks the session hash as invalid. The raw token in the cookie becomes useless immediately.

Password handling

  • Argon2id with OWASP 2026 recommended parameters.
  • Minimum 12-character enforcement at sign-up.
  • Anti-enumeration: unknown-email and empty-password branches pay the full Argon2id verify cost against a dummy hash synthesized once at New time. Timing side-channel is closed.
  • Password reset tokens are single-use and short-lived (PasswordResetTTL, default 1 hour). Consuming a reset token revokes all sessions for the user.

Rate limiting

  • Per-IP and per-email rate limits on every credential endpoint.
  • Rate limit buckets use r.RemoteAddr by default; X-Forwarded-For is only consulted when the proxy is in Config.TrustedProxies.
  • POST /oauth/register has a separate per-IP rate limit: 1 req/min in anonymous mode, 5 req/min in bearer-gated mode.

OAuth 2.1 AS

  • PKCE S256 is mandatory. Requests without code_challenge are rejected.
  • Audience binding (RFC 8707) is mandatory. Tokens are bound to a specific resource URI at mint time and verified at introspection.
  • Refresh token family revocation: if a used (rotated) refresh token is presented again, the entire family is invalidated (RFC 9700).
  • DCR bearer tokens are compared with crypto/subtle.ConstantTimeCompare against pre-hashed SHA-256 digests. Timing oracle on token presence is closed.
  • Remote JWKS documents (a client’s jwks_uri for private_key_jwt, or a trusted issuer’s JWKSURL) are fetched through a guarded client since v2.7.1: https only, non-public addresses refused at dial time, no redirects, a 5 second timeout, a 512 KiB cap, and a bounded cache with a TTL. JWTBearerConfig.AllowPrivateJWKSNetworks relaxes this for local development only.
  • Signing keys are AES-256-GCM encrypted at rest (requires Config.EncryptionKey).

Agent and delegation

  • Agent status transitions are audited. Suspended and revoked agents fail authentication immediately after the clientauthcache TTL (default 5 minutes) expires. The cache is busted synchronously on SuspendAgent and RevokeAgent (introduced in v2.1.0 PR #33).
  • Delegation grants verify body.userId is a member of the admin’s organization before creation (cross-org grant attack closed, audit H3 2026-06-20).
  • Actor chain depth is capped at 3 at both the mint path and the config validation path (ErrAgentChainDepthTooHigh).

WebAuthn / passkeys

  • Sign-count replay protection: a new sign count must be strictly greater than the stored value. Clone-attempt returns ErrReplayDetected.
  • Challenge storage is ephemeral and GC’d after ChallengeTTL.

TOTP

  • TOTP secrets are AES-256-GCM encrypted at rest.
  • Recovery codes are SHA-256 hashed.
  • 10 single-use recovery codes per user.

SAML

  • Only signed assertions are accepted (ErrSAMLUnsignedAssertion on unsigned).
  • AuthnRequest replay tracking with a configurable TTL.

SCIM

  • Bearer tokens are SHA-256 hashed. Full token never stored.
  • Per-organization isolation enforced at every CRUD endpoint.

Out of scope

The following are explicitly not covered by theauth-go:
  • TLS termination. Run a TLS-terminating reverse proxy (nginx, Caddy, AWS ALB) in front.
  • DDoS and volumetric flood protection. Use a WAF or CDN-level rate limiter for sustained volumetric attacks.
  • Physical security of the host running Postgres.
  • Audit tamper-evidence. The audit_events table is append-only (no UPDATE/DELETE in the Storage interface) but not cryptographically tamper-evident: it is not Merkle-chained or signed. This is not currently on docs/ROADMAP.md; operators who need tamper-evidence should layer Merkle signing or stream to an append-only SIEM sink themselves.
  • ABAC and hierarchical roles. RBAC is a closed permission catalog with flat roles. Time-bound and attribute-based access control are deferred.
  • Hardware-attested agent credentials. AgentCredentialKindJWK and AgentCredentialKindX509 paths exist but return ErrNotImplemented. TPM-backed signing is a planned future feature.

Reporting security issues

See SECURITY.md. Report security vulnerabilities to security@glincker.com. Do not open public issues for security findings.

Full STRIDE threat analysis

Scope: theauth-go library, as deployed by an operator in a Go HTTP application. Method: STRIDE (Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege). Last reviewed: 2026-10-07, against v2.7.1 and the CHANGELOG since v2.4.0. Re-checked claims are those about cookies, OAuth state, CIMD fetching, login throttling, storage backends, SAML and dependency versions; the rest was spot-checked against the code, not re-derived. This is a design document, not an audit report or a certification.

Table of Contents

  1. Trust Boundaries
  2. Components in Scope
  3. Out of Scope
  4. Authorization Server (AS)
  5. Resource Server / mcpresource
  6. Storage Backend
  7. Authentication Flows
  8. Grant Types
  9. Audit Subsystem
  10. Session Subsystem
  11. Agent Identity Flow
  12. Delegation Chains
  13. CIMD
  14. JWT-Bearer and private_key_jwt Key Fetches
  15. Threat Catalog

Trust Boundaries

The library does not control what crosses the operator’s TLS terminator, the operator’s secret store, or the operator’s hosting environment. Those are explicitly out of scope.

Components in Scope

  • Authorization Server (AS): issues JWTs (authorization_code, refresh_token, client_credentials, token_exchange grants). Lives in internal/as/.
  • Resource Server / mcpresource: validates inbound JWTs at protected API endpoints. Lives in mcpresource/.
  • Storage backend: memory (storage/memory/), Postgres (storage/postgres/), MySQL (storage/mysql/) and SQLite (storage/sqlite/, a separate module) stores. Pluggable via the Storage interface and its capability interfaces in storage.go.
  • Authentication flows: password, OAuth social login, magic link, WebAuthn, TOTP, SAML, SCIM.
  • Audit subsystem: event emission, redaction, sinks. Lives in internal/audit/, audit/.
  • Session subsystem: session minting, lookup, expiry, revocation. Lives in internal/session/.
  • Agent identity flow: machine credential issuance and revocation. Lives in internal/agent/.
  • Delegation chains: scoped delegation grants. Lives in internal/delegation/.
  • CIMD (Client Identity Metadata Document): fetches and caches machine-client metadata. Lives in internal/cimd/.
  • RBAC: role and permission enforcement. Lives in internal/rbac/.

Out of Scope

The following are explicitly not mitigated by this library. They are the deploying operator’s responsibility.
  • TLS termination and certificate management. The library emits a slog.Warn at startup when SecureCookie=false and BaseURL is not https, and adds Secure to cookies per request when BaseURL is https, the connection is TLS, or a TrustedProxies peer sends X-Forwarded-Proto: https. It cannot enforce TLS on the transport layer itself.
  • Secret management at rest. Encryption keys (AuditConfig.EncryptionKey, TOTP key, etc.) must be supplied by the operator; the library does not manage a secret store.
  • Container/VM/Kubernetes pod isolation. Network policies, pod security admission, and kernel security modules are outside library scope.
  • Operator’s authentication device security. TOTP app compromise, WebAuthn key theft, or phone compromise for CIBA-style flows are not addressable at the library layer.
  • DDoS at the network layer. The library provides application-layer limits (RateLimitByIP, RateLimitByEmail, and the password login throttle) but not TCP/UDP-layer flood protection.
  • Side channels in Go runtime or crypto/subtle. The library uses crypto/subtle for all sensitive comparisons but cannot guarantee the Go runtime’s memory allocator or garbage collector do not introduce timing channels.

Authorization Server (AS)

Trust Boundaries

  • Receives client_id, client_secret, code_verifier, and DPoP header values over HTTP.
  • Reads client rows and authorization code rows from storage.
  • Holds Ed25519 private keys in process memory.
  • Writes access tokens and refresh tokens to storage.

STRIDE Analysis

Spoofing Tampering Repudiation Information Disclosure Denial of Service Elevation of Privilege

Resource Server / mcpresource

Trust Boundaries

  • Receives HTTP requests with Authorization: Bearer <token> or Authorization: DPoP <token> plus optional DPoP: proof header.
  • Fetches public keys from the JWKS endpoint (operator-configured URL, cached in process).
  • No storage writes.

STRIDE Analysis

Spoofing Tampering Information Disclosure Denial of Service

Storage Backend

Trust Boundaries

  • Library code speaks to storage via the Storage interface and its capability interfaces (defined in storage.go).
  • The Postgres, MySQL and SQLite stores pass values as bound parameters (Postgres uses sqlc-generated and hand-written queries; MySQL and SQLite use hand-written ones). Where SQL text is assembled with fmt.Sprintf, the pieces are constants such as the migration ledger name and a shared liveness predicate.
  • Memory store is in-process only; no network trust boundary.
  • The shared contract suite (storagetest) is authoritative against the memory backend in CI. Running it against Postgres and MySQL is opt-in and currently fails on constraint and foreign-key edge cases, so those adapters are less proven against the contract than the memory store (see the roadmap).

STRIDE Analysis

Tampering Information Disclosure Denial of Service

Authentication Flows

Password

Trust Boundaries: Receives plaintext password over HTTP (must be HTTPS at operator’s transport layer). Reads password hash from storage. Residual risk: Operator must enforce HTTPS to prevent plaintext password capture.

OAuth Social Login

Trust Boundaries: Operator’s app redirects user to provider; provider redirects back with code + state. The state is a crypto/rand token held in a server-side state store; the theauth_oauth_state HttpOnly cookie carries a separate browser-binding secret, not the state value (changed in v2.6.0). Trust Boundaries: Token emailed to user; operator’s email delivery path is outside library scope.

WebAuthn

Trust Boundaries: Browser generates a cryptographic assertion over the challenge; assertion verified against stored public key.

TOTP

Trust Boundaries: User supplies 6-digit code generated by an authenticator app.

SAML

Trust Boundaries: IdP posts a signed XML assertion to the library’s SP assertion consumer service.

SCIM

Trust Boundaries: SCIM client presents a bearer token; library validates it against a stored hash.

Grant Types

Authorization Code + PKCE

Refresh Token

Client Credentials

Token Exchange (RFC 8693)

DPoP-Bound Tokens (RFC 9449)

PAR (Pushed Authorization Request)

PAR (RFC 9126) is implemented (internal/as/par.go). When AuthorizationServerConfig.PAR is set and the configured storage backend implements the optional PARStorage interface, POST /oauth/par accepts the full set of authorization parameters over a back-channel POST and returns a request_uri reference; GET /oauth/authorize then accepts that request_uri in place of inline query parameters. PARConfig.RequirePAR (default false) can be set to reject any /oauth/authorize request that still passes inline parameters, forcing every client through PAR. Residual risk: PAR is opt-in and not mounted at all unless the storage backend satisfies PARStorage (the memory and Postgres adapters do; the MySQL and SQLite adapters do not). Deployments that have not enabled AuthorizationServerConfig.PAR still pass authorization parameters as query string values on the redirect; an attacker who can intercept the redirect URL can observe but not forge them (PKCE protects the exchange). Those deployments should combine RequireState=true with PKCE S256 as an interim mitigation.

Audit Subsystem

Trust Boundaries

  • Events are emitted in-process; sink goroutines forward them out-of-process to OTLP, Splunk, or webhook endpoints.
  • The webhook sink signs each request body with HMAC-SHA256 (audit/sinks/webhook/webhook.go).

STRIDE Analysis

Repudiation Information Disclosure Denial of Service

Session Subsystem

Trust Boundaries

  • Session token is a crypto/rand opaque value stored as SHA-256 hash in the database.
  • Token is transmitted as an HttpOnly cookie.

STRIDE Analysis

Spoofing Tampering Information Disclosure

Agent Identity Flow

Trust Boundaries

  • Machine clients authenticate with client_id + client_secret; secret is Argon2id-hashed.
  • Agents issue tokens via the client_credentials grant.

STRIDE Analysis

Spoofing Elevation of Privilege

Delegation Chains

Trust Boundaries

  • An agent acting on behalf of a user (RFC 8693 token exchange) carries a delegation grant that specifies the permitted scope.

STRIDE Analysis

Elevation of Privilege

CIMD (Client Identity Metadata Document)

Trust Boundaries

  • The AS fetches a JSON document from an HTTPS URL supplied as the client_id.
  • The document is cached in process memory for the configured CacheTTL.

STRIDE Analysis

Spoofing Server-Side Request Forgery Since v2.6.0, CIMD fetches also refuse non-public destinations: the dialer rejects loopback, private, link-local (including cloud metadata addresses), CGNAT and similar ranges at connect time (so a resolver that rebinds after validation cannot swap in a private address), redirects are never followed, and no proxy is used. CIMDConfig.AllowPrivateNetworks is a development opt-in that turns the check off, and CIMDConfig.DenyHost adds a host deny hook. Residual risk: TrustPolicy is still the primary gate. With AllowAnyHTTPS() any public HTTPS host can be fetched, so prefer AllowHTTPSHost allowlists in production, and leave AllowPrivateNetworks off outside local development.

JWT-Bearer and private_key_jwt Key Fetches

Trust Boundaries

  • For clients using private_key_jwt or the JWT-bearer grant, the AS fetches a JWKS document from a client’s registered jwks_uri or a trusted issuer’s JWKSURL (internal/as/jwtbearer.go).

STRIDE Analysis

Server-Side Request Forgery

Threat Catalog

The following threats are referenced in the summary above. For procurement, this table maps each named threat to its mitigation status.
Last modified on October 7, 2026