ProudAuthDocs

Threat model: cross-system trust surfaces#

Scope: features that extend trust across a system boundary or across tenants: the OIDC session-management iframe (OIDC-014), the shared-signals transmitter and receiver (AUD-016, ADV-007), the container-registry token bridge (ADV-010), passkey attestation trust and related origins (PASS-009, PASS-010), federation transfer (FED-016), organization relationships and partner project access (TEN-021, TEN-020), authentication flow revisions (AUT-019), historical entitlement evidence (ADV-011) and external risk signals (ADV-012). The rest of the system is in system.md. Like that document, this reflects code review, not an independent assessment (SEC-018 has not been done).

Assets and boundaries#

AssetWhere
Browser session state readable by scripts (pa_opbs)Cookie on /realms/{realm}/
Security events about users, and the push Authorization values of receiversssf_events (signed tokens), ssf_streams.authorization_ciphertext (AEAD)
Trust in outside transmitters: push secrets, keys, action policyssf_transmitters (secret as digest)
Registry accessRegistry tokens (JWT, realm signing keys), registry service rules
Authenticator trust metadataOperator MDS3 BLOB, webauthn_trusted_authenticators
Account linksexternal_identities, transfer maps
Cross-organization authorityorganization_relationships, project_grants, project role assignments
Sign-in policy historyauth_flow_revisions
BoundaryOutside partyEnforcement
RP page → check-session iframeAny web origin that frames the iframepostMessage answers only to origins registered for the named client; state recomputed with the sender's origin
SSF receiver → stream APIMachine clientsAdmin-audience client-credentials token; realm.ssf_streams.manage; streams owned by the calling client
ProudAuth → SSF push endpointsReceiver HTTP serversWebhook egress policy, residency allowlist, https in production
Outside transmitter → push endpointRegistered transmittersPush secret, signature, issuer, audience, age, replay record, explicit action policy
Registry client → token endpointDocker clients, CI systems, anonymous usersHTTP Basic with a resource-bound credential or client secret; permission and scope checks per action
Organization → organizationOther tenants of the realmExplicit relationships, owner-issued project grants, delegated role subsets

Threats#

