Security Rules
Baseline security rules for Node.js HTTP services handling untrusted input, secrets, and outbound requests.
Search across all documentation pages
Baseline security rules for Node.js HTTP services handling untrusted input, secrets, and outbound requests.
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:
// 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:
JWT_SECRET fails before listen.| 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 |
z.infer<typeof Schema> types validated data.any on req.body - type comes from parse result only.req.body after partial validation - Extra fields passed to ORM. Fix: .strict() on Zod objects.* with credentials - Browser security violation and data leak. Fix: explicit origin allowlist.Object.assign(req.body) - Use structuredClone or schema parse output object.
| 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 |
Set { limit: "100kb" } plus schema validation; large payloads need streaming endpoints separately.
Minimum 32 bytes random; rotate with dual-key verify window.
Resolve and connect to IP in same guarded fetch wrapper; short timeout; block redirects to private IPs.
Use @fastify/type-provider-typebox or Zod compiler - validate before handler runs.
Enable globally with whitelist: true and forbidNonWhitelisted: true.
No. Combine audit, Socket, and secure coding rules; scanners miss app logic flaws.
Scan type/size; store outside web root; never execute uploaded content.
Runtime inject via orchestrator secrets; not build-args baked in image layers.
API gateway or @fastify/rate-limit / express-rate-limit on auth routes minimum.
Content-Security-Policy (API JSON may be minimal), X-Content-Type-Options, Strict-Transport-Security behind TLS.
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.
Reviewed by Chris St. John·Last updated Jul 19, 2026