ProbCheck LogoProbCheck
Back to Blog
JavaScript

JavaScript Security Best Practices: Protect Your Code from Attacks

March 20, 2025
10 min read

JavaScript powers most modern web applications, making it a prime target for attackers. Following security best practices is essential to protect your users and your application from vulnerabilities.

1. Always Validate and Sanitize User Input

Never trust user input. All data coming from users, APIs, or external sources should be validated and sanitized.

Bad Practice:

// ❌ Dangerous - No validation
const username = req.body.username;
document.getElementById('welcome').innerHTML = 'Hello ' + username;

Good Practice:

// ✅ Safe - Validate and escape
const username = sanitize(req.body.username);
document.getElementById('welcome').textContent = 'Hello ' + username;

2. Prevent Cross-Site Scripting (XSS)

XSS is one of the most common JavaScript vulnerabilities. Attackers inject malicious scripts that run in users' browsers.

Prevention techniques:

  • Use textContent instead of innerHTML when displaying user data
  • Sanitize HTML with libraries like DOMPurify
  • Implement Content Security Policy (CSP) headers
  • Escape special characters in user input

Example:

// ❌ Vulnerable to XSS
element.innerHTML = userInput;

// ✅ Safe from XSS
element.textContent = userInput;

// ✅ Safe HTML rendering with DOMPurify
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);

3. Use Strict Mode

Always enable strict mode to catch common coding errors and prevent unsafe actions.

'use strict';

// This will now throw an error instead of silently failing
undeclaredVariable = 10; // ReferenceError

4. Secure Authentication and Session Management

Authentication vulnerabilities can lead to account takeovers and data breaches.

Best practices:

  • Store tokens in httpOnly cookies (not localStorage)
  • Implement proper session timeout
  • Use secure, random session IDs
  • Enable HTTPS for all authentication requests
  • Implement rate limiting on login endpoints
// ❌ Insecure - Token in localStorage
localStorage.setItem('token', authToken);

// ✅ Secure - httpOnly cookie
res.cookie('token', authToken, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  maxAge: 3600000
});

5. Avoid eval() and Similar Functions

Never use eval(), Function(), or setTimeout() with strings. They can execute arbitrary code.

// ❌ Extremely dangerous
eval(userInput);

// ❌ Also dangerous
new Function(userInput)();
setTimeout(userInput, 1000);

// ✅ Use safe alternatives
const result = JSON.parse(userInput); // For data
const fn = myFunction; // Direct function reference

6. Implement Content Security Policy (CSP)

CSP helps prevent XSS attacks by controlling which resources can be loaded.

// Set CSP headers in your server
Content-Security-Policy: 
  default-src 'self'; 
  script-src 'self' https://trusted-cdn.com; 
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;

7. Secure API Calls

When making API requests, implement proper security measures.

  • Use HTTPS for all API calls
  • Validate API responses
  • Implement rate limiting
  • Don't expose sensitive data in URLs
  • Use proper CORS configuration
// ❌ Bad - Sensitive data in URL
fetch('https://api.example.com/user?token=' + userToken);

// ✅ Good - Token in header
fetch('https://api.example.com/user', {
  headers: {
    'Authorization': 'Bearer ' + userToken,
    'Content-Type': 'application/json'
  }
});

8. Prevent Prototype Pollution

Prototype pollution can allow attackers to modify JavaScript object prototypes.

// ❌ Vulnerable to prototype pollution
function merge(target, source) {
  for (let key in source) {
    target[key] = source[key];
  }
}

// ✅ Safe - Check for __proto__
function safeMerge(target, source) {
  for (let key in source) {
    if (key === '__proto__' || key === 'constructor') continue;
    target[key] = source[key];
  }
}

// ✅ Even better - Use Object.assign
const merged = Object.assign({}, target, source);

9. Use Security Linters

Automated tools can catch security issues during development.

  • ESLint Security Plugin: Detects common security issues
  • npm audit: Finds vulnerabilities in dependencies
  • ProbCheck: AI-powered vulnerability scanner

10. Keep Dependencies Updated

Outdated packages are a major security risk. Regularly update your dependencies.

# Check for vulnerabilities
npm audit

# Update packages
npm update

# Check for outdated packages
npm outdated

11. Secure File Uploads

File uploads can be exploited to upload malicious scripts.

  • Validate file types on the server
  • Limit file size
  • Rename uploaded files
  • Store files outside web root
  • Scan files for malware

12. Implement Rate Limiting

Prevent brute force attacks and DDoS with rate limiting.

// Using express-rate-limit
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per windowMs
});

app.use('/api/', limiter);

Quick Security Checklist

  • ✅ All user input validated and sanitized
  • ✅ Using textContent instead of innerHTML
  • ✅ CSP headers implemented
  • ✅ HTTPS enabled everywhere
  • ✅ Tokens in httpOnly cookies
  • ✅ Dependencies up to date
  • ✅ Rate limiting on sensitive endpoints
  • ✅ Error messages don't leak sensitive info
  • ✅ Security linters configured
  • ✅ Regular security audits

Conclusion

JavaScript security requires constant vigilance. By following these best practices, you can significantly reduce your application's attack surface and protect your users' data.

Remember: Security is not a one-time effort. Regularly scan your code, update dependencies, and stay informed about new vulnerabilities.

Scan Your JavaScript Code for Free

ProbCheck automatically detects XSS, injection vulnerabilities, and 40+ other security issues in your JavaScript code. Get instant security insights.

Start Free Scan →