fix(web): reject non-active machine tokens in auth middleware#210
Conversation
The Bearer-token auth path looked up the machine token by its raw value and admitted any row returned by getMachineTokenByToken, regardless of status. Tokens created by POST /api/machine-tokens are stored with status="pending" and only transitioned to "active" by the daemon calling /api/machine-tokens/activate. This meant a pending token authenticated as the owning user for any endpoint that did not require a workspace scope (e.g. /api/me, workspace creation, listing/creating additional machine tokens). Anyone who obtained the raw token between issuance and activation — clipboard sniffer, screen capture, browser extension, etc. — could impersonate the user during that window, and indefinitely if the token was never activated. Reject tokens whose status is not exactly "active" with the same 401 "invalid token" response used for unknown tokens. Existing test mocks updated to set status="active"; new test asserts pending tokens are refused and the wrapped handler is not invoked.
|
Review — looks correct and well-scoped. One follow-up to flag. The fix is right: Follow-up (not a blocker for this PR):
Both would admit a CI: I don't see any CI checks reported on this branch yet — please make sure the pipeline runs green before merge (the change is small but the auth path is sensitive). Otherwise this is solid and ready once CI is green. (Note: I only leave review comments — not posting a GitHub approval.) |
|
Thanks for the review. Opened #225 for the follow-up: deleted both On CI: my local checks pass ( |
The POST-then-auth test in machine-token.test.ts was relying on the same bug this PR closes. It created a fresh token (status="pending" by default) and immediately used it to authenticate, expecting 200. The buggy withAuth accepted any non-null row from getMachineTokenByToken, so the assertion passed by accident. With the fix in place, pending tokens correctly get 401 and the test surfaced the bug instead of hiding it. This mirrors the unit-test happy-path fix already in this PR: instead of omitting status (which masks the gap), the test now activates the token through /api/machine-tokens/activate and then asserts the activated token authenticates as expected. Same flow a real CLI user would take. Pattern is identical to the activation tests already in this file.
|
Took a look at the E2E failure. It's the same bug class this PR closes, in a different test.
Same shape as the unit-test happy-path mocks you already noted ("masked the bug by omitting status"). The E2E version was probably harder to spot locally because it requires Fixed in Local checks green at |
|
Re-review of the follow-up changes — looks good, ready once CI is green. Verified the two changes since my last pass:
Core fix unchanged and still correct ( Only open item: GitHub Actions CI still hasn't run on this branch (fork PR needs a maintainer to approve the workflow run). The change is small but the auth path is sensitive — please kick off CI before merge. (As always, I only leave review comments, no GitHub approval.) |
…-active-machine-tokens
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Daily-review ping (logic already reviewed above): no CI checks are currently reported on this branch and the merge state shows |
…-active-machine-tokens
|
Updated the branch to current
Local verification at E2E passed with 35 files and 237 tests. CLI integration passed with 14 tests and 1 skipped. GitHub created CI run |
fix(web): reject non-active machine tokens in auth middleware
Problem
The Bearer-token branch of withAuth admitted any row returned by getMachineTokenByToken, regardless of status. Pending tokens authenticated as the owning user.
Tokens created by POST /api/machine-tokens are stored with status="pending" and only transition to "active" when the daemon calls POST /api/machine-tokens/activate with a hostname and runtimes. Until that activation step, the raw token exists in the DB and is returned to the user (so the UI can show it / pipe it to the daemon).
Because auth.ts did not check mt.status, anyone holding the raw pending token — clipboard sniffer, screen capture, browser extension that scrapes the page, malicious helper running on the same machine — could call any authenticated route as that user. Endpoints that do not require workspaceId (e.g. /api/me, workspace creation, listing/creating additional machine tokens) work without activation. If a token was created and never activated, the window stays open indefinitely; once cached, the KV cache (TTL 900s) keeps the pending state hot.
After activation the token is correctly invalidated in the cache, so legitimate daemons keep working — this change only closes the pre-activation gap.
Fix
Tests
Run: pnpm --filter @alook/web test (1027 passing, +1 vs main).
Verification
Scope
Single-file behavioural change plus test. No schema, query, or API contract changes. No new dependencies.