refactor: collapse CORS/preflight duplication in local_server#124
refactor: collapse CORS/preflight duplication in local_server#124piazzatron wants to merge 2 commits into
Conversation
Replaces the six per-endpoint Access-Control header dicts and four
preflight handlers with a single path→CorsPolicy map applied through
one aiohttp middleware. Converts ad-hoc TypedDict + manual `.get()`
param extraction into dacite-validated dataclasses that mirror the
camelCase wire contract, so missing or wrong-typed fields fail at the
boundary with a clear message.
The RPC `/` endpoint keeps its `{action, version, params}` contract;
the dispatch table becomes a module-level dict keyed by a dedicated
ActionHandler signature. No URL or response-shape changes.
`dacite` added to the vendored-deps list (already in requirements.txt).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @piazzatron's task —— View job Code Review - Bug and Vulnerability AnalysisTodo List:
Security FindingsAfter thorough analysis of the refactored local server code, I found 1 potential security issue and 1 edge case bug: 🔴 Security Issue: JWT Validation Bypass
🟡 Edge Case Bug: CORS Origin Header Handling
Positive Security Observations✅ CORS Implementation: Properly restricts origins and rejects unauthorized requests The refactor significantly improves code organization while maintaining security. The JWT validation issue should be addressed before merging. |
Code reviewFound 1 issue:
anki-smart-notes/src/local_server.py Lines 252 to 265 in dd372d4 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
The allowlist on /auth/callback and /subscription/refresh defended against an attacker hijacking the user's JWT from a malicious page — but the impact is just "attacker pays for their own usage," which isn't a threat model worth the policy-map indirection. Removes CorsPolicy, CORS_POLICIES, ALLOWED_ORIGINS, is_allowed, and the rejection branches. The middleware now does one thing: for the three browser-facing paths, answer preflights with 204 and decorate real responses with CORS headers (including the PNA header so private- network fetches actually succeed). Net: -71 lines across server + tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @piazzatron's task —— View job Bug and Security ReviewTodo List:
Security FindingsAfter analyzing the refactored local server code, I found 1 potential security issue: 🔴 JWT Validation Bypass in
Code Quality NotesThe refactor significantly improves code organization and maintainability. The CORS implementation properly restricts access, dacite provides strong parameter validation, and error handling doesn't leak sensitive information. The action dispatch system is clean and well-organized. |
Summary
/auth/callback,/subscription/refresh,/ping) share a single implementation of preflight handling and response decoration, including theAccess-Control-Allow-Private-Networkheader./auth/callbackand/subscription/refreshtosmart-notes.xyz. The CSRF attack this defended against — a malicious page overwriting the user's JWT — would only redirect API costs to the attacker's own account, which isn't a threat model worth the code. Any origin now gets echoed back and the request is served..get()-and-string-check param extraction with dacite-validated dataclasses that mirror the camelCase JSON wire contract. Missing or wrong-typed fields now raiseParamErrorat the boundary with a clear message (e.g."noteType is required")./RPCactiondispatch into a module-levelACTIONS: dict[str, ActionHandler]registry; each action becomes a top-levelasyncfunction. The external contract ({action, version, params}→{result, error}) is unchanged.Note: option/model string fields on the request dataclasses are deliberately
Optional[str]rather than theLiteralunions frommodels.py— a strict dacite check there would reject any newly-added model before the add-on updates.daciteis added to the vendored-deps list inscripts/build.sh(already inrequirements.txt).Verification
Automated
./scripts/build.sh check—ruff format --check,ruff check,pyright: all green.pytest— 96/96 tests pass, including the rewrittentest_local_server.pysuite.LocalServerin its background thread on127.0.0.1:8766and hit every endpoint withurllib. Confirmed:/ping,/auth/callback,/subscription/refreshall echo whateverOriginthe caller sent (including unknown origins),OPTIONSpreflights return 204 with the CORS + PNA headers, the RPC/endpoint remains free of CORS headers (intentional — not browser-facing), andserver.stop()shuts down cleanly.Not performed
/anki— headless Linux host with no Anki install. TheLocalServerlifecycle and all HTTP behavior was verified via the TCP smoke test above, but the plugin-in-Anki launch path was not exercised end-to-end.Claude session
/home/michael/development/anki-smart-notes-master/anki-smart-notes6fb6c964-c1aa-492e-9b9d-78696c8a5809