fix(draft): fail closed when the submission-token expires_at is unparseable#1713
Conversation
…seable processSubmitDraft gated the contributor content-PR flow on `new Date(tokenRow.expires_at).getTime() < Date.now()`. expires_at is a TEXT NOT NULL column, so SQLite does not enforce ISO-8601 shape; a corrupted/partial write or any future path storing a non-ISO value makes .getTime() return NaN, and `NaN < Date.now()` is false -- the token was then treated as not expired and the submission proceeded with a possibly stale user token. Mirror src/auth/security.ts' fail-closed session-expiry guard: compute expiresAtMs with Date.parse, treat a non-finite value as expired, so an unparseable expires_at short-circuits to the existing token_unavailable arm and never proceeds with the token. No behavior change for any well-formed row. Closes JSONbored#1712
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-06-29 18:31:47 UTC
⏸️ Suggested Action - Manual Review
Review summary Nits — 7 non-blocking
Review context
Contributor next steps
Signal definitions
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1713 +/- ##
=======================================
Coverage 95.58% 95.58%
=======================================
Files 204 204
Lines 22313 22314 +1
Branches 8065 8066 +1
=======================================
+ Hits 21328 21329 +1
Misses 408 408
Partials 577 577
🚀 New features to boost your workflow:
|
Summary
processSubmitDraft(src/services/draft.ts) gates the contributor content-PR flow on the stored user token's expiry:expires_atis aTEXT NOT NULLcolumn (migration0048_drafts.sql), so SQLite does not enforce ISO-8601 shape. A corrupted/partial write, a schema migration, or any future path that stores a non-ISO value makesnew Date(...).getTime()returnNaN, andNaN < Date.now()isfalse-- the token is then treated as not expired, and the submission proceeds with a possibly-stale/invalid user token.Fix
Mirror
src/auth/security.ts's fail-closed session-expiry guard, which carries an explicit comment naming this exact failure mode:Compute
expiresAtMswithDate.parseand treat a non-finite value as expired, so an unparseableexpires_atshort-circuits to the existingtoken_unavailablearm and never proceeds with the token. No behavior change for any well-formed row.Scope
type(scope): short summaryConventional Commit format.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
git diff --checknpm run actionlintnpm run db:migrations:checknpm run typechecknpm run test:coverage--test/unit/draft.test.ts95/95 pass; the changed lines are covered (the existing expired/consumed/no-token tests plus a new malformed-expires_atregression that fails closed totoken_unavailable).Targeted run:
npx vitest run test/unit/draft.test.ts # 95/95 passedSafety
expires_atnow fails closed instead of authenticating). No CORS/cookie/session-shape change; the stored token is only ever consumed when its expiry parses to a finite, future timestamp.UI Evidence
Not applicable -- backend token-expiry guard with no visible UI, frontend, docs, or extension surface.
Closes #1712