Skip to main content

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 data payloads for create, update, and upsert operations.
  • Deeply-nested relational payload arguments (e.g. nested create, update, or upsert queries).

What the Guard does NOT strip:

  • where clauses (stripping fields here could alter query filters and cause unintended database changes).
  • select and include clauses (output formatting remains your responsibility).
  • orderBy, groupBy, and having clauses.

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.3ms estimate.

Want to run these benchmarks locally?
(Cloned repository required) Run npm run benchmark in the package root.

🛠️ Why It's So Fast

  1. 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 (where n = S + R fields). All field membership operations (key in data) compile to highly optimized O(1) V8 engine lookups.
  2. 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.
  3. 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.
  4. 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)