Skip to content

Fix IDOR: derive identity from verified token, not client-supplied userId#9

Open
aisraelov wants to merge 1 commit into
mainfrom
dev
Open

Fix IDOR: derive identity from verified token, not client-supplied userId#9
aisraelov wants to merge 1 commit into
mainfrom
dev

Conversation

@aisraelov

@aisraelov aisraelov commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary

Fixes the same IDOR class as the Livestreamer disclosure, found across several endpoints in Notes. The app trusted client-supplied user identifiers, letting any caller read/write another user's private notes and live voice transcripts.

Issues fixed

Severity Endpoint Problem Fix
🔴 Critical WS /ws/sync Upgraded on a raw ?userId= with no auth — the primary sync channel for all notes/transcripts Requires a verified MentraOS frontend token (?aos_frontend_token= or Bearer); userId derived from it; 401 otherwise
🔴 High GET /api/search, /search/sentences, /search/backfill-status Explicit || query("userId") auth bypass Identity now only from requireAuth(c) (verified middleware)
🟡 Medium POST /api/test/simulate-* Injected into any user's session, unauthenticated, shipped to prod 404 unless NODE_ENV=development
🧹 Cleanup useSSE/api/events?userId= Dead hook hitting an unimplemented route (misleading insecure pattern) Removed (was unused)

How identity is verified

A frontend token is userId:sha256(userId + sha256(apiKey)). The WS upgrade splits out the claimed userId, regenerates the token via the SDK's own generateFrontendToken(userId, apiKey), and constant-time compares. Only a token signed with our API key validates — no network round-trip per connect.

Frontend: WS sync and search authenticate with the verified frontendToken from useMentraAuth(). useSynced reads the token internally, so the other 12 call sites are unchanged.

Verification

  • Token logic unit-checked against the real SDK (@mentra/sdk@3.0.0-hono.8): valid accepted; forged userId, tampered hash, wrong-API-key, and empty/malformed all rejected.
  • Typecheck: 37 pre-existing errors before and after — zero net-new.
  • Server mints the token with the same generateFrontendToken my verifier uses, and @mentra/react always sets frontendToken alongside userId — so no regression in when the app connects.

⚠️ Do not merge yet — for manual testing first.

Operational note (pre-existing, not in this PR)

COOKIE_SECRET falls back to MENTRAOS_API_KEY if unset (index.ts). Worth setting a distinct COOKIE_SECRET in prod so session-cookie signing isn't tied to the API key.

🤖 Generated with Claude Code


Note

High Risk
Security-critical auth changes on the primary sync channel and search APIs; incorrect deployment or token handling could break connections or leave residual bypasses.

Overview
Closes IDOR paths where callers could target another user’s notes and live transcripts by supplying a userId.

Backend: /ws/sync no longer upgrades on ?userId=; it requires a MentraOS frontend token (aos_frontend_token or Bearer), verifies it with generateFrontendToken + constant-time compare, and derives userId from that. Search routes (/search, /search/backfill-status, /search/sentences) drop the userId query fallback and use requireAuth(c) only. Dev transcript simulation endpoints return 404 outside NODE_ENV=development.

Frontend: useSynced connects with frontendToken from useMentraAuth() and waits for the token before opening the socket. Search sends Authorization: Bearer instead of userId query params. Unused useSSE (insecure userId on /api/events) is removed.

Reviewed by Cursor Bugbot for commit 9c5fb3e. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes an IDOR by deriving user identity only from a verified MentraOS frontend token across WebSocket sync and search. Prevents cross-user reads/writes of private notes and transcripts.

  • Bug Fixes

    • /ws/sync now requires a verified token via ?aos_frontend_token= or Authorization: Bearer <frontendToken>; userId is derived from the token; 401 otherwise. Verification uses generateFrontendToken from @mentra/sdk with a constant-time compare.
    • /api/search, /search/sentences, and /search/backfill-status no longer accept ?userId=; identity comes only from requireAuth(c).
    • Dev test endpoints (/api/test/simulate-*) return 404 unless NODE_ENV=development.
    • Removed dead useSSE hook that called an unimplemented /api/events?userId= route.
    • Frontend: useSynced authenticates WS with frontendToken from @mentra/react; SearchPage sends Authorization: Bearer <frontendToken>.
  • Migration

    • Clients must authenticate search with Authorization: Bearer <frontendToken> and WS with ?aos_frontend_token= or the same header.
    • No changes needed for existing useSynced call sites; it reads frontendToken internally.

Written for commit 9c5fb3e. Summary will update on new commits.

Review in cubic

…erId

The app trusted client-supplied user identifiers in several places, allowing
any caller to read/write another user's private notes and transcripts (IDOR).

- WS sync (/ws/sync): upgraded on a raw ?userId= query param with no auth.
  Now requires a MentraOS frontend token (?aos_frontend_token= or Bearer),
  verified by regenerating the SDK token and constant-time comparing; the
  userId is derived from the verified token. Rejects with 401 otherwise.
- Search endpoints (/api/search, /search/sentences, /search/backfill-status):
  removed the explicit "|| query userId" auth bypass; identity now comes only
  from the verified auth middleware (requireAuth).
- Dev test endpoints (/api/test/simulate-*): returned data for an arbitrary
  user with no auth and shipped to prod. Now 404 unless NODE_ENV=development.
- Removed dead useSSE hook that hit an unimplemented /api/events?userId= route
  (misleading insecure pattern; nothing rendered it).

Frontend: WS sync and search now authenticate with the verified frontendToken
from useMentraAuth() instead of sending a raw userId. useSynced reads the token
internally so call sites are unchanged.

Verified: token verification logic unit-checked against the real SDK (valid
tokens accepted; forged/tampered/wrong-key/empty rejected). No new type errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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