AeThex-OS/docs/ORG_SCOPING_IMPLEMENTATION.md

4.8 KiB

Organization Scoping Security Implementation - Complete

Overview

All high-risk database queries have been secured with organization-level scoping to prevent cross-org data leakage.

Changes Implemented

1. Helper Layer (server/org-storage.ts)

- getOrgIdOrThrow(req): Extracts and validates org context
- orgEq(req): Returns { organization_id: orgId } filter
- orgScoped(table, req): Returns scoped Supabase query builder

2. Middleware Strengthening (server/org-middleware.ts)

  • Cache req.orgMemberId to avoid repeated DB lookups
  • requireOrgMember returns 400 (not 403) when org context missing
  • Clear error message: "Please select an organization (x-org-id header)"

3. Route Protection (server/routes.ts)

Sites (aethex_sites)

  • GET /api/sites: Scoped by orgScoped('aethex_sites', req)
  • POST /api/sites: Requires organization_id in insert
  • PATCH /api/sites/:id: Validates .eq('organization_id', orgId)
  • DELETE /api/sites/:id: Validates .eq('organization_id', orgId)

Opportunities (aethex_opportunities)

  • GET /api/opportunities: Optional ?org_id= query param for filtering
  • POST /api/opportunities: Requires organization_id
  • PATCH /api/opportunities/:id: Validates org ownership
  • DELETE /api/opportunities/:id: Validates org ownership

Events (aethex_events)

  • GET /api/events: Optional ?org_id= query param
  • POST /api/events: Requires organization_id
  • PATCH /api/events/:id: Validates org ownership
  • DELETE /api/events/:id: Validates org ownership

Projects (projects)

  • Already protected via multi-tenancy implementation
  • Uses assertProjectAccess for collaborator/owner checks

Files (files - in-memory)

  • Storage keyed by ${userId}:${orgId}
  • All CRUD operations require org context
  • Files can be linked to project_id for additional access control

4. Project Access Middleware (requireProjectAccess)

requireProjectAccess(minRole: 'owner' | 'admin' | 'contributor' | 'viewer')

Applied to:

  • GET /api/projects/:id (viewer)
  • GET /api/projects/:id/collaborators (contributor)
  • All project mutation routes via assertProjectAccess

Role hierarchy:

  • owner > admin > contributor > viewer
  • Org owners are implicit project owners
  • Project collaborators override org role

5. WebSocket Updates (server/websocket.ts)

  • Join org:<orgId> room on auth
  • Join user:<userId> room
  • Alerts emitted to org-specific rooms
  • Socket auth accepts orgId parameter

6. Audit Script (script/org-scope-audit.ts)

npm run audit:org-scope

Scans server/routes.ts for:

  • Supabase .from(table) calls
  • Missing .eq('organization_id', ...) for org-scoped tables
  • Exits non-zero if violations found

7. Integration Tests (server/org-scoping.test.ts)

npm run test:org-scope

Test coverage:

  • User B in orgB cannot list orgA sites
  • User B in orgB cannot update orgA opportunities
  • User B in orgB cannot delete orgA events
  • User A in orgA can access all orgA resources
  • Projects are scoped and isolated

Middleware Application Pattern

// Org-scoped routes
app.get("/api/sites", requireAuth, attachOrgContext, requireOrgMember, handler);
app.post("/api/sites", requireAuth, attachOrgContext, requireOrgMember, handler);

// Project-scoped routes
app.get("/api/projects/:id", requireAuth, requireProjectAccess('viewer'), handler);
app.patch("/api/projects/:id", requireAuth, requireProjectAccess('admin'), handler);

// Public routes (no org required)
app.get("/api/opportunities", handler); // Optional ?org_id= filter
app.get("/api/events", handler);

Exception Routes (No Org Scoping)

  • /api/auth/* - Authentication endpoints
  • /api/metrics - Public metrics
  • /api/directory/* - Public directory
  • /api/me/* - User-specific resources
  • Admin routes - Cross-org access with audit logging

Verification Checklist

  • All 15 high-risk gaps from audit closed
  • Sites CRUD protected
  • Opportunities CRUD protected
  • Events CRUD protected
  • Projects protected (via existing multi-tenancy)
  • Files protected (org-scoped storage keys)
  • WebSocket rooms org-scoped
  • Middleware caches membership
  • requireOrgMember returns 400 with clear error
  • Audit script detects violations
  • Integration tests verify isolation

Next Steps (Blocked Until This Is Complete)

  1. Security gaps closed - COMPLETE
  2. 🔜 Project Graph canonicalization (projects vs aethex_projects)
  3. 🔜 Revenue event primitive
  4. 🔜 Labs organization type
  5. 🔜 Cross-project identity primitive

Running Tests

# Run org-scoping audit
npm run audit:org-scope

# Run integration tests
npm run test:org-scope

# Full type check
npm run check

Status: All 15 high-risk security gaps closed. Production-ready for org-scoped operations.