ThreatMitigation (code)TestsResidual
I: script-readable session state (OIDC-014)Off by default (sessions.check_session_iframe); pa_opbs is a keyed digest of the session ID under the lookup key, never the session secret; scoped to the realm path; the iframe page allows only its own hashed inline script and connect-src 'self' (httpapi/checksession.go, session/checksession.go)TestCheckSessionIframeThe cookie is SameSite=None and not HttpOnly by necessity: any script running on the IdP origin under the realm path could read it. It identifies the browser session to such a script, though it is no credential
S/I: an origin probing whether a user is signed inThe iframe answers error to origins not registered for the client (allowed origins or redirect-URI origins); session_state includes the RP origin and a fresh salt, so a value copied to another origin never reads unchangedTestCheckSessionIframe (origin check and recomputation, in Go; the script itself is not run in a browser)An origin registered for a client can poll the iframe with its own session_state values, which is the purpose of the extension. Browsers that partition third-party cookies make every check changed; server-side revocations are not visible to the iframe at all (use back-channel logout)
I: transmitter sends events about users unrelated to the receiver (AUD-016)Default scope client_users: only users with an authorization grant for the receiver client; listed narrows further; all only by an administrator; adding subjects never widens the scope and answers alike for unknown subjects; pairwise clients get their pairwise sub (internal/ssf/ssf.go)TestSharedSignalsTransmitterGrants count while their rows are retained, including revoked grants (up to privacy.session_retention after they ended). An administrator-set all scope sends events about every user to that receiver. identifier-changed tells the receiver that an identifier changed (not the value)
S: a client manages streams it should notStream API requires a service-principal token for the admin audience with realm.ssf_streams.manage; human administrator tokens are refused; streams are filtered by the calling clientTestSharedSignalsTransmitter (human admin, client without the permission, other receiver)The permission is realm-wide: a receiver with it can open streams about its own users without further review; audit ssf.stream.created is the review point
T/S: token confusion with security event tokenstyp: secevent+jwt, aud = the receiver's client_id, events claimTestSharedSignalsTransmitter (typ, signature, audience)The realm's ordinary signing keys sign them; receivers and resource servers must check typ
I: receiver push credentials leakauthorization_header is write-only, AEAD-encrypted and bound to the stream; never returned or logged—Readable by anyone holding the root keys and database
S: forged inbound events (ADV-007)Per-transmitter push secret (digest), single signature with RS256/PS256/ES256/EdDSA keys of the registered set, iss = registered issuer, aud = push endpoint, iat age and future-skew limits, jti replay record (internal/ssf/receiver.go)TestSharedSignalsReceiverA compromised transmitter (or its signing key and push secret) can trigger every action its policy allows for the subjects it can name
E: an outside signal escalates or locks outExplicit action policy per event type (default record only); account-enabled and identifier-changed can only be recorded; protected accounts are never suspended (sessions revoked instead); iss_sub maps only through the transmitter's federation connection, email only to verified addresses and only when allowedTestSharedSignalsReceiversuspend_user and mark_password_compromised let a trusted transmitter deny service to ordinary accounts; review action policies like any other trust decision
I: subject enumeration through the push endpointUnknown subjects are answered 202 like accepted events; records keep no token or subject identifiersTestSharedSignalsReceiverTiming is not equalized
S/E: registry tokens beyond a principal's rights (ADV-010)Credentials must be minted for the registry's resource (or a machine client's grant to it); each action needs the rule's permission (in its organization or realm-wide) and the credential's scope; organization-bound credentials reach only that organization's rules; ungranted actions are omitted (internal/registry/registry.go)TestRegistryTokenBridgeFirst matching rule wins: an early broad pattern decides for everything it matches. anonymous_pull exposes matching images to anyone. Tokens are bearer tokens valid up to token_ttl_seconds after revocation of the credential. Machine-client secrets and API keys are tested; a real registry is not
S: registry accepts other realm tokensRegistry tokens carry aud = service name and an access claim—The registry trusts every token the realm keys sign for that audience; keep service names distinct from API resource audiences
S: untrusted authenticator models (PASS-009)Optional trusted attestation against operator FIDO metadata (verified against the FIDO root or a configured root at startup) or realm trusted authenticators; status reports checked; allow/block lists at registration and sign-in (internal/passkey/trust.go)TestPasskeyAttestationTrustWithout trusted attestation the AAGUID is self-reported. The metadata BLOB is only as fresh as the operator keeps it, and is read at startup only. Real metadata and real authenticators are not tested
S: passkey use from foreign origins (PASS-010)Related origins need the operator switch, an explicit RP ID and an explicit list (at most 20 origins, five registrable domains); no fallbackTestPasskeyAdvancedCapabilitiesEvery listed origin can run ceremonies for the realm's passkeys: a compromised related origin can phish assertions as the RP origin could
E: account takeover through relinking (FED-016)No email matching; per-entry analysis with collision reporting; second administrator approves; apply re-analyses under lock and moves only ready entries; upstream tokens dropped (internal/federation/transfer.go)TestFederationTransferThe map's correctness is the administrators' responsibility: an approved entry that pairs one person's source subject with another person's target subject gives that other person the account. Two colluding administrators can approve any map
E: implicit cross-tenant access (TEN-021, TEN-020)Relationships grant nothing; project grants only by the owner organization, only to related organizations (unless a realm administrator), only a subset of the project's roles; partner assignments only to the partner's active members, removed with the grant or the membership; organization-authority permissions need an MFA session (internal/organization/relationships.go, internal/application/projectgrants.go)TestOrganizationRelationshipsAndPartnerProjects, TestProjectGrantExpiryA grant past its valid_until is ended by the worker's expiry task (assignments removed, authorization revisions incremented, project.grant.expired), so it lasts until the next run of that task; ending a relationship leaves existing grants in place
T: silent weakening of sign-in policy (AUT-019)Every flow change becomes an audited, published revision; bypass errors block publication, PUT /settings and imports; stale drafts are refused; warnings must be acknowledged in the revision API (internal/realm/flowrev.go)TestAuthenticationFlowRevisions, internal/realm/flowrev_test.goThe analysis is conservative and rule-by-rule: overlap by several earlier rules is only a warning. PUT /settings and imports do not ask for warning acknowledgement
S/E: a risk service, or an attacker who reaches it, steers sign-in (ADV-012)Risk signals enter only through authentication.risk hooks: signed requests, egress and residency policy, a bounded timeout, a versioned contract (ProudAuth-Hook-Version). Answers are only low/medium/high with reasons; anything else is a failed call. A level can only make a flow rule match: deny, challenge, review or a stronger factor; allow never removes requirements, and risk never reaches authorization decisions (authentication-flows.md)TestRiskNeverOverridesDeny (internal/realm/flowrev_test.go), TestAuthenticationFlowPolicy, TestAuthenticationFlowRevisionsA compromised risk service can push every sign-in to high (denial of service, extra challenges) or keep low where a realm's rules relax on low risk; it can never lift a deny placed before the risk rules or skip MFA
I: risk hooks as a data channel (ADV-012)Requests carry IDs, the primary method, the network reduced to /24 or /48, the remembered-device flag and the recent failure count; no credentials, tokens, email addresses or profile dataTestAuthenticationFlowPolicy (hook request contents)The hook operator learns when and from which network each user signs in; treat it as a processor of sign-in metadata
F: risk service down or slow (ADV-012)Deterministic fallback: a missing signal applies restrictive rules (unless on_missing_signal: skip) and never satisfies allow; with several hooks the highest answered level is a lower boundTestAuthenticationFlowPolicy, TestRiskNeverOverridesDenyAn outage of the risk service turns every risk-gated sign-in into its restrictive path
I: investigation queries as a disclosure channel (ADV-011)realm.audit.read, MFA session, required reason, audited reconstruction (internal/authorization/history.go)TestHistoricalEntitlementsNo second-person approval; the result is evidence for the auditor and should be handled as such

Not covered#

  • Real browsers running the check-session script, real SSF transmitters and receivers, real container registries, hardware authenticators and real FIDO metadata have not been tested (interoperability.md).
  • The SSF push endpoint, the iframe's origin check and the registry token endpoint have no rate budget of their own; only the general request limits (body size, load shedding) apply. The credentials they accept are high-entropy secrets, so guessing is not the concern; volume is.
ProudAuth · Self-hosted identityBuilt 2026-10-13 · d9cc813ae809 · content snapshot
⌕

Search all guides and the API contract.

↑ ↓ to navigate Enter to open · Esc to close