Description of the Change
Problem
TokenCache is the per-request check that an API token is still active. GetStatus reads the token row from the database, then writes the result into a 5-minute cache. The write is unconditional, so a revocation that lands between the read and the write is lost: a request reads status = active; an admin revokes the token (UpdateToken writes revoked, then calls Invalidate); the in-flight request then writes active back into the cache. For the next 5 minutes every request serves the cached active, so the revoked token keeps authenticating until the entry expires. DeleteToken has the same shape. The user-status/hash check on the same auth path is not affected, because it goes through UserCache, which already guards this race.
Solution
Apply the storeMu + epoch guard that UserCache already uses. GetStatus captures epoch before the DB read and writes both cache entries (found and not-found) through a new storeIfCurrent, which stores only if epoch is unchanged. Invalidate, InvalidateUser, and InvalidateAll bump epoch under the lock. The window drops from the full 5-minute TTL to the single in-flight request.
No related issue.
Type of Change
- 🐛 Bug fix (non-breaking change which fixes an issue)
- 🛡️ Security update
Areas Affected
- Core Services (Frontend UI/Backend API)
Testing and Verification
Test Configuration
Backend Go unit tests only; no running stack, keys, or deployment needed.
PentAGI Version: n/a (backend unit test)
Go: 1.26.5
Host OS: Linux x86_64Test Steps
cd backend && go test ./pkg/server/auth/cd backend && go test ./pkg/server/auth/ -race- Revert only
api_token_cache.goand re-run the new test to see it fail on the old code.
Test Results
go test ./pkg/server/auth/ -racepasses (full package).- New test
TestAPITokenCache_GetStatus_DoesNotCacheAReadOverlappingAnInvalidationforces a revocation between the read and the store with a gorm query callback (same technique as the existingUserCachetest). It fails on the current code (servesactive) and passes with the fix (servesrevoked). gofmt,go vet, andgolangci-lint(repo config) clean.
Security Considerations
Fixes a revocation-bypass window: a revoked or deleted API token could keep authenticating for up to the 5-minute cache TTL. No new dependencies, no permission or schema changes. The change improves the security model and adds no new sensitive data handling.
Performance Impact
Negligible. The cache-hit path is unchanged. On a cache miss the change adds one mutex-guarded read of a counter before the DB query and one mutex-guarded compare on write; no new allocations. Matches UserCache.
Documentation Updates
- (none required)
Deployment Notes
None. No new environment variables, configuration, or migrations.
Checklist
Code Quality
- My code follows the project's coding standards
- I have added tests to cover my changes
- All new and existing tests pass
- I have run
go fmtandgo vet(for Go code) - I have run
pnpm run lint(no frontend change)
Security
- I have considered security implications
- Changes maintain or improve the security model
- Sensitive information has been properly handled
Compatibility
- Changes are backward compatible
- Breaking changes are clearly marked and documented (n/a — none)
- Dependencies are properly updated (n/a — none added)
Documentation
- Comments are added for non-obvious code
Additional Notes
This mirrors the existing storeMu/epoch guard in backend/pkg/server/auth/users_cache.go; reviewing the two side by side is the quickest check.