fix(usage): reject invalid explicit dates in usage RPC date parsing#93745
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 16, 2026, 3:14 PM ET / 19:14 UTC. Summary PR surface: Source +58, Tests +44. Total +102 across 2 files. Reproducibility: yes. Source inspection plus a Node Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Merge the shared parser and handler-boundary rejection once maintainers accept the narrow Do we have a high-confidence way to reproduce the issue? Yes. Source inspection plus a Node Is this the best way to solve the issue? Yes. The shared parser plus RPC-boundary rejection is the narrow maintainable fix because both affected handlers already share the date parser/range path, and leaving the protocol schema pattern-only avoids unnecessary protocol churn. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against c6b1921a91bf. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +58, Tests +44. Total +102 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
usage.cost and sessions.usage accepted shape-valid but impossible dates such as 2026-02-30: parseDateParts validated only the YYYY-MM-DD regex, so Date.* silently rolled them over (2026-02-30 -> 2026-03-02) and the RPC returned cost/usage for the wrong day. Out-of-range parts now fail a UTC round-trip check, and an explicitly provided unparseable date (bad format or impossible calendar date) returns INVALID_REQUEST instead of silently falling back to the default range. Absent/valid dates are unchanged. [AI-assisted]
8036497 to
ead2ae8
Compare
|
Maintainer verification complete. Fresh structured review caught one real edge in the contributor patch: explicit non-string date values could still silently fall through to the default usage range. I repaired that on the contributor branch, rebased it onto current Verified:
The handlers now reject impossible calendar dates and any explicitly supplied unparseable date value before querying usage; blank/null dates remain absent. Ready to squash merge. |
…penclaw#93745) * fix(usage): reject invalid explicit dates in usage RPC date parsing usage.cost and sessions.usage accepted shape-valid but impossible dates such as 2026-02-30: parseDateParts validated only the YYYY-MM-DD regex, so Date.* silently rolled them over (2026-02-30 -> 2026-03-02) and the RPC returned cost/usage for the wrong day. Out-of-range parts now fail a UTC round-trip check, and an explicitly provided unparseable date (bad format or impossible calendar date) returns INVALID_REQUEST instead of silently falling back to the default range. Absent/valid dates are unchanged. [AI-assisted] * fix(usage): reject non-string explicit dates --------- Co-authored-by: Vincent Koc <[email protected]>
…penclaw#93745) * fix(usage): reject invalid explicit dates in usage RPC date parsing usage.cost and sessions.usage accepted shape-valid but impossible dates such as 2026-02-30: parseDateParts validated only the YYYY-MM-DD regex, so Date.* silently rolled them over (2026-02-30 -> 2026-03-02) and the RPC returned cost/usage for the wrong day. Out-of-range parts now fail a UTC round-trip check, and an explicitly provided unparseable date (bad format or impossible calendar date) returns INVALID_REQUEST instead of silently falling back to the default range. Absent/valid dates are unchanged. [AI-assisted] * fix(usage): reject non-string explicit dates --------- Co-authored-by: Vincent Koc <[email protected]>
Summary
usage.costandsessions.usageaccepted shape-valid but impossible dates(e.g.
2026-02-30).parseDatePartsvalidated only theYYYY-MM-DDregex, soDate.*silently rolled them over (2026-02-30→2026-03-02) and the RPCreturned cost/usage for the wrong day with
success: true.This PR:
parseDatePartsvia a UTC round-trip probe (kills the silent rollover; also catches the JS 2-digit-year remap).INVALID_REQUESTfromusage.cost/sessions.usagewhen an explicitly providedstartDate/endDateis unparseable (bad format or impossible calendar date), instead of silently falling back to the default 30-day range. This completes the fail-closed contractsessions.usagealready had for malformed-format dates (its schema rejects format but not out-of-range), and adds it tousage.cost, which had no date validation.Out of scope: the
gateway-protocolschema (pattern-only) is unchanged — validation lives at the single choke point both RPCs share. Two incidental oxfmt-0.52 line-wraps on pre-existing >100-char lines in the touched files.Linked context
No linked issue — self-found bug. Closes: none.
Real behavior proof
usage.cost/sessions.usagesilently returned cost/usage for the wrong day when given an impossible-but-shape-valid date (e.g.2026-02-30→2026-03-02); now rejected withINVALID_REQUEST.upstream/main3630ce6cbb, Node 22.22.3, isolated git worktree; drove the realusage.costRPC handler and the exported date helpers viatsx(no mocks of the parse/handler logic), importing the actualsrc/gateway/server-methods/usage.tsfrom currentmain(before) and this branch (after).tsxscript invokingusageHandlers["usage.cost"]({ params: { startDate: "2026-02-30" }, context: { getRuntimeConfig: () => ({}) } })plus thefindInvalidExplicitDate/parseDateToMshelpers, against both the unfixedmaincopy and this branch.INVALID_REQUESTinstead of returning a successful response for the wrong/default range; valid dates and the real leap day2024-02-29are unaffected.Tests and validation
node scripts/run-vitest.mjs src/gateway/server-methods/usage.test.ts— 38/38 pass (added: out-of-range rejection + leap-day guard,findInvalidExplicitDateunit test, handler-levelINVALID_REQUESTtest).tsgo:core+tsgo:core:test,oxlint,oxfmt --check— clean.codex review— clean, 0 findings (Claude implemented → Codex reviewed).Risk checklist
INVALID_REQUESTinstead of a silent default-range success.usage.costpreviously had no date validation, so this adds a fail-closed path. Mitigated by rejecting only explicitly provided unparseable dates — absent/valid dates behave exactly as before — and covered by the handler-level test.AI-assisted: planned and implemented by Claude (Opus 4.8), reviewed by Codex (gpt-5.5).