Skip to content

Fix single-use CSRF token: a failed submit no longer burns it - #105

Merged
WebTigers merged 1 commit into
mainfrom
fix/csrf-hash-single-use
Aug 7, 2026
Merged

Fix single-use CSRF token: a failed submit no longer burns it#105
WebTigers merged 1 commit into
mainfrom
fix/csrf-hash-single-use

Conversation

@WebTigers

Copy link
Copy Markdown
Owner

The bug

On the profile Security / change-password form: submit with any field invalid → "Please correct the highlighted fields" (correct). Fix the field, resubmit → "Oops — your security token expired. Please refresh the page to continue." The only cure is a full page refresh — you can't correct a validation error and resubmit.

Root cause

Zend_Form_Element_Hash::initCsrfToken() arms the token with setExpirationHops(1, …) — a single-request expiration. The first submit consumes the token, so a submit that fails any other field's validation leaves the corrected resubmit with a spent token → CSRF failure.

That directly contradicts Tiger_Form's own documented design (csrfSalt()): "the token isn't single-use; it validates until it times out" (CSRF_TIMEOUT = 2h, salt-shared across related endpoints).

The fix

Add Tiger_Form_Element_Hash (alongside the existing Tiger_Form_Element_Recaptcha) that overrides initCsrfToken() to arm the token on the seconds TTL only — no hop limit. Tiger_Form::init() uses it in place of the stock 'hash'. One rendered token now survives a failed-then-corrected resubmit within the session, exactly as the design intended. Everything else (salt, session name, hash generation, the CSRF validator) is inherited unchanged.

Security posture is unchanged from the documented intent: session-lifetime CSRF tokens (not single-use) are the standard, and the salt-per-form scoping is untouched.

Verified

  • Unit test (tests/Unit/Form/HashElementTest.php): the element arms on timeout with no hop; a contrast test pins the stock Zend 1-hop default.
  • End-to-end on dev-com: two changePassword submits with the same rendered token both return the field error — neither the CSRF error (before the fix, the 2nd was "token expired").
  • Full unit suite green (798 tests).

🤖 Generated with Claude Code

Zend_Form_Element_Hash arms the CSRF token with a 1-HOP session expiration, so the
first submit consumes it. If that submit fails any OTHER field's validation, the
corrected resubmit dies with "your security token expired" and the only cure is a
full page refresh — reproduced on the profile Security / change-password form (fix
the flagged New Password, resubmit, token already gone).

That contradicts Tiger_Form's own documented CSRF design ("the token isn't
single-use; it validates until it times out", CSRF_TIMEOUT = 2h, salt-shared across
related endpoints). Add Tiger_Form_Element_Hash, which arms the token on the seconds
TTL only and drops the hop limit; Tiger_Form uses it in place of the stock 'hash'.
One rendered token now survives a failed-then-corrected resubmit within the session.

Verified end-to-end on dev-com: two changePassword submits with the same rendered
token both return the field error, neither the CSRF error. Unit test asserts the
element arms on timeout (no hop), with a contrast test pinning the stock Zend default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@WebTigers
WebTigers merged commit 6685a99 into main Aug 7, 2026
12 checks passed
@WebTigers
WebTigers deleted the fix/csrf-hash-single-use branch August 7, 2026 17:10
WebTigers added a commit that referenced this pull request Aug 7, 2026
…on UX (#107)

Bug-fix release: the single-use CSRF token (#105) and the change-password form's
validation surfacing — localized inline field errors, the strength meter on the
profile page, a clear 'Passwords do not match', and a client-side match check (#106).
Also backfills the 0.51.0-beta CHANGELOG entry that was skipped.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant