Skip to content
Development
Skill

/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.

From plugin
safe-agentic-workflow
39520 skills11 agents24 commands
Install
$ npx -y skills add bybren-llc/safe-agentic-workflow --skill rls-patterns --agent claude-code

How 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.md
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}}_dev

Common 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
Ships withsafe-agentic-workflow

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.

Get the whole plugin