/rls-patterns
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.
$ npx -y skills add bybren-llc/safe-agentic-workflow --skill rls-patterns --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/rls-patterns
Context preview
The summary Claude sees to decide when to auto-load this skill.
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.
SKILL.md
rls-patterns.SKILL.mdname: 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
// ❌ 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
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
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
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
// 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:
// 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):
# 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}}_devCommon Patterns
API Route with User Context
// 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
// 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,Read more
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
// ❌ 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
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
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
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
// 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:
// 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):
# 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}}_devCommon Patterns
API Route with User Context
// 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
// 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,SAW — SAFe Agentic Workflow AI Agent Harness for Multi-Agent Team Workflows Built on SAFe methodology (Scaled Agile Framework), adapted for AI agent teams (Now With AI-DLC!) Works for any team with repeatable processes: Software, Marketing, Research, Legal, Operations.
Other skills on safe-agentic-workflow.
- /agent-coordination
Agent assignment matrix, blocker escalation, and TDM coordination patterns. Use when assigning work to specialists, managing blockers, or coordinating multi-agent workflows.
Open skill - /api-patterns
API route implementation patterns with RLS, Zod validation, and error handling. Use when creating API routes, implementing endpoints, or adding server-side validation.
Open skill - /confluence-docs
Documentation templates for ADRs, runbooks, and architecture docs. Use when creating architectural decision records, operational runbooks, or technical documentation.
Open skill - /deployment-sop
Deployment workflows, pre-deploy validation, and smoke testing patterns. Use when deploying to staging or production, running smoke tests, or validating deployments.
Open skill - /frontend-patterns
Frontend patterns for Next.js App Router, Clerk auth, shadcn/Radix UI, and PostHog analytics. Use when building UI components, creating pages, implementing auth flows, or adding analytics events. Ensures consistent UX patterns and accessibility standards.
Open skill - /git-advanced
Advanced git operations including rebase, bisect, cherry-pick, and conflict resolution. Use when rebasing branches, debugging with bisect, cherry-picking commits, or resolving complex merge conflicts.
Open skill

