Require fresh authorization and audit recap time logging #589

Closed
opened 2026-08-11 20:20:26 +00:00 by timmy · 0 comments
Owner

User problem

POST /api/v1/today/recaps/log-time can mutate up to 20 Gitea issues (480 hours total) using only an active dashboard session and CSRF token. Unlike merge, close, review, and access-management actions, it has no fresh-authorization boundary and no Security activity evidence. A captured active session can therefore write consequential false time records without a passkey/recovery-token check.

Evidence

  • src/main.py:1923-1987 saves the recap and sends sequential Gitea time mutations without _require_step_up.
  • src/main.py:278-286 has no recap-time action in StepUpAction.
  • frontend/session.js already supports a generic 428 → fresh authorization → exact-request retry flow.
  • Historical issues #341, #507, and #509 cover other consequential actions; #587 introduced recap time logging but not this authorization/audit boundary. All 297 historical issue titles and recent commits through 53bf89b were checked; none duplicates this behavior.

Acceptance criteria

  • Session + CSRF without a fresh grant returns 428 before recap persistence, journal reservation, or Gitea contact.
  • One grant is bound to the exact session ID, selected canonical identities, and corrected actual-minute values; payload changes, replay, cross-session use, and expiry fail before side effects.
  • One authorization covers the whole selected batch, using the existing passkey/recovery-token retry flow.
  • Each claimed time mutation reserves privacy-safe gitea_time_logged Security activity: confirmed success completes it, ambiguous outcome remains pending, and definite retryable no-write failure discards it.
  • Security activity renders a human-readable label and stores no recap text, token, or raw digest material.
  • Existing idempotency and retry/verify semantics remain intact; focused and full suites pass.
## User problem `POST /api/v1/today/recaps/log-time` can mutate up to 20 Gitea issues (480 hours total) using only an active dashboard session and CSRF token. Unlike merge, close, review, and access-management actions, it has no fresh-authorization boundary and no Security activity evidence. A captured active session can therefore write consequential false time records without a passkey/recovery-token check. ## Evidence - `src/main.py:1923-1987` saves the recap and sends sequential Gitea time mutations without `_require_step_up`. - `src/main.py:278-286` has no recap-time action in `StepUpAction`. - `frontend/session.js` already supports a generic 428 → fresh authorization → exact-request retry flow. - Historical issues #341, #507, and #509 cover other consequential actions; #587 introduced recap time logging but not this authorization/audit boundary. All 297 historical issue titles and recent commits through `53bf89b` were checked; none duplicates this behavior. ## Acceptance criteria - Session + CSRF without a fresh grant returns 428 before recap persistence, journal reservation, or Gitea contact. - One grant is bound to the exact session ID, selected canonical identities, and corrected actual-minute values; payload changes, replay, cross-session use, and expiry fail before side effects. - One authorization covers the whole selected batch, using the existing passkey/recovery-token retry flow. - Each claimed time mutation reserves privacy-safe `gitea_time_logged` Security activity: confirmed success completes it, ambiguous outcome remains pending, and definite retryable no-write failure discards it. - Security activity renders a human-readable label and stores no recap text, token, or raw digest material. - Existing idempotency and retry/verify semantics remain intact; focused and full suites pass.
timmy self-assigned this 2026-08-11 20:20:26 +00:00
Sign in to join this conversation.
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: stackchain/stackchain-dashboard#589
No description provided.