fix(platform): audit successful 2FA and passkey lifecycle events - #3181
Open
Israeltheminer wants to merge 1 commit into
Open
fix(platform): audit successful 2FA and passkey lifecycle events#3181Israeltheminer wants to merge 1 commit into
Israeltheminer wants to merge 1 commit into
Conversation
0.4 audited every SUCCESSFUL second-factor lifecycle event (#1508). The port kept only the failure half, so an attacker who registered their own passkey and turned 2FA off left no successful-action trail — the events that matter most in an account takeover. All five 0.4 action names are reused verbatim: 2fa_enrolled, 2fa_disabled, passkey_added, passkey_removed, passkey_sign_in. A renamed action silently breaks any existing query or export that groups on it. Checked against 0.4's full vocabulary — passkey_removed and passkey_revoked_by_admin were distinct events there and stay distinct. Rows are written in the same transaction as the state change, with the 0.4 security/success shape. Split out of a wider branch that also gated conversation writes; that half is held back because it breaks the harness (see the PR). Refs #3142.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An attacker who registered their own passkey and turned 2FA off left no successful-action trail. 0.4 audited every successful second-factor lifecycle event (#1508); the port kept only the failure half.
Split out of #3149. Refs #3142.
Why
The failure half audits, so a wrong code is recorded — but enabling 2FA, disabling it, and adding, removing or signing in with a passkey were silent. Those are the events that matter most in an account takeover.
All five 0.4 action names are reused verbatim:
2fa_enrolled,2fa_disabled,passkey_added,passkey_removed,passkey_sign_in. A renamed action silently breaks any query or export that groups on it. Checked against 0.4's full vocabulary —passkey_removedandpasskey_revoked_by_adminwere distinct events there and stay distinct here.Rows are written in the same transaction as the state change, carrying the 0.4
security/successshape.What is NOT here, and why
#3149 also restored the conversation write-role gate, so a read-only member could no longer reply, send outbound mail, close, bulk-act or delete. That half is held back. It is faithful — 0.4's rule is literally
authorizeRls(role, 'conversations', 'write'), and this repo's own matrix givesmemberread only — but it breaks the harness:The first observable failure is real, not incidental:
The gate fires before the attachment-ownership check, so that assertion gets the wrong 403 — and several later chat checks then cascade. Either the harness fixtures compose as under-privileged actors and need their roles raised, or the gate's placement relative to the ownership check needs rethinking. That is real work, not a rebase, so it is not being smuggled in behind a verified fix.
Tests
integration-check.tsasserts the whole trail and its shape, on a dedicated throwaway user rather than the harness's own:That isolation is load-bearing, and finding out cost three runs. Running the lifecycle on the shared session left that user's second factor changed and their session invalid, which failed five unrelated checks — the 2FA grace check plus four chat checks. The throwaway user also needs an org membership, because audit rows are org-scoped and without one the trail reads empty.
WebAuthn cannot be driven from a script, so the passkey trio is asserted at the writer: the action names land as rows with the security shape.
374/380, and the six failures are byte-identical to a baseline run of unmodified
mainon the same instance — the warm-MinIO bucket collision, twoyt-dlpprobes and three agent-lane probes.Gate:
typecheck,oxlint --type-aware,oxfmt --check,lint:sastgreen; branched fromorigin/mainat899fcc08a.