Session codec max-age still 30 days: bring securecookie codecs in line with the 7-day cap #108
Reference in New Issue
Block a user
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?
Follow-up from the independent review of PR #105 (#66). Not blocking there, because #105 adds a stricter server-side check that shadows this — but the underlying skew remains and should be closed at the source.
The skew
internal/session/session.go:148assignsstore.Optionswholesale. Ingorilla/sessions@v1.4.0,NewCookieStoresets the codec max-age to 30 days viacs.MaxAge(...), and assigningstore.Optionsdirectly never touchesCodecs. So the securecookie layer will still decode a cookie up to 30 days old; only the newexpiredcheck in #105 rejects it.Verified by execution during the #105 review with a throwaway probe: the app's pattern still decodes a cookie past its
Options.MaxAge(returnstrue), whilestore.MaxAge(1)correctly rejects it (false).Why it matters even though #105 shadows it
Before #105 this was a genuine hole — the 7-day absolute cap was enforced only by the browser, and a retained cookie would have been honoured by the server for up to 30 days. #105 closes the exploitable path, but leaves two layers disagreeing about the same policy. That is exactly the shape that breaks later: anyone refactoring the
expiredcheck, or adding a second entry point that decodes a session without going throughIsAuthenticated, silently reopens a 30-day window. Defence in depth belongs at the codec too.Fix: replace the wholesale
store.Optionsassignment withstore.MaxAge(secondsPerDay * sessionMaxAgeDays), which setsOptions.MaxAgeand propagates to every codec. Check whether the same pattern appears anywhere else a store is constructed.Done when: a test proves codec-level rejection of a cookie older than the 7-day cap — i.e. it fails if the
expiredcheck from #105 is removed, so it is genuinely testing the codec and not the new server check.Also from the same review
idleRefreshDivisorfrom 10 to 5 leaves the full suite green (verified). The PR documents "expires up to 10% early, never late" as a deliberate tradeoff, so the constant deserves a test that fails if it drifts — otherwise the documented guarantee is prose only.internal/middleware/middleware.go:217reuses the outererrrather than scoping it inside theif. Harmless, inconsistent with the surrounding style.*fakeClockthey were handed, andfakeClockis duplicated across two test packages. Fold into a shared helper if a third use appears.created_at/last_seen), and it says the timeout is disabled by setting0where the code disables on any non-positive value.Definition of done
make checkgreen via the repo's own entrypoints;.golangci.ymluntouched.Implemented in #132 (base
next).The store is now built by
newStore, which applies the cap withstore.MaxAge(...)soOptions.MaxAgeand every codec agree; it is the only place a store is constructed. Two codec tests decode a re-stamped cookie an hour inside and an hour outside the cap throughSession.Getalone, so no server-side check participates: reverting the fix fails the rejection test both withexpiredpresent and withexpiredstubbed out, and stubbingexpiredalone leaves both tests passing. The refresh divisor is pinned two-sidedly and fails at 5 and at 20. Items 2 and 4 fixed; item 3 declined for want of a third use. Verified withmake check(exit 0) anddocker build --no-cache-filter=lint,builderwith the lint and test stages executed rather than cached. Details and the full mutation table are in the PR.Correction to the original text of this comment: it claimed the item 2 rename also caught the log line reporting the outer nil error rather than the save error. That was wrong. The pre-change code reassigned
errfromSaveon the line immediately above the check, so that log site always carried the save error. Item 2 is scoping hygiene with no behaviour change, exactly as the issue describes it.clawbot referenced this issue2026-08-17 23:50:11 +02:00