Implement RLS context helpers consistently across all services #195

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

Problem

RLS context helpers exist (db-context.ts) but are not used by services. Security relies on manual workspace filtering everywhere. If RLS policies are enabled, queries will fail. If they aren't, one missed filter becomes a cross-tenant data leak.

Locations

  • apps/api/src/prisma/db-context.ts:75 - withUserContext helper exists
  • apps/api/src/tasks/tasks.service.ts:56 - Manual filtering: where: { workspaceId }
// Helper exists but unused
export function withUserContext(userId: string, workspaceId: string) {
  return { userId, workspaceId };
}

// Services do manual filtering
const tasks = await this.prisma.task.findMany({
  where: { workspaceId },  // Manual - easy to forget
});

Questions (URGENT - Need Answers)

  1. Are Postgres RLS policies enabled in production?
  2. If yes, why aren't services using withUserContext?
  3. If no, when will RLS be enabled?

Risk

If RLS policies are enabled without withUserContext:

  • All queries will fail (no context set)

If RLS policies are NOT enabled:

  • One missed workspaceId filter = data leak
  • No defense-in-depth

Acceptance Criteria

  • DECISION: Are RLS policies enabled?
  • Audit all service queries for workspace filtering
  • Implement Prisma middleware to set RLS context
  • OR centralize withUserContext in request-scoped Prisma
  • Add tests for cross-tenant isolation
  • Document RLS usage patterns
  • Add lint rule to require workspace filtering
prisma.$use(async (params, next) => {
  const context = getRequestContext();
  await prisma.$executeRaw`
    SET LOCAL app.user_id = ${context.userId};
    SET LOCAL app.workspace_id = ${context.workspaceId};
  `;
  return next(params);
});

Option B: Request-scoped Prisma

// Inject Prisma with context pre-set
@Injectable({ scope: Scope.REQUEST })
export class PrismaService {
  async onModuleInit() {
    await this.setContext(this.userId, this.workspaceId);
  }
}

Testing

  • Test RLS policies enforce isolation
  • Test queries fail without context (if RLS enabled)
  • Test cross-workspace queries blocked
  • Verify performance impact acceptable

References

External security review findings (2026-02-02)

## Problem RLS context helpers exist (db-context.ts) but are not used by services. Security relies on manual workspace filtering everywhere. If RLS policies are enabled, queries will fail. If they aren't, one missed filter becomes a cross-tenant data leak. ## Locations - apps/api/src/prisma/db-context.ts:75 - withUserContext helper exists - apps/api/src/tasks/tasks.service.ts:56 - Manual filtering: where: { workspaceId } ```typescript // Helper exists but unused export function withUserContext(userId: string, workspaceId: string) { return { userId, workspaceId }; } // Services do manual filtering const tasks = await this.prisma.task.findMany({ where: { workspaceId }, // Manual - easy to forget }); ``` ## Questions (URGENT - Need Answers) 1. Are Postgres RLS policies enabled in production? 2. If yes, why aren't services using withUserContext? 3. If no, when will RLS be enabled? ## Risk If RLS policies are enabled without withUserContext: - All queries will fail (no context set) If RLS policies are NOT enabled: - One missed workspaceId filter = data leak - No defense-in-depth ## Acceptance Criteria - [ ] **DECISION**: Are RLS policies enabled? - [ ] Audit all service queries for workspace filtering - [ ] Implement Prisma middleware to set RLS context - [ ] OR centralize withUserContext in request-scoped Prisma - [ ] Add tests for cross-tenant isolation - [ ] Document RLS usage patterns - [ ] Add lint rule to require workspace filtering ## Option A: Prisma Middleware (Recommended) ```typescript prisma.$use(async (params, next) => { const context = getRequestContext(); await prisma.$executeRaw` SET LOCAL app.user_id = ${context.userId}; SET LOCAL app.workspace_id = ${context.workspaceId}; `; return next(params); }); ``` ## Option B: Request-scoped Prisma ```typescript // Inject Prisma with context pre-set @Injectable({ scope: Scope.REQUEST }) export class PrismaService { async onModuleInit() { await this.setContext(this.userId, this.workspaceId); } } ``` ## Testing - [ ] Test RLS policies enforce isolation - [ ] Test queries fail without context (if RLS enabled) - [ ] Test cross-workspace queries blocked - [ ] Verify performance impact acceptable ## References External security review findings (2026-02-02)
jason.woltje added the databasesecuritydatabaseapiapip1 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#195