nodejs_owasp_top10 20 Q&As

Node.js OWASP Top10 FAQ & Answers

20 expert Node.js OWASP Top10 answers researched from official documentation. Every answer cites authoritative sources you can verify.

unknown

20 questions
A

Use parameterized queries with ORMs (Sequelize, TypeORM) or query builders (Knex). NEVER concatenate user input into SQL strings. Unsafe: db.query(SELECT * FROM users WHERE email = '${email}') - attacker sends email = ' OR '1'='1. Safe with Sequelize: User.findOne({where: {email: email}}). Safe with pg: client.query('SELECT * FROM users WHERE email = $1', [email]). Safe with mysql2: connection.query('SELECT * FROM users WHERE email = ?', [email]). Parameterized queries ensure input treated as data, not executable SQL. Also validate input types: if (typeof email !== 'string') return error. Use prepared statements for repeated queries (better performance + security). Applies to all databases: PostgreSQL, MySQL, MongoDB (use Mongoose, not string concatenation).

99% confidence
A

Use bcrypt with 10+ salt rounds for password hashing. Install: npm install bcrypt. Hash password: const bcrypt = require('bcrypt'); const saltRounds = 12; const hash = await bcrypt.hash(password, saltRounds). Verify password: const match = await bcrypt.compare(inputPassword, storedHash). Salt rounds: 10 is minimum (2^10 iterations), 12 recommended for 2024 (4x slower than 10, better security), 14+ for high-security. Each increment doubles hashing time - balances security vs UX. Never use crypto.createHash('sha256') for passwords (no salt, too fast). bcrypt is adaptive - increase rounds as hardware improves. Store hash in database (60 characters), never store plaintext passwords. Automatically generates unique salt per password. NEVER: md5/sha1 (broken), plain crypto.createHash (no salt), custom hashing (use battle-tested library).

99% confidence
A

4 critical practices: (1) HTTPS only: Enforce TLS 1.2+ with strong ciphers, redirect HTTP to HTTPS. Use Strict-Transport-Security header. (2) Never hardcode secrets: Use environment variables (process.env.SECRET) or secrets managers (AWS Secrets Manager, Azure Key Vault). Add sensitive files to .gitignore. (3) Encrypt data at rest: Use AES-256-GCM for database encryption: crypto.createCipheriv('aes-256-gcm', key, iv). (4) Filter response data: Don't expose password hashes, internal IDs, system paths. Pattern: const sanitize = (user) => ({id: user.id, email: user.email}) - explicit whitelist. Use helmet to remove revealing headers. Configure error handlers to not expose stack traces in production. Scan code for secrets: use git-secrets, truffleHog. Regular security audits.

99% confidence
A

