Invariants Overview
Understanding the security invariants that SecurityChecks validates.
Security Invariants
SecurityChecks validates invariants - security properties that should always hold true in your codebase. When an invariant is violated, it indicates a potential security vulnerability.
P0 Invariants (Critical)
These issues can lead to immediate security breaches if exploited.
P0-MISSING-AUTHZ
Missing Authorization Check
Every endpoint that accesses or modifies user data must verify the requester has permission.
// Vulnerable
export async function GET(req: Request) {
const userId = req.params.id;
return await db.user.findUnique({ where: { id: userId } });
}
// Fixed
export async function GET(req: Request) {
const { userId: currentUserId } = await auth();
const targetUserId = req.params.id;
// Verify permission
if (currentUserId !== targetUserId && !isAdmin(currentUserId)) {
return new Response('Forbidden', { status: 403 });
}
return await db.user.findUnique({ where: { id: targetUserId } });
}
P0-MISSING-RATE-LIMIT
Missing Rate Limiting
Authentication and sensitive endpoints must be rate limited to prevent brute force attacks.
// Vulnerable
export async function POST(req: Request) {
const { email, password } = await req.json();
return await attemptLogin(email, password);
}
// Fixed
import { Ratelimit } from '@upstash/ratelimit';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(5, '1 m'),
});
export async function POST(req: Request) {
const ip = req.headers.get('x-forwarded-for');
const { success } = await ratelimit.limit(ip);
if (!success) {
return new Response('Too many requests', { status: 429 });
}
const { email, password } = await req.json();
return await attemptLogin(email, password);
}
P0-RACE-CONDITION
Race Condition in Transaction
Operations that read-modify-write must be atomic to prevent exploitation.
// Vulnerable
async function transfer(from: string, to: string, amount: number) {
const fromBalance = await getBalance(from);
if (fromBalance < amount) throw new Error('Insufficient funds');
await updateBalance(from, fromBalance - amount);
await updateBalance(to, await getBalance(to) + amount);
}
// Fixed
async function transfer(from: string, to: string, amount: number) {
await prisma.$transaction(async (tx) => {
const fromAccount = await tx.account.findUnique({
where: { id: from },
});
if (fromAccount.balance < amount) {
throw new Error('Insufficient funds');
}
await tx.account.update({
where: { id: from },
data: { balance: { decrement: amount } },
});
await tx.account.update({
where: { id: to },
data: { balance: { increment: amount } },
});
});
}
P0-MISSING-IDEMPOTENCY
Missing Idempotency in Webhook Handler
Webhook handlers must check for duplicate deliveries to prevent double-processing.
// Vulnerable
export async function POST(req: Request) {
const event = await req.json();
await processPayment(event.data);
}
// Fixed
export async function POST(req: Request) {
const event = await req.json();
const eventId = event.id;
// Check if already processed
const existing = await db.processedEvent.findUnique({
where: { id: eventId },
});
if (existing) {
return new Response('Already processed', { status: 200 });
}
// Mark as processing
await db.processedEvent.create({
data: { id: eventId, status: 'processing' },
});
await processPayment(event.data);
await db.processedEvent.update({
where: { id: eventId },
data: { status: 'completed' },
});
}
P0-MISSING-INPUT-VALIDATION
Missing Input Validation
All user input must be validated before use.
// Vulnerable
export async function POST(req: Request) {
const { amount, currency } = await req.json();
return await createPayment(amount, currency);
}
// Fixed
import { z } from 'zod';
const paymentSchema = z.object({
amount: z.number().positive().max(1000000),
currency: z.enum(['USD', 'EUR', 'GBP']),
});
export async function POST(req: Request) {
const body = await req.json();
const result = paymentSchema.safeParse(body);
if (!result.success) {
return Response.json(
{ error: 'Invalid input', details: result.error.flatten() },
{ status: 400 }
);
}
return await createPayment(result.data.amount, result.data.currency);
}
P1 Invariants (Important)
These issues should be fixed but may not lead to immediate exploitation.
P1-MISSING-CACHE-INVALIDATION
Permission caches must be invalidated when access is revoked.
P1-MISSING-AUDIT-LOG
Security-sensitive operations should be logged for compliance and incident response.
P1-HARDCODED-SECRET
Secrets must not be committed to source code.
P1-MISSING-ERROR-HANDLING
Errors must be handled gracefully without exposing internals.
P1-INSECURE-DEFAULT
Security features should be enabled by default.
Learn More
Use the CLI to get detailed explanations:
securitychecks explain P0-MISSING-AUTHZ