Security Rules
Baseline security rules for Node.js HTTP services handling untrusted input, secrets, and outbound requests.
Recipe
Quick-reference recipe card - copy-paste ready.
import { z } from "zod";
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(120),
});
app.post("/users", (req, res) => {
const body = CreateUserSchema.safeParse(req.body);
if (!body.success) return res.status(400).json({ error: body.error.flatten() });
// ...
});When to reach for this:
- Every route accepting JSON, query params, or headers.
- Services fetching user-supplied URLs.
- Any code touching API keys or database credentials.
Working Example
// src/config/env.ts
import { z } from "zod";
const EnvSchema = z.object({
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
PORT: z.coerce.number().default(3000),
});
export const env = EnvSchema.parse(process.env);// src/security/ssrf.ts
import { lookup } from "node:dns/promises";
const BLOCKED = [/^127\./, /^10\./, /^192\.168\./, /^169\.254\./, /^0\./];
export async function assertSafeUrl(raw: string) {
const url = new URL(raw);
if (!["http:", "https:"].includes(url.protocol)) throw new Error("protocol blocked");
const { address } = await lookup(url.hostname);
if (BLOCKED.some((re) => re.test(address))) throw new Error("ssrf blocked");
}// src/routes/preview.ts
app.post("/preview", async (req, res) => {
const { targetUrl } = z.object({ targetUrl: z.string().url() }).parse(req.body);
await assertSafeUrl(targetUrl);
const html = await fetch(targetUrl, { signal: AbortSignal.timeout(3_000) });
res.send(await html.text());
});What this demonstrates:
- Env validated at startup - missing
JWT_SECRETfails before listen. - SSRF guard resolves DNS and blocks private IPs.
- Zod validates input shape before outbound fetch.
Deep Dive
How It Works
- Validate at trust boundary (HTTP edge), not deep in repositories.
- Secrets never logged; redact in structured logger serializers.
- Use helmet or framework plugins for security headers.
- Rate limit auth and expensive endpoints at edge or middleware.
Security Rules Table
| Rule | Enforcement |
|---|---|
| Validate all inputs | Zod/schema at route |
| Secrets in env/vault only | gitignore, secret scanners |
| SSRF guard outbound fetch | DNS + IP blocklist |
| Helmet/CORS explicit config | middleware |
npm audit high+ blocked | CI |
TypeScript Notes
z.infer<typeof Schema>types validated data.- Avoid
anyonreq.body- type comes from parse result only.
Gotchas
- Trusting
req.bodyafter partial validation - Extra fields passed to ORM. Fix:.strict()on Zod objects. - Logging full request bodies - PII in logs. Fix: log field allowlist only.
- JWT in query strings - Leaks via logs and Referer. Fix: Authorization header only.
- Default CORS
*with credentials - Browser security violation and data leak. Fix: explicit origin allowlist. - Prototype pollution via
Object.assign(req.body)- UsestructuredCloneor schema parse output object.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| joi / valibot | Team standard | Already standardized on Zod |
| WAF at edge | DDoS, OWASP rules | Replacing input validation |
| mTLS internal | Service mesh zero trust | Public REST only |
FAQs
Is express.json() limit enough?
Set { limit: "100kb" } plus schema validation; large payloads need streaming endpoints separately.
How long JWT secret?
Minimum 32 bytes random; rotate with dual-key verify window.
SSRF DNS rebinding?
Resolve and connect to IP in same guarded fetch wrapper; short timeout; block redirects to private IPs.
Fastify validation?
Use @fastify/type-provider-typebox or Zod compiler - validate before handler runs.
NestJS ValidationPipe?
Enable globally with whitelist: true and forbidNonWhitelisted: true.
Dependency scanning enough?
No. Combine audit, Socket, and secure coding rules; scanners miss app logic flaws.
How to handle file uploads?
Scan type/size; store outside web root; never execute uploaded content.
Secrets in Docker?
Runtime inject via orchestrator secrets; not build-args baked in image layers.
Rate limiting where?
API gateway or @fastify/rate-limit / express-rate-limit on auth routes minimum.
Security headers minimum set?
Content-Security-Policy (API JSON may be minimal), X-Content-Type-Options, Strict-Transport-Security behind TLS.
Related
- Node Project Rules Checklist - rules 6-10
- Dependency Rules - supply chain
- API Rules - error exposure boundaries
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.