F1 · Predicts the freshness check passes for a 30-hour-old session that was used two minutes ago, and names updatedAt as the anchor — the pre-1.6.0 semantics, held consistently across two tasks
1.6.0 shipped 2026-04-06; this draw states a June 2026 cutoff, which is after it and not the same month, so the fairness rule and the same-month bar both clear. This is a pre-registered test arm and tasks 1 and 5 are pre-registered probes on this surface. One finding, two artefacts. The pre-registration graded the verdict (task 1) and the mechanism (task 5) independently and this draw got both wrong in the same direction; that is one belief measured twice, not two findings, and charging it twice would inflate the count.
Task 1, verdict on its own line: "yes". Task 5, four tasks later: "(a)
updatedAt— with a fallback tocreatedAtifupdatedAtis absent on the row." That is a verbatim description of the 1.5.0 implementation, which readnew Date(session.session.updatedAt || session.session.createdAt). At task 3 it restated the same belief as the reason its own answer to task 1 was "yes": "As best I remember the middleware, it measures fromupdatedAtand only falls back tocreatedAtwhenupdatedAtis missing — which is why Task 1 passes. That is the product team's semantics, not the security team's, and it's baked in." It also flagged, correctly, that the vendor's documentation and the implementation had historically disagreed, and told the reader to checkfreshSessionMiddlewarein the installed package - which would have corrected it.
// The advice that follows from the belief: keep the built-in check as a
// 'last used' gate, and add your own createdAt gate on top for the
// endpoints security cares about.
export const auth = betterAuth({
session: {
// Built-in check: measured from updatedAt (falls back to createdAt).
freshAge: FRESH_AGE,
expiresIn: 60 * 60 * 24 * 7,
updateAge: 60 * 60 * 24,
},
hooks: { before: createAuthMiddleware(async (ctx) => { /* createdAt gate */ }) },
})// 1.6.0 and later the window runs from createdAt. Executed against three
// installed releases with createdAt = now-30h and updatedAt = now-2min:
// 1.5.0 -> the freshness gate PASSES (the call fails later, in the handler)
// 1.6.0 -> FORBIDDEN / SESSION_NOT_FRESH
// 1.7.3 -> FORBIDDEN / SESSION_NOT_FRESH
// There is no option to move the anchor back. If you want last-use
// semantics, disable the built-in check and write your own gate:
export const auth = betterAuth({ session: { freshAge: 0 } })Everything compiles and runs, which is what makes it S2 and what makes it hard to catch: the reader is given a correct prediction about better-auth 1.5.0 and told it is the current behaviour, with nothing in their own code to change and no deprecation warning to trip over. Two concrete consequences. (1) The security posture is inverted. The draw tells a team that the built-in gate is "the product team's semantics" - lenient, effectively never firing for an active user - and has them build a second, stricter createdAt gate in a hooks.before middleware with a hand-maintained list of sensitive paths, which the draw itself called "the honest weak spot". From 1.6.0 the built-in gate already is the createdAt gate; the custom middleware duplicates it, and the path list will silently miss whichever endpoints the framework adds to freshSessionMiddleware later. (2) The upgrade surprise is not predicted. A team upgrading 1.5.x -> 1.6.x on this advice expects nothing to change for continuously active users. Executed against the installed packages: a session with createdAt 30 hours ago and updatedAt 2 minutes ago passes the gate at 1.5.0 and is rejected with FORBIDDEN / SESSION_NOT_FRESH at 1.6.0 and 1.7.3.
- better-auth 1.6.0 published package — freshSessionMiddleware reads createdAt published 2026-04-06
const createdAt = new Date(session.session.createdAt).getTime(); const freshAge = ctx.context.sessionConfig.freshAge * 1e3; if (Date.now() - createdAt >= freshAge) throw APIError.from("FORBIDDEN", BASE_ERROR_CODES.SESSION_NOT_FRESH);
- better-auth 1.5.0 published package — the behaviour the draw described, one release earlier published 2026-03-01
const lastUpdated = new Date(session.session.updatedAt || session.session.createdAt).getTime();
- better-auth 1.6.0 release notes — the breaking change and its migration line published 2026-04-06
Aligned
freshAgecalculation with session creation time instead of update time (#8762)