Fix auth token exposure via externally_connectable (#246)#255
Fix auth token exposure via externally_connectable (#246)#255
Conversation
- Restrict externally_connectable localhost to port 3462 only
- GET_AUTH_STATE: return { authenticated, user } instead of raw JWT token
- DASHBOARD_LOGIN: validate token with server before accepting
- Dashboard: remove extension token auto-sync (use own OAuth flow)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jessie-coco
left a comment
There was a problem hiding this comment.
Codex Review R1: CLEAN — 0 P1 + 0 P2 + 1 P3
Reviewed all 4 changed files in full (not just diff hunks). Security fix is well-implemented.
What was checked
Correctness: GET_AUTH_STATE correctly strips the JWT and returns only { authenticated, authUser: { name, email } }. DASHBOARD_LOGIN validates the token server-side via /api/v2/auth/me before calling setAuthState, and uses the server-verified user object instead of trusting message.user. The AUTH_STATE_CHANGED broadcast and { success: true } return only execute on the happy path — early returns in the error/catch branches prevent false-positive notifications.
Security: externally_connectable tightened from *://localhost/* to *://localhost:3462/* — correct. Token no longer exposed to external callers. Server-side token validation prevents arbitrary token injection via DASHBOARD_LOGIN. Dashboard's syncLoginToExtension() (push direction: dashboard → extension) is the correct flow and now validated.
Integration: Dashboard removed extension→dashboard token auto-sync but still pushes its own OAuth token to the extension via syncLoginToExtension. This means the extension stays in sync when the user logs in through the dashboard. The getAuthFromExtension function in api.js is updated to return { user } only. No callers broken.
Edge cases: Empty/null authUser handled via the authenticated boolean gate. getConfig().serverUrl trailing slash stripped consistently. Fetch errors caught and returned as { error } messages.
P3 (non-blocking)
- P3: Dead import —
getAuthFromExtensionis still imported indashboard/src/main.js(line 18) but no longer called anywhere in the file after the auto-sync removal. Harmless but worth cleaning up.
LGTM. Approving.
Summary
externally_connectablelocalhost pattern from*://localhost/*to*://localhost:3462/*, preventing arbitrary localhost pages from messaging the extension{ authenticated: boolean, authUser: { name, email } }instead/api/v2/auth/me) before accepting it into extension storage, preventing arbitrary token injectionTest plan
GET_AUTH_STATEunchanged)localhost:3462, verify OAuth login flow works independentlyDASHBOARD_LOGINwith a valid token succeeds and syncs to extensionDASHBOARD_LOGINwith an invalid/expired token is rejectedlocalhost:9999cannot send messages to the extensionGET_AUTH_STATEfrom dashboard no longer leaks the JWT tokenCloses #246
🤖 Generated with Claude Code