Node.js security is boundary design: minimize authority, validate untrusted data, encode for the destination, constrain resource use, protect dependencies and secrets, and make failure observable without exposing internals.
List assets, entry points, identities, dependencies, and failure impact before selecting controls. HTTP fields, files, environment values, queue messages, database rows, package output, and internal service responses may all be untrusted in their current context.
Authentication establishes identity; authorization decides whether that identity may perform this action on this resource. Validate authorization at the service boundary for every request, including guessed IDs and nested resources.
Allow expected types, ranges, lengths, formats, enum values, and object keys. Reject ambiguous coercion and bound nested structures. Validation does not replace output encoding: HTML, URL, SQL, shell, log, and header contexts have different rules.
Use parameterized database queries. Never compose shell commands from request text. Prevent header splitting, log injection, and open redirects by using structured APIs and allowlists. Return generic failure details while preserving internal evidence.
Do not join an upload name or URL path directly beneath a storage root and assume it stayed there. Generate server-owned identifiers where possible. Otherwise resolve the candidate, verify it remains inside the allowed root, reject absolute and traversal forms, and handle symbolic-link policy.
Limit upload bytes, file count, content types, decompressed size, and processing time. Store uploads outside executable or public roots, scan where the risk requires it, and publish only after complete validation.
import path from 'node:path';
function inside(root, name) {
const base = path.resolve(root);
const candidate = path.resolve(base, name);
if (candidate !== base && !candidate.startsWith(base + path.sep)) {
throw new Error('path escapes storage root');
}
return candidate;
}
console.log(inside('/srv/files', 'reports/july.txt'));
The comparison checks the normalized absolute path boundary; production code must also define symbolic-link behavior.
Prefer execFile or spawn with an executable and argument array. exec invokes a shell, so interpolated input can become commands, substitutions, redirects, or pipelines. Use allowlisted operations and fixed executable paths, minimal environment, timeouts, output limits, and least-privilege OS identity.
A direct argument can still be dangerous if the target program interprets it as a path, URL, option, or script. Validate domain meaning and use -- when the executable supports an option terminator. Never expose a generic command endpoint.
Commit the lockfile and use npm ci in automated builds. Review new package ownership, maintenance, install scripts, transitive size, release history, and requested capabilities. Run npm audit as one signal, then assess reachability, exploit conditions, fix availability, and regression risk.
Minimize dependencies for small tasks and remove unused packages. Build from a clean environment, protect publishing credentials, pin CI actions and base images according to policy, generate an inventory, and patch the supported runtime and operating system.
Load secrets from the deployment secret mechanism or environment, validate presence, and redact them from logs, errors, diagnostic reports, snapshots, and URLs. Rotate credentials and scope each identity to required resources only.
Use node:crypto high-level, established constructions. Passwords require a purpose-built password hashing function with salt and cost; random tokens require cryptographically secure randomBytes or randomUUID. Do not invent encryption, compare secret tags with ordinary string equality, or reuse nonces contrary to the chosen algorithm.
Node 24 supports the stable --permission model to restrict file reads and writes, child processes, workers, native addons, WASI, and related capabilities. Start with no optional authority, grant only required paths or operations, and verify denied behavior.
The permission model is defense in depth, not a sandbox against malicious code. Combine it with a non-root user, read-only file systems where possible, container or service isolation, network policy, secret scoping, and database least privilege.
node --permission --allow-fs-read=./config,./public server.js
The process receives read access only to the listed application paths; add each required capability deliberately.
Set request-body, header, URL, upload, response, decompression, concurrency, queue, and timeout limits. Rate-limit by an identity and operation appropriate to the threat, and protect expensive endpoints with quotas and cancellation.
Test traversal, injection strings, duplicate keys, malformed encodings, oversized and slow bodies, authorization across tenants, expired credentials, dependency failure, and denied permissions. Record security events with stable codes and request IDs without retaining secret payloads.
No. It reduces available capabilities as defense in depth, but it is not a complete sandbox. Use operating-system isolation and never execute untrusted code in the application process.
No. It identifies known advisories; teams must also assess reachability, package trust, update risk, runtime support, secrets, build integrity, and deployment controls.
Explore 500+ free tutorials across 20+ languages and frameworks.