Repository: cppa-cursor-browser
Assignee: Brad @bradjin8
Severity: High
Problem
The module-level global _workspace_path_override in utils/workspace_path.py is the project's only shared mutable state. It is written by the POST /api/set-workspace route handler and read by every subsequent request's call to resolve_workspace_path(). No lock, no atomic operation, and no threading check guards the mutation. Under any threaded WSGI deployment (gunicorn with --threads, waitress, etc.), concurrent requests to set-workspace and any data-reading endpoint race on server-wide state, potentially serving one user's workspace data to another user's request.
Acceptance Criteria
Implementation Notes
The cleanest fix is to move workspace path into Flask's request-scoped g object or session, eliminating the shared mutable global entirely. This would make the application inherently safe under threaded deployment without any locking overhead. If that refactoring is too invasive for this sprint, a threading.Lock wrapping the read/write of _workspace_path_override is the minimum viable fix. Either way, the race is currently latent (single-threaded Werkzeug default) but becomes exploitable the moment anyone deploys with --threads. The fix here also unblocks item 7 (threading/deployment documentation) — you can't document safety guarantees until the code actually provides them.
References
- Eval finding: test 13
- Cluster:
workspace-path-race-condition
- Related files:
utils/workspace_path.py, api/workspaces.py (the set-workspace route handler)
- Compounds: COMPOUND-B (race condition + missing documentation), COMPOUND-E (undocumented API + race condition)
Repository: cppa-cursor-browser
Assignee: Brad @bradjin8
Severity: High
Problem
The module-level global
_workspace_path_overrideinutils/workspace_path.pyis the project's only shared mutable state. It is written by thePOST /api/set-workspaceroute handler and read by every subsequent request's call toresolve_workspace_path(). No lock, no atomic operation, and no threading check guards the mutation. Under any threaded WSGI deployment (gunicorn with--threads, waitress, etc.), concurrent requests toset-workspaceand any data-reading endpoint race on server-wide state, potentially serving one user's workspace data to another user's request.Acceptance Criteria
_workspace_path_overrideis protected by athreading.Lock(or equivalent synchronization primitive) for both reads and writesg, session, or request context)set-workspace+resolve_workspace_pathcalls underthreadingto verify no data races_workspace_path_overrideImplementation Notes
The cleanest fix is to move workspace path into Flask's request-scoped
gobject or session, eliminating the shared mutable global entirely. This would make the application inherently safe under threaded deployment without any locking overhead. If that refactoring is too invasive for this sprint, athreading.Lockwrapping the read/write of_workspace_path_overrideis the minimum viable fix. Either way, the race is currently latent (single-threaded Werkzeug default) but becomes exploitable the moment anyone deploys with--threads. The fix here also unblocks item 7 (threading/deployment documentation) — you can't document safety guarantees until the code actually provides them.References
workspace-path-race-conditionutils/workspace_path.py,api/workspaces.py(theset-workspaceroute handler)