Security guarantees#
Each property below has at least one test that fails if it stops holding.
See also
To report a vulnerability, follow SECURITY.md. Please do not use the public issue tracker.
What the package guarantees#
Authentication flows#
Property |
Detail |
|---|---|
Every flow carries |
A callback is bound to the session that started it. |
A linking flow needs an authenticated session |
At initiation and at completion, and by the same session. Holding the code, the state and the flow cookie is not enough to attach an identity to somebody else's account. |
A link collision is a hard error |
An external identity already linked to one userid is never attached to another. There is no merge, and adding one is out of scope. |
Unlinking your last way in is refused |
Unless you have a verified email identity or a real password. |
Post-login redirect targets are validated |
Against the portal, on the backend and the frontend. A target that never reaches the backend cannot be checked by it. |
Email verification and linking#
Property |
Detail |
|---|---|
Automatic linking needs two switches, both off by default |
It matches only an address this site holds as verified, and only when the provider now asserting it is one the operator marked as trusting. |
Only a literal |
A forged unverified email claim cannot link. |
A provider's |
Off unless a driver knows the provider really checks. |
There is one notion of verified |
A trusted provider's verified addresses are recorded exactly as a magic link records one. No second flag to drift. |
A magic link only goes to an address already on your profile |
The proof is proof of control over whatever address it was sent to, so a free-text box would verify any mailbox somebody can reach. A caller with no profile is not held to this: there is no list to name an address on. |
Magic-link tokens |
Single use, burned server side, at most fifteen minutes. The send endpoint is rate limited per address and per IP, and answers identically for known and unknown addresses. |
Storage and exposure#
Property |
Detail |
|---|---|
Secrets are write-only everywhere |
Including GenericSetup export. The audit log never records credentials or tokens. |
This package's vocabularies require a permission |
|
A provider icon is sanitized as it is stored |
An icon is an SVG rendered inline so it can take the button's colour, which makes it markup: it can carry a script, a stylesheet, and references to other documents. Only shapes and attributes on a fixed list survive; an element off the list is dropped with everything inside it rather than unwrapped; no attribute value may reference an address elsewhere; the result is serialized from the parsed tree rather than sliced out of the input. A document that is not an SVG is refused. |
Sanitizing on save rather than on render is what keeps the registry, a GenericSetup export, and anything else reading the record from holding the dangerous version.
Enforced in CI#
Rule |
Enforced by |
|---|---|
Core never imports from |
|
Protocol messages are never constructed by hand |
A grep-level rule failing the build if authorization URLs, token requests, or JWT parsing appear outside the flow modules, which delegate to authlib. |
Things to know before deploying#
The userid is permanent and opaque |
A random UUID minted once, never derived from an email address or a username, because both change. Nothing in this package rewrites it. |
Provider avatars are off by default |
Keep them off unless you have read why. See About profiles and groups. |
IP and user-agent recording stores personal data |
Off by default. See The audit log. |
The audit log is not a session ledger |
It records authentication events, not sessions. |
Deleting a user keeps their identities and audit entries |
Each keeps personal data against a userid that no longer resolves: a claims snapshot on the identity, a login history on the audit entries. Unlink the identities before deleting the user if you have an erasure obligation. See Users and groups as content. |
A client granted |
|
A group inside a group grants through it |
At any depth, so a nesting is a grant and reviewing one group's access means reviewing what feeds into it. An inactive group grants nothing and passes nothing through. A cycle terminates rather than raising; it means both groups grant each other. |
A membership list is personal data about other people |
|
A provider's groups grant nothing until you map them |
A group map starts empty, an unmapped provider group grants nothing and is never created locally, and a row pointing at a group this site does not have is skipped and logged. Every sign-in reconciles, and takes back only what that same provider granted. See How to map provider groups. |
Access tokens cannot be recalled |
They are self-encoded and there is no denylist, so a revoked client's tokens die when they expire—at most the configured access-token TTL. See How to enable back-channel logout. |