Runtime Protection
Prisma Guard features a lightweight runtime Prisma Client extension that automatically strips unmapped/extra fields from your database query payloads.
This protects your database queries from malicious input parameter injections or deprecated parameters without needing manual mapping.
Setup
To enable runtime protection, simply mount the prismaGuard extension onto your Prisma Client instance:
import { PrismaClient } from "@prisma/client";
import { prismaGuard } from "@explita/prisma-guard";
const prisma = new PrismaClient().$extends(prismaGuard());
export default prisma;
Field-Stripping Mechanism
Once the extension is registered, query payloads are parsed before they reach your database. Any fields not explicitly defined in the Prisma model schema are silently stripped:
// Any extra fields passed to 'data' will be silently stripped
await prisma.user.create({
data: {
email: "user@example.com",
poisonField: "will be removed", // Stripped at runtime
},
});
Understanding boundaries
The runtime guard acts as a filter, not a validator. It does not perform validation constraints (such as checking string lengths or formats); it only filters keys.
What the Guard Strips:
- Unmapped properties in query
datapayloads forcreate,update, andupsertoperations. - Deeply-nested relational payload arguments (e.g. nested
create,update, orupsertqueries).
What the Guard does NOT strip:
whereclauses (stripping fields here could alter query filters and cause unintended database changes).selectandincludeclauses (output formatting remains your responsibility).orderBy,groupBy, andhavingclauses.
Core Prisma Validation
The guard runs before Prisma's query validation. Prisma will still throw errors for:
- Field name typos (e.g.
emial: "test@test.com"triggers an unknown field error). - Missing required database columns.
- Basic database type mismatches.
// ✅ Extra field: Stripped. Operation succeeds.
await prisma.user.create({
data: { email: "test@test.com", oldField: "deprecated" },
});
// ❌ Typo: Prisma client throws a validation error.
await prisma.user.create({
data: { emial: "test@test.com" },
}); // Error: Unknown argument `emial`
Debugging
If you are unsure why an input field is missing or why Prisma throws error messages during development, enable debug mode in your configuration file:
// prisma-guard.config.js
export default defineConfig({
debug: true,
});
When active, Prisma Guard outputs descriptive logs to the terminal console:
[prisma-guard] Stripping extra field "poisonField" from model "User"
Performance
The runtime guard is designed for ultra-low latency, introducing virtually zero friction into your database request path.
📊 Real Benchmark Results
Running the stripExtraFields routine over 100,000 recursive sanitization iterations on a payload with nested writes:
- Total duration:
221.26ms - Average per query:
2.21 microseconds(0.0022ms)
💡 Under-promise, Over-deliver: The actual sanitization runs 135x faster than our conservative
~0.3msestimate.
Want to run these benchmarks locally?
(Cloned repository required) Runnpm run benchmarkin the package root.
🛠️ Why It's So Fast
- Strict
O(n)Complexity: Instead of dynamically traversing arbitrary keys from raw user input, the sanitizer loops strictly over the predefined fields in the model schema (wheren = S + Rfields). All field membership operations (key in data) compile to highly optimizedO(1)V8 engine lookups. - Memoized Whitelists: The schema definitions (scalars and relations) are parsed and cached in memory exactly once during the application's bootstrap phase. At query runtime, there is zero filesystem I/O or JSON parsing overhead.
- Hot-Path Early Exits: If a field is a scalar value (e.g., a string, number, or boolean), the algorithm exits immediately (
typeof data !== "object"). This prevents unnecessary nested recursion checks. - Call Stack Protection: Recursion depth is hard-limited to 10 levels to prevent call stack exhaustion or resource exhaustion from circular references.
💡 Real-World Impact
If a database query takes 10-50ms, adding 0.0022ms introduces virtually 0% overhead.
Benchmark Environment:
- CPU: Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz
- Node.js: v22.14.0
- OS: Microsoft Windows 11 Home (64-bit)
- Payload: Complex nested object with 2 levels of nested write operations
Worst-Case Performance
Even under heavy loads, maximum recursion depth (10 levels), and models with 100+ fields:
- Max observed overhead: ~15-20 microseconds
- Still negligible compared to network latency (10-50ms)
Memory Footprint
- Cache Size: ~2-5KB per model in-memory
- No Per-Query Allocations: Reuses structure signatures; no memory churn
- GC Pressure: Virtually zero (no garbage collection overhead)