Fix F-16: revoke old system token before issuing new one

- ui/handlers_accounts.go (handleIssueSystemToken): call
  GetSystemToken before issuing; if one exists, call
  RevokeToken(existing.JTI, "rotated") before TrackToken
  and SetSystemToken for the new token; mirrors the pattern
  in REST handleTokenIssue and gRPC IssueServiceToken
- db/db_test.go: TestSystemTokenRotationRevokesOld verifies
  the full rotation flow: old JTI revoked with reason
  "rotated", new JTI tracked and active, GetSystemToken
  returns the new JTI
- AUDIT.md: mark F-16 as fixed
Security: without this fix an old system token remained valid
  after rotation until its natural expiry, giving a leaked or
  stolen old token extra lifetime. With the revocation the old
  JTI is immediately marked in token_revocation so any validator
  checking revocation status rejects it.
This commit is contained in:
2026-03-11 20:34:57 -07:00
parent d42f51fc83
commit 005e734842
3 changed files with 79 additions and 1 deletions

View File

@@ -349,6 +349,14 @@ func (u *UIServer) handleIssueSystemToken(w http.ResponseWriter, r *http.Request
return
}
// Security: revoke the previous system token before issuing a new one (F-16).
// This matches the pattern in the REST handleTokenIssue and gRPC IssueServiceToken
// so that old tokens do not remain valid after rotation.
existing, err := u.db.GetSystemToken(acct.ID)
if err == nil {
_ = u.db.RevokeToken(existing.JTI, "rotated")
}
expiry := u.cfg.ServiceExpiry()
tokenStr, claims, err := u.issueToken(acct.UUID, roles, expiry)
if err != nil {