prisma-firewall
v0.1.1
Published
A security firewall middleware for Prisma. Block dangerous queries before they hit your database!
Downloads
254
Maintainers
Readme
prisma-firewall 🔒
A security firewall for Prisma. Blocks dangerous queries before they hit your database.
The Problem
Prisma makes database access easy. Too easy sometimes.
A single line like this wipes your entire users table with no confirmation and no warning:
await prisma.user.deleteMany()Or this returns every user in your database — passwords, tokens, sensitive fields — to whoever called it:
await prisma.user.findMany()These aren't hypothetical risks. They happen from bugs, tired developers, bad inputs, and compromised dependencies. prisma-firewall sits between your code and your database and stops them before they cause damage.
Installation
npm install prisma-firewallSetup
Wrap your Prisma client with withFirewall:
import { PrismaClient } from '@prisma/client'
import { withFirewall } from 'prisma-firewall'
const baseClient = new PrismaClient()
export const prisma = withFirewall(baseClient, {
rules: {
blockDeleteWithoutWhere: true,
blockUpdateWithoutWhere: true,
blockDeleteSingleWithoutId: true,
blockUpdateSingleWithoutId: true,
blockFindManyWithoutLimit: true,
maxRows: 1000,
blockDangerousRawPatterns: true,
blockOperatorInjection: true,
auditLog: true
}
})That's it. Use prisma exactly as you normally would. The firewall runs silently in the background.
What It Blocks
Delete without a where clause
// BLOCKED — deletes every row in the table
await prisma.user.deleteMany()
// ALLOWED
await prisma.user.deleteMany({ where: { active: false } })Update without a where clause
// BLOCKED — updates every row in the table
await prisma.user.updateMany({ data: { role: 'ADMIN' } })
// ALLOWED
await prisma.user.updateMany({
where: { trialExpired: true },
data: { role: 'FREE' }
})Delete without an id
// BLOCKED
await prisma.user.delete({ where: { email: '[email protected]' } })
// ALLOWED
await prisma.user.delete({ where: { id: '123' } })findMany with no limit
// Automatically capped to 1000 rows
await prisma.user.findMany()
// Your own limit is respected if under maxRows
await prisma.user.findMany({ take: 50 })Sensitive fields in select
// password and token are silently stripped before the query runs
await prisma.user.findMany({
select: {
id: true,
email: true,
password: true,
token: true
}
})Dangerous raw queries
// BLOCKED
await prisma.$executeRaw`DROP TABLE users`
await prisma.$executeRaw`TRUNCATE TABLE orders`Operator injection
A less known but real attack. Instead of sending a plain password string, an attacker sends this:
{
"email": "[email protected]",
"password": { "not": "" }
}Your code looks harmless:
const user = await prisma.user.findFirst({
where: { email, password }
})But Prisma accepts { "not": "" } as a valid operator, meaning find the user where password is NOT empty. Authentication bypassed without knowing the password.
blockOperatorInjection catches this before it reaches the database.
All Rules
| Rule | Type | Description |
|------|------|-------------|
| blockDeleteWithoutWhere | boolean | Blocks deleteMany with no where clause |
| blockUpdateWithoutWhere | boolean | Blocks updateMany with no where clause |
| blockDeleteSingleWithoutId | boolean | Blocks delete with no id in where |
| blockUpdateSingleWithoutId | boolean | Blocks update with no id in where |
| blockFindManyWithoutLimit | boolean | Caps findMany results to maxRows |
| maxRows | number | Maximum rows findMany can return (default 1000) |
| blockRawQueries | boolean | Blocks all $queryRaw and $executeRaw |
| blockDangerousRawPatterns | boolean | Blocks DROP, TRUNCATE, ALTER in raw queries |
| blockOperatorInjection | boolean | Blocks Prisma operator injection attacks |
| blockedFields | string[] | Fields to strip from select clauses |
| rateLimit | object | Rate limits specific operations per minute |
| auditLog | boolean | Logs every query with timestamp |
Default blocked fields
If you don't provide blockedFields, these are stripped automatically:
password, passwordHash, token, accessToken, refreshToken,
secret, apiKey, privateKey, ssn, creditCard, cardNumber, cvvRate Limiting
withFirewall(baseClient, {
rules: {
rateLimit: {
operations: ['deleteMany', 'updateMany'],
maxPerMinute: 10
}
}
})Custom Alerting
By default the firewall logs to the console. Override onBlock to send alerts anywhere:
withFirewall(baseClient, {
rules: { ... },
onBlock: (details) => {
fetch('your-slack-webhook', {
method: 'POST',
body: JSON.stringify({
text: `Blocked ${details.operation} on ${details.model}: ${details.reason}`
})
})
}
})Custom Audit Logging
Override onAudit to send audit logs to your own monitoring service:
withFirewall(baseClient, {
rules: { auditLog: true },
onAudit: (details) => {
myLogger.log(details)
}
})What a Blocked Query Looks Like
PRISMA FIREWALL — QUERY BLOCKED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Model: User
Operation: deleteMany
Reason: deleteMany without a where clause will delete every row in the table
Rule: BLOCK_DELETE_WITHOUT_WHERE
Timestamp: 2026-04-09T03:26:20.640Z
Args: {}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━Who Is This For
- Solo developers who want a safety net on destructive queries
- Small teams where anyone can push a migration or script
- Production apps where a single bad query can cause real damage
- Anyone building on Prisma who wants peace of mind
License
MIT