3-layer defense: (1) Input validation: Validate all user input against expected format. Use validator.js: validator.isEmail(email), reject unexpected patterns. (2) Output encoding: Escape HTML special characters before rendering. Use template engines with auto-escaping (Pug, EJS with <%- for raw, <%= for escaped). Manual: const escape = (str) => str.replace(/[&<>"']/g, (char) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char])). (3) Content-Security-Policy: Use helmet CSP to block inline scripts: helmet.contentSecurityPolicy({directives: {scriptSrc: ["'self'"]}}). Never use dangerouslySetInnerHTML in React without sanitization. Use DOMPurify for user HTML: DOMPurify.sanitize(html). Test with XSS payloads: , <img src=x onerror=alert('XSS')>.

99% confidence
A

Implement authorization on EVERY protected endpoint, never trust client claims. Pattern: middleware checks if authenticated user can access resource. Code: const authorize = (requiredRole) => (req, res, next) => {if (!req.user) return res.status(401).json({error: 'Unauthorized'}); if (!req.user.roles.includes(requiredRole)) return res.status(403).json({error: 'Forbidden'}); next();}. Use: app.get('/admin/users', authenticate, authorize('admin'), getUsers). Principle of least privilege: Grant minimum permissions needed. Check authorization at data layer too: User.findOne({where: {id, userId: req.user.id}}) - ensures user owns resource. Don't expose internal IDs: Use UUIDs, not sequential integers. Prevent horizontal privilege escalation: User shouldn't access other user's data by changing ID. Test: Try accessing resources as different users, verify 403 Forbidden. Use JWT claims for roles but validate on server.

99% confidence
A

4 practices: (1) Run npm audit regularly: Checks for known vulnerabilities in dependencies. Run: npm audit, fix with npm audit fix (auto-updates), manually fix breaking changes. Set up: npm audit in CI/CD pipeline, fail build on high/critical vulnerabilities. (2) Use npm outdated: Shows available updates. Update: npm update (respects semver), npm install package@latest for major versions. (3) Use Snyk or Dependabot: Automated vulnerability scanning, creates PRs for fixes. Snyk: snyk test checks vulnerabilities, snyk monitor for continuous monitoring. (4) Pin versions: Use exact versions in package.json (1.2.3 not ^1.2.3) for production, test updates in staging first. Review dependencies before installing: Check last updated, weekly downloads, known vulnerabilities. Remove unused dependencies. Use npm ci in production (installs exact versions from package-lock.json).

99% confidence
A

Prevent OWASP A08:2021 (Software and Data Integrity Failures, CWE-502) by never accepting serialized objects from untrusted sources. JSON.parse is safe (only creates data, no code execution) but validate structure after parsing. Pattern: const data = JSON.parse(input); const schema = Joi.object({id: Joi.number().required(), name: Joi.string().max(100).required()}); const {error, value} = schema.validate(data); if (error) throw new Error('Invalid data'). NEVER use eval(), Function() constructor, node-serialize, or serialize-javascript with user input. For session storage, use signed cookies with cryptographic integrity checks: app.use(cookieParser(crypto.randomBytes(64).toString('hex'))). Implement digital signatures on serialized data: const hmac = crypto.createHmac('sha256', secret).update(data).digest('hex'). Enforce strict type constraints and isolate deserialization in low-privilege environments. Monitor deserialization errors and log suspicious patterns. Use trusted npm repositories only.

99% confidence
A

Log 6 security events: (1) Authentication: Login attempts (success/fail), password resets, account lockouts. (2) Authorization: Access denied (403), privilege escalation attempts. (3) Input validation: Rejected inputs, suspicious patterns (SQL injection attempts). (4) Changes: User data modifications, permission changes, configuration updates. (5) Errors: Uncaught exceptions, external service failures, rate limit triggers. (6) System: Application start/stop, dependency changes. Use structured logging: winston, pino. Pattern: logger.warn({event: 'auth.failed', ip: req.ip, email, reason: 'invalid_password'}). DON'T log: Passwords, tokens, credit cards, full request bodies. Sanitize logs: Remove sensitive data before logging. Centralize: Send to ELK, Splunk, Datadog. Alert on: Multiple failed logins (brute force), unusual access patterns, system errors. Retain logs 90+ days for forensics.

99% confidence
A

Prevent OWASP A10:2021 (SSRF) by validating and restricting all outbound HTTP requests. Attack vector: Attacker tricks server into fetching internal URLs (AWS metadata 169.254.169.254, internal services, bypass firewall). Layered defense: (1) Allowlist domains: const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com']; const url = new URL(input); if (!ALLOWED_HOSTS.includes(url.hostname)) throw new Error('Forbidden host'). (2) Blacklist private IPs: Reject 127.0.0.1, 10.0.0.0/8, 192.168.0.0/16, 169.254.0.0/16 (AWS IMDS), 172.16.0.0/12, ::1. Use library: is-private-ip. (3) Protocol validation: Only allow https://, reject file://, gopher://, ftp://. (4) Cloud metadata protection: Enforce IMDSv2 (require HTTP tokens) on AWS EC2 instances. (5) Network egress controls: Use firewall rules, run in isolated VPC with strict outbound rules. (6) Timeout limits: Prevent hanging on internal endpoints. Never: fetch(userInput) without validation. 2025 best practice: Implement defense-in-depth with allowlists + network controls.

99% confidence
A

Prevent XML External Entity (XXE) attacks by disabling external entity processing and DTD in XML parsers. XXE attack vector: Attacker sends XML with <!DOCTYPE> and <!ENTITY> declarations that read local files (/etc/passwd), trigger SSRF (http://internal), or cause DoS (billion laughs attack). Node.js has no native XML parser - use third-party libraries securely. Safe configurations: (1) xml2js (safe by default): const parser = new xml2js.Parser(); - external entities disabled by default. (2) fast-xml-parser: const parser = new XMLParser({processEntities: false, allowBooleanAttributes: false, ignoreAttributes: false}). (3) libxmljs (UNSAFE by default): MUST explicitly disable: libxmljs.parseXml(xml, {noent: false, dtdload: false, dtdvalid: false}). 2025 best practice: Prefer JSON over XML (eliminates XXE risk entirely). If XML required: Disable DTD processing, disable external entities, validate against strict XSD schema, sanitize all inputs. Recent 2025 attacks target cloud apps and APIs with insecure XML parsers.

99% confidence
A

Use parameterized queries with ORMs (Sequelize, TypeORM) or query builders (Knex). NEVER concatenate user input into SQL strings. Unsafe: db.query(SELECT * FROM users WHERE email = '${email}') - attacker sends email = ' OR '1'='1. Safe with Sequelize: User.findOne({where: {email: email}}). Safe with pg: client.query('SELECT * FROM users WHERE email = $1', [email]). Safe with mysql2: connection.query('SELECT * FROM users WHERE email = ?', [email]). Parameterized queries ensure input treated as data, not executable SQL. Also validate input types: if (typeof email !== 'string') return error. Use prepared statements for repeated queries (better performance + security). Applies to all databases: PostgreSQL, MySQL, MongoDB (use Mongoose, not string concatenation).

99% confidence
A

Use bcrypt with 10+ salt rounds for password hashing. Install: npm install bcrypt. Hash password: const bcrypt = require('bcrypt'); const saltRounds = 12; const hash = await bcrypt.hash(password, saltRounds). Verify password: const match = await bcrypt.compare(inputPassword, storedHash). Salt rounds: 10 is minimum (2^10 iterations), 12 recommended for 2024 (4x slower than 10, better security), 14+ for high-security. Each increment doubles hashing time - balances security vs UX. Never use crypto.createHash('sha256') for passwords (no salt, too fast). bcrypt is adaptive - increase rounds as hardware improves. Store hash in database (60 characters), never store plaintext passwords. Automatically generates unique salt per password. NEVER: md5/sha1 (broken), plain crypto.createHash (no salt), custom hashing (use battle-tested library).

99% confidence
A

4 critical practices: (1) HTTPS only: Enforce TLS 1.2+ with strong ciphers, redirect HTTP to HTTPS. Use Strict-Transport-Security header. (2) Never hardcode secrets: Use environment variables (process.env.SECRET) or secrets managers (AWS Secrets Manager, Azure Key Vault). Add sensitive files to .gitignore. (3) Encrypt data at rest: Use AES-256-GCM for database encryption: crypto.createCipheriv('aes-256-gcm', key, iv). (4) Filter response data: Don't expose password hashes, internal IDs, system paths. Pattern: const sanitize = (user) => ({id: user.id, email: user.email}) - explicit whitelist. Use helmet to remove revealing headers. Configure error handlers to not expose stack traces in production. Scan code for secrets: use git-secrets, truffleHog. Regular security audits.

99% confidence
A

3-layer defense: (1) Input validation: Validate all user input against expected format. Use validator.js: validator.isEmail(email), reject unexpected patterns. (2) Output encoding: Escape HTML special characters before rendering. Use template engines with auto-escaping (Pug, EJS with <%- for raw, <%= for escaped). Manual: const escape = (str) => str.replace(/[&<>"']/g, (char) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char])). (3) Content-Security-Policy: Use helmet CSP to block inline scripts: helmet.contentSecurityPolicy({directives: {scriptSrc: ["'self'"]}}). Never use dangerouslySetInnerHTML in React without sanitization. Use DOMPurify for user HTML: DOMPurify.sanitize(html). Test with XSS payloads: , <img src=x onerror=alert('XSS')>.

99% confidence
A

Implement authorization on EVERY protected endpoint, never trust client claims. Pattern: middleware checks if authenticated user can access resource. Code: const authorize = (requiredRole) => (req, res, next) => {if (!req.user) return res.status(401).json({error: 'Unauthorized'}); if (!req.user.roles.includes(requiredRole)) return res.status(403).json({error: 'Forbidden'}); next();}. Use: app.get('/admin/users', authenticate, authorize('admin'), getUsers). Principle of least privilege: Grant minimum permissions needed. Check authorization at data layer too: User.findOne({where: {id, userId: req.user.id}}) - ensures user owns resource. Don't expose internal IDs: Use UUIDs, not sequential integers. Prevent horizontal privilege escalation: User shouldn't access other user's data by changing ID. Test: Try accessing resources as different users, verify 403 Forbidden. Use JWT claims for roles but validate on server.

99% confidence
A

4 practices: (1) Run npm audit regularly: Checks for known vulnerabilities in dependencies. Run: npm audit, fix with npm audit fix (auto-updates), manually fix breaking changes. Set up: npm audit in CI/CD pipeline, fail build on high/critical vulnerabilities. (2) Use npm outdated: Shows available updates. Update: npm update (respects semver), npm install package@latest for major versions. (3) Use Snyk or Dependabot: Automated vulnerability scanning, creates PRs for fixes. Snyk: snyk test checks vulnerabilities, snyk monitor for continuous monitoring. (4) Pin versions: Use exact versions in package.json (1.2.3 not ^1.2.3) for production, test updates in staging first. Review dependencies before installing: Check last updated, weekly downloads, known vulnerabilities. Remove unused dependencies. Use npm ci in production (installs exact versions from package-lock.json).

99% confidence
A

Prevent OWASP A08:2021 (Software and Data Integrity Failures, CWE-502) by never accepting serialized objects from untrusted sources. JSON.parse is safe (only creates data, no code execution) but validate structure after parsing. Pattern: const data = JSON.parse(input); const schema = Joi.object({id: Joi.number().required(), name: Joi.string().max(100).required()}); const {error, value} = schema.validate(data); if (error) throw new Error('Invalid data'). NEVER use eval(), Function() constructor, node-serialize, or serialize-javascript with user input. For session storage, use signed cookies with cryptographic integrity checks: app.use(cookieParser(crypto.randomBytes(64).toString('hex'))). Implement digital signatures on serialized data: const hmac = crypto.createHmac('sha256', secret).update(data).digest('hex'). Enforce strict type constraints and isolate deserialization in low-privilege environments. Monitor deserialization errors and log suspicious patterns. Use trusted npm repositories only.

99% confidence
A

Log 6 security events: (1) Authentication: Login attempts (success/fail), password resets, account lockouts. (2) Authorization: Access denied (403), privilege escalation attempts. (3) Input validation: Rejected inputs, suspicious patterns (SQL injection attempts). (4) Changes: User data modifications, permission changes, configuration updates. (5) Errors: Uncaught exceptions, external service failures, rate limit triggers. (6) System: Application start/stop, dependency changes. Use structured logging: winston, pino. Pattern: logger.warn({event: 'auth.failed', ip: req.ip, email, reason: 'invalid_password'}). DON'T log: Passwords, tokens, credit cards, full request bodies. Sanitize logs: Remove sensitive data before logging. Centralize: Send to ELK, Splunk, Datadog. Alert on: Multiple failed logins (brute force), unusual access patterns, system errors. Retain logs 90+ days for forensics.

99% confidence
A

Prevent OWASP A10:2021 (SSRF) by validating and restricting all outbound HTTP requests. Attack vector: Attacker tricks server into fetching internal URLs (AWS metadata 169.254.169.254, internal services, bypass firewall). Layered defense: (1) Allowlist domains: const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com']; const url = new URL(input); if (!ALLOWED_HOSTS.includes(url.hostname)) throw new Error('Forbidden host'). (2) Blacklist private IPs: Reject 127.0.0.1, 10.0.0.0/8, 192.168.0.0/16, 169.254.0.0/16 (AWS IMDS), 172.16.0.0/12, ::1. Use library: is-private-ip. (3) Protocol validation: Only allow https://, reject file://, gopher://, ftp://. (4) Cloud metadata protection: Enforce IMDSv2 (require HTTP tokens) on AWS EC2 instances. (5) Network egress controls: Use firewall rules, run in isolated VPC with strict outbound rules. (6) Timeout limits: Prevent hanging on internal endpoints. Never: fetch(userInput) without validation. 2025 best practice: Implement defense-in-depth with allowlists + network controls.

99% confidence
A

Prevent XML External Entity (XXE) attacks by disabling external entity processing and DTD in XML parsers. XXE attack vector: Attacker sends XML with <!DOCTYPE> and <!ENTITY> declarations that read local files (/etc/passwd), trigger SSRF (http://internal), or cause DoS (billion laughs attack). Node.js has no native XML parser - use third-party libraries securely. Safe configurations: (1) xml2js (safe by default): const parser = new xml2js.Parser(); - external entities disabled by default. (2) fast-xml-parser: const parser = new XMLParser({processEntities: false, allowBooleanAttributes: false, ignoreAttributes: false}). (3) libxmljs (UNSAFE by default): MUST explicitly disable: libxmljs.parseXml(xml, {noent: false, dtdload: false, dtdvalid: false}). 2025 best practice: Prefer JSON over XML (eliminates XXE risk entirely). If XML required: Disable DTD processing, disable external entities, validate against strict XSD schema, sanitize all inputs. Recent 2025 attacks target cloud apps and APIs with insecure XML parsers.

99% confidence