Skip to content

auth: drop token-status reads that race a revocation - #466

Open
Alpovka wants to merge 1 commit into
vxcontrol:mainfrom
Alpovka:fix/tokencache-revoke-race
Open

Alpovka wants to merge 1 commit into
vxcontrol:mainfrom
Alpovka:fix/tokencache-revoke-race

Conversation

@Alpovka

@Alpovka Alpovka commented Oct 20, 2026 •

Copy link
Copy Markdown

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_64

Test Steps

  1. cd backend && go test ./pkg/server/auth/
  2. cd backend && go test ./pkg/server/auth/ -race
  3. Revert only api_token_cache.go and re-run the new test to see it fail on the old code.

Test Results

  • go test ./pkg/server/auth/ -race passes (full package).
  • New test TestAPITokenCache_GetStatus_DoesNotCacheAReadOverlappingAnInvalidation forces a revocation between the read and the store with a gorm query callback (same technique as the existing UserCache test). It fails on the current code (serves active) and passes with the fix (serves revoked).
  • gofmt, go vet, and golangci-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 fmt and go 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.

TokenCache cached a token status read even when a revocation landed
between the DB read and the cache write. The revoked token then
authenticated for the whole 5-minute TTL.

UserCache already guards this with a storeMu + epoch check. Apply the
same guard to TokenCache: capture the epoch before the DB read, write
through storeIfCurrent, and bump the epoch on every invalidation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADYQncLu3WWTRt5cfBbRau
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant