Skip to content

sec(auth): stale whole-row user writes can still erase MFA fields #493

Description

@cristim

PR #487 (merged, closed #474) narrowed profile update, password change and reset request to compare-and-swap column writes. Other writers still read a user row, mutate it and save the whole row, so a stale read can overwrite concurrent MFA fields (or restore an old password hash).

Remaining whole-row writers on main:

  • ConfirmPasswordReset: internal/auth/service_password.go:398, 406, 418. Worst case: a reset-token holder whose read precedes a concurrent MFA enrollment wipes MFA and sets a password they know.
  • Recovery-code use at login: internal/auth/service.go:255. Can also restore an old password hash over a concurrent password change.
  • Admin UpdateUser: internal/auth/service_user.go:359.
  • MFA lifecycle writes: internal/auth/service_mfa.go:308, 350, 398, 421, 463, 494 (overlaps sec(auth): MFA can be replaced with a session and password alone #227).

Also: #487's race tests call the service directly, not the HTTP API.

Suggested direction: reuse the narrow CAS write pattern from UpdateUserCredentials in internal/auth/store_postgres_credentials.go, with a column-scoped write per caller.

Acceptance: none of these paths writes MFA fields or the password hash from a stale read; race tests cover each path, at least one through the HTTP API.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions