Fix workspace ID transmission mismatch between API and client #194

Closed
opened 2026-02-02 17:26:15 +00:00 by jason.woltje · 0 comments
Owner

Problem

WorkspaceGuard checks header/URL param/body, but web client sends workspaceId in query string. GETs will be rejected with "Workspace ID is required."

Locations

  • apps/api/src/workspace/workspace.guard.ts:96 - Checks header/param/body only
  • apps/web/lib/api/tasks.ts:28 - Sends in query string
// Guard checks (in order)
1. X-Workspace-Id header
2. :workspaceId URL param
3. body.workspaceId

// Client sends
GET /api/tasks?workspaceId=xxx  // ❌ Not checked by guard

Impact

  • All GET requests with workspace filtering fail
  • Cannot list workspace-scoped resources
  • Multi-tenant isolation may be bypassed if guard is skipped

Acceptance Criteria

  • DECISION: Choose workspace ID transmission method
  • Update guard to accept chosen method
  • Update all client API calls
  • Document workspace ID requirement
  • Add tests for workspace isolation
  • Verify workspace access validation
// Client
fetch(url, {
  headers: { 'X-Workspace-Id': workspaceId }
});

// Guard (add to check list)
const workspaceId = req.headers['x-workspace-id'];

Option B: Query String

// Guard (add to check list)
const workspaceId = req.query.workspaceId || 
                    req.headers['x-workspace-id'] ||
                    req.params.workspaceId ||
                    req.body.workspaceId;

Testing

  • Test GET requests with workspace ID
  • Test POST/PATCH/DELETE with workspace ID
  • Test missing workspace ID rejected
  • Test cross-workspace access blocked

References

External security review findings (2026-02-02)

## Problem WorkspaceGuard checks header/URL param/body, but web client sends workspaceId in query string. GETs will be rejected with "Workspace ID is required." ## Locations - apps/api/src/workspace/workspace.guard.ts:96 - Checks header/param/body only - apps/web/lib/api/tasks.ts:28 - Sends in query string ```typescript // Guard checks (in order) 1. X-Workspace-Id header 2. :workspaceId URL param 3. body.workspaceId // Client sends GET /api/tasks?workspaceId=xxx // ❌ Not checked by guard ``` ## Impact - All GET requests with workspace filtering fail - Cannot list workspace-scoped resources - Multi-tenant isolation may be bypassed if guard is skipped ## Acceptance Criteria - [ ] **DECISION**: Choose workspace ID transmission method - [ ] Update guard to accept chosen method - [ ] Update all client API calls - [ ] Document workspace ID requirement - [ ] Add tests for workspace isolation - [ ] Verify workspace access validation ## Option A: Header (Recommended) ```typescript // Client fetch(url, { headers: { 'X-Workspace-Id': workspaceId } }); // Guard (add to check list) const workspaceId = req.headers['x-workspace-id']; ``` ## Option B: Query String ```typescript // Guard (add to check list) const workspaceId = req.query.workspaceId || req.headers['x-workspace-id'] || req.params.workspaceId || req.body.workspaceId; ``` ## Testing - [ ] Test GET requests with workspace ID - [ ] Test POST/PATCH/DELETE with workspace ID - [ ] Test missing workspace ID rejected - [ ] Test cross-workspace access blocked ## References External security review findings (2026-02-02)
jason.woltje added the webapiapip1 labels 2026-02-02 17:26:15 +00:00
jason.woltje added this to the M7.1-Remediation (0.0.8) milestone 2026-02-03 22:31:44 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaic/stack#194