PST-010: A recovery link stays usable for its full hour after the person has changed their password or signed in #739
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#739
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found in the security audit of 2026-10-10 (
SECURITY-AUDIT-2026-10-10.md), run againstmainatc30851bfrom 8 to 10 October 2026, as PST-010. Severity: Low.The text below is the audit's entry, unchanged apart from links to the other issues it names. The finding was produced by automated review and checked by a second automated pass; re-check it before acting on it.
src/postulo/accounts/recovery.py(46-66)Who can do it, and what they need. Whoever ends up holding a recovery link after the person it was made for no longer needs it: a chat log, a screenshot, a shoulder surfer. Precondition: the link was issued in the last hour and not revoked by hand.
How it works.
Impact. Narrow window and a secret token are required, so a hardening gap rather than a hole: the person's own password change does not close a door an administrator opened for them.
Evidence.
Verification (verdict: confirmed; severity after verification: Low). Keep the finding to the narrow gap: a live RecoveryLink is not revoked when the person changes their password or signs in, so a leaked token stays usable for the remainder of its hour. Remediation: revoke the person's live links on password_changed, password_reset and user_logged_in (or fold the password hash and last_login into the link's validity, as Django's reset tokens do). Drop step 4 ('minting needs no fresh proof'): that is the trusted administrator or a compromised browser, both outside the threat model. Line refs: recovery.py:46-66 and 81-96, views.py:576-624 (_link at 586-592), models.py:663-664 are correct.
Suggested remediation. Revoke live recovery links whenever the account's password changes (allauth's password_changed / password_reset signals and recovery.spend itself) and on a successful sign-in; consider requiring reauthentication to mint one and emailing the account when a link is created. Add a test that a password change kills a live link.
Fixed in
366d02ee3on main.