rls-patterns
bybren-llc/safe-agentic-workflow/.claude/skills/rls-patterns/SKILL.md
Row Level Security patterns for database operations. Use when writing Prisma/database code, creating API routes that access data, or implementing webhooks. Enforces withUserContext, withAdminContext, or withSystemContext helpers. NEVER use direct prisma calls.
Skill405 starsChanged 3 months ago
What's in it
- RLS Patterns Skill
- Purpose
- When This Skill Applies
- Critical Rules
- NEVER Do This
- ALWAYS Do This
- Context Helper Reference
- withUserContext(prisma, userId, callback)
- withAdminContext(prisma, userId, callback)
- withSystemContext(prisma, contextType, callback)
- Admin Pages: Force Dynamic Rendering
- Protected Tables
- User Data Tables (User Isolation)
- Admin/System Tables (Role-Based)
- Testing Requirements
- Common Patterns
- API Route with User Context
- Webhook Handler with System Context
- Authoritative References
Tools it asks for
- Read
- Grep
- Glob
---
name: rls-patterns
description: Row Level Security patterns for database operations. Use when writing Prisma/database code, creating API routes that access data, or implementing webhooks. Enforces withUserContext, withAdminContext, or withSystemContext helpers. NEVER use direct prisma calls.
user-invocable: false
allowed-tools: Read, Grep, Glob
---
# RLS Patterns Skill
## Purpose
Enforce Row Level Security (RLS) patterns for all database operations. This skill ensures data isolation and prevents cross-user data access at the database level.
## When This Skill Applies
Invoke this skill when:
- Writing any Prisma database query
- Creating or modifying API routes that access the database
- Implementing webhook handlers that write to the database
- Working with user data, payments, subscriptions, or enrollments
- Accessing admin-only tables (disputes, webhook_events)
## Critical Rules
### NEVER Do This
```typescript
// ❌ FORBIDDEN - Direct Prisma calls bypass RLS
const user = await prisma.user.findUnique({ where: { user_id } });
// ❌ FORBIDDEN - No context set
const payments = await prisma.payments.findMany();
```
**ESLint will block direct Prisma calls.** See `eslint.config.mjs` for enforcement rules.
### ALWAYS Do This
```typescript
import {
withUserContext,
withAdminContext,
withSystemContext,
} from "@/lib/rls-context";
// ✅ CORRECT - User context for user operations
const user = await withUserContext(prisma, userId, async (client) => {
return client.user.findUnique({ where: { user_id: userId } });
});
// ✅ CORRECT - Admin context for admin operations
const webhooks = await withAdminContext(prisma, userId, async (client) => {
return client.webhook_events.findMany();
});
// ✅ CORRECT - System context for webhooks/background tasks
const event = await withSystemContext(prisma, "webhook", async (client) => {
return client.webhook_events.create({ data: eventData });
});
```
## Context Helper Reference
### `withUserContext(prisma, userId, callback)`
**Use for**: All user-facing operations
- User profile access
- Payment history
- Subscription management
- Course enrollments
```typescript
const payments = await withUserContext(prisma, userId, async (client) => {
return client.payments.findMany({ where: { user_id: userId } });
});
```
### `withAdminContext(prisma, userId, callback)`
**Use for**: Admin-only operations (requires admin role in `user_roles` table)
- Viewing all webhook events
- Managing disputes
- Accessing payment failures
```typescript
const disputes = await withAdminContext(prisma, adminUserId, async (client) => {
return client.disputes.findMany();
});
```
### `withSystemContext(prisma, contextType, callback)`
**Use for**: Webhooks and background jobs
- Stripe webhook handlers
- Clerk webhook handlers
- Background job processing
```typescript
// Stripe webhook handler
await withSystemContext(prisma, "webhook", async (client) => {
await client.payments.create({ data: paymentData });
});
```
## Admin Pages: Force Dynamic Rendering
**CRITICAL**: Admin pages using RLS queries MUST force runtime rendering:
```typescript
// app/admin/some-page/page.tsx
import { withAdminContext } from "@/lib/rls-context";
import { prisma } from "@/lib/prisma";
// REQUIRED - RLS context unavailable at build time
export const dynamic = "force-dynamic";
async function getAdminData() {
return await withAdminContext(prisma, userId, async (client) => {
return client.someTable.findMany();
});
}
```
Without `export const dynamic = 'force-dynamic'`, Next.js will try to pre-render at build time, causing "permission denied" errors.
## Protected Tables
### User Data Tables (User Isolation)
| Table | Policy Type | Access |
| ------------------- | -------------- | ---------------------- |
| `user` | User isolation | Own data only |
| `payments` | User isolation | Own payments only |
| `subscriptions` | User isolation | Own subscriptions only |
| `invoices` | User isolation | Own invoices only |
| `course_enrollment` | User isolation | Own enrollments only |
### Admin/System Tables (Role-Based)
| Table | Policy Type | Access |
| --------------------- | ------------ | ------------------------ |
| `webhook_events` | Admin+System | Admins and webhooks only |
| `disputes` | Admin only | Admins only |
| `payment_failures` | Admin only | Admins only |
| `trial_notifications` | Admin+System | Admins and system only |
## Testing Requirements
Always test with `{{PROJECT}}_app_user` role (not `{{PROJECT}}_user` superuser):
```bash
# Basic RLS functionality test
node scripts/test-rls-phase3-simple.js
# Comprehensive security validation
cat scripts/rls-phase4-final-validation.sql | \
docker exec -i {{PROJECT_NAME}}-postgres-1 psql -U {{PROJECT}}_app_user -d {{PROJECT}}_dev
```
## Common Patterns
### API Route with User Context
```typescript
// app/api/user/payments/route.ts
import { NextResponse } from "next/server";
import { requireAuth } from "@/lib/auth";
import { withUserContext } from "@/lib/rls-context";
import { prisma } from "@/lib/prisma";
export async function GET() {
const { userId } = await requireAuth();
const payments = await withUserContext(prisma, userId, async (client) => {
return client.payments.findMany({
where: { user_id: userId },
orderBy: { created_at: "desc" },
});
});
return NextResponse.json(payments);
}
```
### Webhook Handler with System Context
```typescript
// app/api/webhooks/stripe/route.ts
import { withSystemContext } from "@/lib/rls-context";
import { prisma } from "@/lib/prisma";
export async function POST(req: Request) {
// Verify webhook signature first...
await withSystemContext(prisma, "webhook", async (client) => {
await client.webhook_events.create({
data: {
event_type: event.type,
payload: event.data,
processed_at: new Date(),
},
});
});
return new Response("OK", { status: 200 });
}
```
## Authoritative References
- **Implementation Guide**: `docs/database/RLS_IMPLEMENTATION_GUIDE.md`
- **Policy Catalog**: `docs/database/RLS_POLICY_CATALOG.md`
- **Migration SOP**: `docs/database/RLS_DATABASE_MIGRATION_SOP.md`
- **ESLint Rules**: `eslint.config.mjs` (direct Prisma call enforcement)
- **RLS Context**: `lib/rls-context.ts`
More agent context in bybren-llc/safe-agentic-workflow
60 other files this repository gives its agents.
AGENTS.md
CLAUDE.md
Cursor rule
- .cursor/rules/00-core-principles.mdc
- .cursor/rules/01-git-workflow.mdc
- .cursor/rules/02-pattern-discovery.mdc
- .cursor/rules/03-safe-ai-dlc.mdc
- .cursor/rules/04-knowledge-vault.mdc
- .cursor/rules/10-backend-python.mdc
- .cursor/rules/11-frontend-react.mdc
- .cursor/rules/12-database-sql.mdc
- .cursor/rules/13-testing.mdc
- .cursor/rules/14-spec-creation.mdc
- .cursor/rules/15-deployment.mdc
- .cursor/rules/16-stripe-payments.mdc
- .cursor/rules/20-agent-architect.mdc
- .cursor/rules/21-agent-backend.mdc
- .cursor/rules/22-agent-qas.mdc
- .cursor/rules/23-agent-security.mdc
- .cursor/rules/30-background-agents.mdc
- .cursor/rules/31-mcp-integration.mdc
- .cursor/rules/README.md
Skill
- agent-coordination.agents/skills/agent-coordination/SKILL.md
- api-patterns.agents/skills/api-patterns/SKILL.md
- confluence-docs.agents/skills/confluence-docs/SKILL.md
- deployment-sop.agents/skills/deployment-sop/SKILL.md
- frontend-patterns.agents/skills/frontend-patterns/SKILL.md
- git-advanced.agents/skills/git-advanced/SKILL.md
- linear-sop.agents/skills/linear-sop/SKILL.md
- migration-patterns.agents/skills/migration-patterns/SKILL.md
- orchestration-patterns.agents/skills/orchestration-patterns/SKILL.md
- pattern-discovery.agents/skills/pattern-discovery/SKILL.md
- release-patterns.agents/skills/release-patterns/SKILL.md
- rls-patterns.agents/skills/rls-patterns/SKILL.md
- safe-ai-dlc.agents/skills/safe-ai-dlc/SKILL.md
- safe-workflow.agents/skills/safe-workflow/SKILL.md
- security-audit.agents/skills/security-audit/SKILL.md
- spec-creation.agents/skills/spec-creation/SKILL.md
- stripe-patterns.agents/skills/stripe-patterns/SKILL.md
- team-coordination.agents/skills/team-coordination/SKILL.md
- testing-patterns.agents/skills/testing-patterns/SKILL.md
- vault-sync.agents/skills/vault-sync/SKILL.md
- agent-coordination.claude/skills/agent-coordination/SKILL.md
- api-patterns.claude/skills/api-patterns/SKILL.md
- confluence-docs.claude/skills/confluence-docs/SKILL.md
- deployment-sop.claude/skills/deployment-sop/SKILL.md
- frontend-patterns.claude/skills/frontend-patterns/SKILL.md
- git-advanced.claude/skills/git-advanced/SKILL.md
- linear-sop.claude/skills/linear-sop/SKILL.md
- migration-patterns.claude/skills/migration-patterns/SKILL.md
- orchestration-patterns.claude/skills/orchestration-patterns/SKILL.md
- pattern-discovery.claude/skills/pattern-discovery/SKILL.md
- release-patterns.claude/skills/release-patterns/SKILL.md
- safe-ai-dlc.claude/skills/safe-ai-dlc/SKILL.md
- safe-workflow.claude/skills/safe-workflow/SKILL.md
- security-audit.claude/skills/security-audit/SKILL.md
- spec-creation.claude/skills/spec-creation/SKILL.md
- stripe-patterns.claude/skills/stripe-patterns/SKILL.md
- team-coordination.claude/skills/team-coordination/SKILL.md
- testing-patterns.claude/skills/testing-patterns/SKILL.md
- vault-sync.claude/skills/vault-sync/SKILL.md
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
No reports yet. Be the first to say whether it worked.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool public_context_discussion, action report. How to connect one.

