Advanced JavaScriptFeatured

JavaScript Security: Best Practices and Common Vulnerabilities

Learn JavaScript security best practices. Understand common vulnerabilities like XSS, CSRF, and injection attacks, and how to prevent them.

By JavaScriptDoc Team
securityjavascriptxsscsrfvulnerabilities

JavaScript Security: Best Practices and Common Vulnerabilities

Security is crucial in JavaScript applications. This guide covers common vulnerabilities, attack vectors, and best practices for building secure applications.

Cross-Site Scripting (XSS)

Understanding XSS Attacks

// Vulnerable code - DOM XSS
function displayUserInput(userInput) {
  // DANGEROUS: Direct HTML injection
  document.getElementById('output').innerHTML = userInput;
}

// Attack example
displayUserInput('<script>alert("XSS Attack!")</script>');
displayUserInput('<img src=x onerror="alert(\'XSS\')"/>');

// Vulnerable code - Reflected XSS
function displaySearchResults() {
  // DANGEROUS: Using URL parameters directly
  const query = new URLSearchParams(window.location.search).get('q');
  document.getElementById('results').innerHTML = `
    <h2>Search results for: ${query}</h2>
  `;
}

// Attack URL: site.com/search?q=<script>alert('XSS')</script>

// Vulnerable code - Stored XSS
function displayComment(comment) {
  // DANGEROUS: Displaying stored user content
  const commentDiv = document.createElement('div');
  commentDiv.innerHTML = comment.text;
  document.getElementById('comments').appendChild(commentDiv);
}

XSS Prevention

// Safe HTML encoding
class HTMLEncoder {
  static encode(str) {
    const div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
  }

  static encodeAttribute(str) {
    return str
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#x27;')
      .replace(/\//g, '&#x2F;');
  }
}

// Safe display functions
function safeDisplayUserInput(userInput) {
  // Use textContent instead of innerHTML
  document.getElementById('output').textContent = userInput;
}

function safeDisplayHTML(userInput) {
  // Sanitize HTML if needed
  const sanitized = DOMPurify.sanitize(userInput);
  document.getElementById('output').innerHTML = sanitized;
}

// Content Security Policy (CSP) headers
// Add to server response headers:
// Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-random123';

// Safe template rendering
class SafeTemplate {
  static render(template, data) {
    // Use a template engine with auto-escaping
    return template.replace(/{{(\w+)}}/g, (match, key) => {
      return HTMLEncoder.encode(data[key] || '');
    });
  }
}

// React/Vue automatically escape content
function SafeComponent({ userInput }) {
  // This is automatically escaped
  return <div>{userInput}</div>;
}

// Validate and sanitize input
class InputValidator {
  static sanitizeHTML(input) {
    // Whitelist allowed tags and attributes
    const allowedTags = ['p', 'br', 'strong', 'em', 'u'];
    const allowedAttributes = [];

    return DOMPurify.sanitize(input, {
      ALLOWED_TAGS: allowedTags,
      ALLOWED_ATTR: allowedAttributes,
    });
  }

  static isValidEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }

  static stripScripts(input) {
    return input.replace(
      /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
      ''
    );
  }
}

Cross-Site Request Forgery (CSRF)

Understanding CSRF Attacks

// Vulnerable code - No CSRF protection
app.post('/api/transfer', async (req, res) => {
  const { toAccount, amount } = req.body;
  const userId = req.session.userId;

  // DANGEROUS: No CSRF token validation
  await transferMoney(userId, toAccount, amount);
  res.json({ success: true });
});

// Attack example (malicious site)
// <form action="https://bank.com/api/transfer" method="POST">
//   <input type="hidden" name="toAccount" value="attacker" />
//   <input type="hidden" name="amount" value="1000" />
// </form>
// <script>document.forms[0].submit();</script>

CSRF Prevention

// CSRF token generation and validation
class CSRFProtection {
  static generateToken() {
    const array = new Uint8Array(32);
    crypto.getRandomValues(array);
    return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join(
      ''
    );
  }

  static validateToken(sessionToken, requestToken) {
    if (!sessionToken || !requestToken) {
      return false;
    }

    // Constant-time comparison to prevent timing attacks
    return this.constantTimeCompare(sessionToken, requestToken);
  }

  static constantTimeCompare(a, b) {
    if (a.length !== b.length) {
      return false;
    }

    let result = 0;
    for (let i = 0; i < a.length; i++) {
      result |= a.charCodeAt(i) ^ b.charCodeAt(i);
    }

    return result === 0;
  }
}

// Server-side implementation
app.use((req, res, next) => {
  if (!req.session.csrfToken) {
    req.session.csrfToken = CSRFProtection.generateToken();
  }
  res.locals.csrfToken = req.session.csrfToken;
  next();
});

// Protected endpoint
app.post('/api/transfer', (req, res) => {
  const sessionToken = req.session.csrfToken;
  const requestToken = req.body.csrfToken || req.headers['x-csrf-token'];

  if (!CSRFProtection.validateToken(sessionToken, requestToken)) {
    return res.status(403).json({ error: 'Invalid CSRF token' });
  }

  // Process the request
});

// Client-side implementation
class SecureAPI {
  constructor() {
    this.csrfToken = this.getCSRFToken();
  }

  getCSRFToken() {
    // Get from meta tag or cookie
    const metaTag = document.querySelector('meta[name="csrf-token"]');
    return metaTag ? metaTag.content : null;
  }

  async post(url, data) {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': this.csrfToken,
      },
      credentials: 'same-origin',
      body: JSON.stringify(data),
    });

    return response.json();
  }
}

// Double submit cookie pattern
class DoubleSubmitCSRF {
  static setToken() {
    const token = CSRFProtection.generateToken();

    // Set as HTTP-only cookie
    document.cookie = `csrf-token=${token}; Path=/; SameSite=Strict`;

    // Also store in sessionStorage for requests
    sessionStorage.setItem('csrf-token', token);

    return token;
  }

  static getToken() {
    return sessionStorage.getItem('csrf-token');
  }
}

Input Validation and Sanitization

Comprehensive Input Validation

class InputSecurity {
  // Prevent SQL injection in client-side validation
  static sanitizeForSQL(input) {
    // Note: Real SQL injection prevention should be done server-side
    // This is just for client-side pre-validation
    return input.replace(/['"\\]/g, '\\$&').replace(/[\x00-\x1f\x7f]/g, '');
  }

  // Prevent NoSQL injection
  static sanitizeForMongoDB(input) {
    if (typeof input !== 'string') {
      throw new Error('Input must be a string');
    }

    // Remove operators
    return input.replace(/[$]/g, '');
  }

  // File upload validation
  static validateFile(file, options = {}) {
    const {
      maxSize = 5 * 1024 * 1024, // 5MB default
      allowedTypes = ['image/jpeg', 'image/png', 'image/gif'],
      allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif'],
    } = options;

    const errors = [];

    // Check file size
    if (file.size > maxSize) {
      errors.push(`File size exceeds ${maxSize / 1024 / 1024}MB limit`);
    }

    // Check MIME type
    if (!allowedTypes.includes(file.type)) {
      errors.push(`File type ${file.type} is not allowed`);
    }

    // Check file extension
    const extension = file.name
      .substring(file.name.lastIndexOf('.'))
      .toLowerCase();
    if (!allowedExtensions.includes(extension)) {
      errors.push(`File extension ${extension} is not allowed`);
    }

    // Check for double extensions
    const parts = file.name.split('.');
    if (parts.length > 2) {
      errors.push('Files with multiple extensions are not allowed');
    }

    return {
      valid: errors.length === 0,
      errors,
    };
  }

  // URL validation
  static validateURL(url, options = {}) {
    const {
      allowedProtocols = ['https:', 'http:'],
      allowedDomains = null,
      blockLocalhost = true,
    } = options;

    try {
      const parsed = new URL(url);

      // Check protocol
      if (!allowedProtocols.includes(parsed.protocol)) {
        throw new Error(`Protocol ${parsed.protocol} is not allowed`);
      }

      // Check for localhost/internal IPs
      if (blockLocalhost) {
        const hostname = parsed.hostname;
        if (
          hostname === 'localhost' ||
          hostname === '127.0.0.1' ||
          hostname.startsWith('192.168.') ||
          hostname.startsWith('10.') ||
          hostname.startsWith('172.')
        ) {
          throw new Error('Local/internal URLs are not allowed');
        }
      }

      // Check allowed domains
      if (allowedDomains && !allowedDomains.includes(parsed.hostname)) {
        throw new Error(`Domain ${parsed.hostname} is not allowed`);
      }

      return { valid: true, parsed };
    } catch (error) {
      return { valid: false, error: error.message };
    }
  }
}

// Form validation with security in mind
class SecureFormValidator {
  constructor(form) {
    this.form = form;
    this.rules = new Map();
  }

  addRule(fieldName, validators) {
    this.rules.set(fieldName, validators);
  }

  validate() {
    const errors = {};

    for (const [fieldName, validators] of this.rules) {
      const field = this.form.elements[fieldName];
      if (!field) continue;

      const value = field.value;
      const fieldErrors = [];

      for (const validator of validators) {
        const result = validator(value);
        if (result !== true) {
          fieldErrors.push(result);
        }
      }

      if (fieldErrors.length > 0) {
        errors[fieldName] = fieldErrors;
      }
    }

    return {
      valid: Object.keys(errors).length === 0,
      errors,
    };
  }
}

// Common validators
const Validators = {
  required: (value) => value.trim() !== '' || 'This field is required',

  email: (value) => {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return regex.test(value) || 'Invalid email address';
  },

  minLength: (min) => (value) =>
    value.length >= min || `Must be at least ${min} characters`,

  maxLength: (max) => (value) =>
    value.length <= max || `Must be no more than ${max} characters`,

  pattern: (regex, message) => (value) => regex.test(value) || message,

  noScriptTags: (value) =>
    !/<script[^>]*>.*?<\/script>/gi.test(value) ||
    'Script tags are not allowed',

  alphanumeric: (value) =>
    /^[a-zA-Z0-9]+$/.test(value) || 'Only letters and numbers are allowed',

  strongPassword: (value) => {
    const checks = [
      { regex: /.{8,}/, message: 'At least 8 characters' },
      { regex: /[A-Z]/, message: 'At least one uppercase letter' },
      { regex: /[a-z]/, message: 'At least one lowercase letter' },
      { regex: /[0-9]/, message: 'At least one number' },
      { regex: /[^A-Za-z0-9]/, message: 'At least one special character' },
    ];

    for (const check of checks) {
      if (!check.regex.test(value)) {
        return check.message;
      }
    }

    return true;
  },
};

Authentication and Authorization

Secure Authentication

// Client-side authentication helper
class AuthManager {
  constructor() {
    this.tokenKey = 'auth_token';
    this.refreshTokenKey = 'refresh_token';
  }

  // Secure token storage
  setTokens(accessToken, refreshToken) {
    // Store in memory for most secure (but lost on refresh)
    this.accessToken = accessToken;

    // Store refresh token in httpOnly cookie (server-side)
    // Or use sessionStorage for less sensitive apps
    sessionStorage.setItem(this.refreshTokenKey, refreshToken);
  }

  getAccessToken() {
    return this.accessToken;
  }

  async refreshAccessToken() {
    const refreshToken = sessionStorage.getItem(this.refreshTokenKey);
    if (!refreshToken) {
      throw new Error('No refresh token available');
    }

    const response = await fetch('/api/refresh', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ refreshToken }),
    });

    if (!response.ok) {
      throw new Error('Failed to refresh token');
    }

    const { accessToken } = await response.json();
    this.accessToken = accessToken;
    return accessToken;
  }

  logout() {
    this.accessToken = null;
    sessionStorage.removeItem(this.refreshTokenKey);
    // Also call server to invalidate tokens
  }

  // Add token to requests
  async authenticatedFetch(url, options = {}) {
    let token = this.getAccessToken();

    if (!token) {
      token = await this.refreshAccessToken();
    }

    return fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        Authorization: `Bearer ${token}`,
      },
    });
  }
}

// Password hashing (client-side, before sending to server)
// Note: Server should still hash passwords
class PasswordSecurity {
  static async hashPassword(password, salt) {
    const encoder = new TextEncoder();
    const data = encoder.encode(password + salt);

    const hashBuffer = await crypto.subtle.digest('SHA-256', data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));

    return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
  }

  static generateSalt() {
    const array = new Uint8Array(16);
    crypto.getRandomValues(array);
    return Array.from(array, (b) => b.toString(16).padStart(2, '0')).join('');
  }

  static checkPasswordStrength(password) {
    let strength = 0;
    const checks = [
      { regex: /.{8,}/, value: 1 },
      { regex: /.{12,}/, value: 1 },
      { regex: /[A-Z]/, value: 1 },
      { regex: /[a-z]/, value: 1 },
      { regex: /[0-9]/, value: 1 },
      { regex: /[^A-Za-z0-9]/, value: 1 },
    ];

    checks.forEach((check) => {
      if (check.regex.test(password)) {
        strength += check.value;
      }
    });

    return {
      score: strength,
      level: strength < 3 ? 'weak' : strength < 5 ? 'medium' : 'strong',
    };
  }
}

// Role-based access control
class RBAC {
  constructor(userRoles) {
    this.userRoles = userRoles;
    this.permissions = new Map();
  }

  defineRole(role, permissions) {
    this.permissions.set(role, new Set(permissions));
  }

  can(permission) {
    for (const role of this.userRoles) {
      const rolePermissions = this.permissions.get(role);
      if (rolePermissions && rolePermissions.has(permission)) {
        return true;
      }
    }
    return false;
  }

  hasRole(role) {
    return this.userRoles.includes(role);
  }

  checkAccess(requiredPermission) {
    if (!this.can(requiredPermission)) {
      throw new Error(
        `Access denied: Missing permission '${requiredPermission}'`
      );
    }
  }
}

Secure Communication

HTTPS and Secure Cookies

// Secure cookie management
class SecureCookies {
  static set(name, value, options = {}) {
    const defaults = {
      secure: true, // HTTPS only
      sameSite: 'Strict', // CSRF protection
      httpOnly: false, // Can't set httpOnly from JS
      path: '/',
      expires: new Date(Date.now() + 24 * 60 * 60 * 1000), // 1 day
    };

    const settings = { ...defaults, ...options };

    let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;

    if (settings.expires) {
      cookie += `; expires=${settings.expires.toUTCString()}`;
    }

    if (settings.path) {
      cookie += `; path=${settings.path}`;
    }

    if (settings.domain) {
      cookie += `; domain=${settings.domain}`;
    }

    if (settings.secure) {
      cookie += '; secure';
    }

    if (settings.sameSite) {
      cookie += `; samesite=${settings.sameSite}`;
    }

    document.cookie = cookie;
  }

  static get(name) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);

    if (parts.length === 2) {
      return decodeURIComponent(parts.pop().split(';').shift());
    }

    return null;
  }

  static delete(name, path = '/') {
    this.set(name, '', {
      expires: new Date(0),
      path,
    });
  }
}

// Encrypted local storage
class EncryptedStorage {
  constructor(encryptionKey) {
    this.encryptionKey = encryptionKey;
  }

  async encrypt(data) {
    const encoder = new TextEncoder();
    const dataBuffer = encoder.encode(JSON.stringify(data));

    const key = await crypto.subtle.importKey(
      'raw',
      encoder.encode(this.encryptionKey),
      { name: 'AES-GCM' },
      false,
      ['encrypt']
    );

    const iv = crypto.getRandomValues(new Uint8Array(12));
    const encrypted = await crypto.subtle.encrypt(
      { name: 'AES-GCM', iv },
      key,
      dataBuffer
    );

    return {
      iv: Array.from(iv),
      data: Array.from(new Uint8Array(encrypted)),
    };
  }

  async decrypt(encryptedData) {
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();

    const key = await crypto.subtle.importKey(
      'raw',
      encoder.encode(this.encryptionKey),
      { name: 'AES-GCM' },
      false,
      ['decrypt']
    );

    const decrypted = await crypto.subtle.decrypt(
      { name: 'AES-GCM', iv: new Uint8Array(encryptedData.iv) },
      key,
      new Uint8Array(encryptedData.data)
    );

    return JSON.parse(decoder.decode(decrypted));
  }

  async setItem(key, value) {
    const encrypted = await this.encrypt(value);
    localStorage.setItem(key, JSON.stringify(encrypted));
  }

  async getItem(key) {
    const stored = localStorage.getItem(key);
    if (!stored) return null;

    const encrypted = JSON.parse(stored);
    return await this.decrypt(encrypted);
  }
}

Content Security Policy

Implementing CSP

// CSP header generator
class ContentSecurityPolicy {
  constructor() {
    this.directives = new Map();
  }

  setDirective(directive, sources) {
    this.directives.set(directive, sources);
  }

  generateNonce() {
    const array = new Uint8Array(16);
    crypto.getRandomValues(array);
    return btoa(String.fromCharCode.apply(null, array));
  }

  toString() {
    const policies = [];

    for (const [directive, sources] of this.directives) {
      const sourceList = Array.isArray(sources) ? sources.join(' ') : sources;
      policies.push(`${directive} ${sourceList}`);
    }

    return policies.join('; ');
  }

  static recommended() {
    const csp = new ContentSecurityPolicy();

    csp.setDirective('default-src', ["'self'"]);
    csp.setDirective('script-src', ["'self'", "'nonce-{NONCE}'"]);
    csp.setDirective('style-src', ["'self'", "'unsafe-inline'"]);
    csp.setDirective('img-src', ["'self'", 'data:', 'https:']);
    csp.setDirective('font-src', ["'self'"]);
    csp.setDirective('connect-src', ["'self'"]);
    csp.setDirective('frame-ancestors', ["'none'"]);
    csp.setDirective('base-uri', ["'self'"]);
    csp.setDirective('form-action', ["'self'"]);

    return csp;
  }
}

// CSP violation reporter
class CSPReporter {
  static setup() {
    document.addEventListener('securitypolicyviolation', (event) => {
      this.reportViolation({
        blockedURI: event.blockedURI,
        columnNumber: event.columnNumber,
        disposition: event.disposition,
        documentURI: event.documentURI,
        effectiveDirective: event.effectiveDirective,
        lineNumber: event.lineNumber,
        originalPolicy: event.originalPolicy,
        referrer: event.referrer,
        sample: event.sample,
        sourceFile: event.sourceFile,
        statusCode: event.statusCode,
        violatedDirective: event.violatedDirective,
      });
    });
  }

  static async reportViolation(violation) {
    try {
      await fetch('/api/csp-report', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          violation,
          timestamp: new Date().toISOString(),
          userAgent: navigator.userAgent,
          url: window.location.href,
        }),
      });
    } catch (error) {
      console.error('Failed to report CSP violation:', error);
    }
  }
}

// Dynamic script loading with nonce
class SecureScriptLoader {
  constructor(nonce) {
    this.nonce = nonce;
  }

  loadScript(src) {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = src;
      script.nonce = this.nonce;

      script.onload = () => resolve();
      script.onerror = () => reject(new Error(`Failed to load script: ${src}`));

      document.head.appendChild(script);
    });
  }

  inlineScript(code) {
    const script = document.createElement('script');
    script.nonce = this.nonce;
    script.textContent = code;
    document.head.appendChild(script);
  }
}

Third-Party Dependencies

Secure Dependency Management

// Subresource Integrity (SRI) checker
class SRIChecker {
  static async generateHash(content, algorithm = 'sha384') {
    const encoder = new TextEncoder();
    const data = encoder.encode(content);

    const hashBuffer = await crypto.subtle.digest(
      algorithm.toUpperCase().replace('SHA', 'SHA-'),
      data
    );

    const hashArray = Array.from(new Uint8Array(hashBuffer));
    const hashBase64 = btoa(String.fromCharCode(...hashArray));

    return `${algorithm}-${hashBase64}`;
  }

  static async verifyScript(url, expectedHash) {
    try {
      const response = await fetch(url);
      const content = await response.text();
      const actualHash = await this.generateHash(content);

      return actualHash === expectedHash;
    } catch (error) {
      console.error('Failed to verify script:', error);
      return false;
    }
  }

  static addScriptWithSRI(src, integrity, options = {}) {
    const script = document.createElement('script');
    script.src = src;
    script.integrity = integrity;
    script.crossOrigin = options.crossOrigin || 'anonymous';

    if (options.async) script.async = true;
    if (options.defer) script.defer = true;

    return new Promise((resolve, reject) => {
      script.onload = resolve;
      script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
      document.head.appendChild(script);
    });
  }
}

// Dependency auditor
class DependencyAuditor {
  constructor() {
    this.vulnerabilityDatabase = new Map();
  }

  async checkDependency(name, version) {
    // In real implementation, check against vulnerability databases
    const vulnerabilities = this.vulnerabilityDatabase.get(name) || [];

    return vulnerabilities.filter((vuln) => {
      return this.versionInRange(version, vuln.affectedVersions);
    });
  }

  versionInRange(version, range) {
    // Simplified version checking
    // In production, use a proper semver library
    return range.includes(version);
  }

  async auditPackageJson(packageJson) {
    const report = {
      vulnerabilities: [],
      summary: {
        total: 0,
        high: 0,
        medium: 0,
        low: 0,
      },
    };

    const dependencies = {
      ...packageJson.dependencies,
      ...packageJson.devDependencies,
    };

    for (const [name, version] of Object.entries(dependencies)) {
      const vulns = await this.checkDependency(name, version);

      vulns.forEach((vuln) => {
        report.vulnerabilities.push({
          package: name,
          version,
          vulnerability: vuln,
        });

        report.summary.total++;
        report.summary[vuln.severity]++;
      });
    }

    return report;
  }
}

Security Headers

// Security headers manager
class SecurityHeaders {
  static getRecommendedHeaders() {
    return {
      'X-Content-Type-Options': 'nosniff',
      'X-Frame-Options': 'DENY',
      'X-XSS-Protection': '1; mode=block',
      'Referrer-Policy': 'strict-origin-when-cross-origin',
      'Permissions-Policy': 'geolocation=(), microphone=(), camera=()',
      'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
      'Content-Security-Policy': this.getCSP(),
    };
  }

  static getCSP() {
    return [
      "default-src 'self'",
      "script-src 'self' 'nonce-{NONCE}'",
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https:",
      "font-src 'self'",
      "connect-src 'self'",
      "frame-ancestors 'none'",
      "base-uri 'self'",
      "form-action 'self'",
    ].join('; ');
  }

  static validateHeaders(headers) {
    const required = this.getRecommendedHeaders();
    const missing = [];
    const incorrect = [];

    for (const [header, expectedValue] of Object.entries(required)) {
      if (!headers[header]) {
        missing.push(header);
      } else if (
        headers[header] !== expectedValue &&
        header !== 'Content-Security-Policy'
      ) {
        incorrect.push({
          header,
          expected: expectedValue,
          actual: headers[header],
        });
      }
    }

    return { missing, incorrect };
  }
}

Best Practices Summary

// Security checklist implementation
class SecurityChecklist {
  static async runChecks() {
    const results = [];

    // Check for HTTPS
    results.push({
      check: 'HTTPS',
      passed: location.protocol === 'https:',
      message: 'Site should be served over HTTPS',
    });

    // Check for secure cookies
    const cookies = document.cookie.split(';');
    const insecureCookies = cookies.filter((cookie) => {
      return !cookie.includes('Secure') || !cookie.includes('SameSite');
    });

    results.push({
      check: 'Secure Cookies',
      passed: insecureCookies.length === 0,
      message: 'All cookies should have Secure and SameSite flags',
    });

    // Check for CSP
    const cspHeader = document.querySelector(
      'meta[http-equiv="Content-Security-Policy"]'
    );
    results.push({
      check: 'Content Security Policy',
      passed: cspHeader !== null,
      message: 'CSP should be implemented',
    });

    // Check for external scripts without SRI
    const externalScripts = document.querySelectorAll('script[src^="http"]');
    const scriptsWithoutSRI = Array.from(externalScripts).filter(
      (script) => !script.integrity
    );

    results.push({
      check: 'Subresource Integrity',
      passed: scriptsWithoutSRI.length === 0,
      message: 'External scripts should use SRI',
    });

    return results;
  }
}

// Security monitoring
class SecurityMonitor {
  static init() {
    // Monitor for XSS attempts
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.type === 'childList') {
          mutation.addedNodes.forEach((node) => {
            if (node.nodeType === Node.ELEMENT_NODE) {
              this.checkForDangerousContent(node);
            }
          });
        }
      });
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true,
    });

    // Monitor for clickjacking
    if (window.self !== window.top) {
      console.warn('Page is loaded in a frame - possible clickjacking');
      // Optionally break out of frame
      // window.top.location = window.self.location;
    }
  }

  static checkForDangerousContent(element) {
    // Check for inline scripts
    if (element.tagName === 'SCRIPT' && !element.src) {
      console.warn('Inline script detected:', element);
    }

    // Check for javascript: URLs
    const dangerousAttributes = ['href', 'src', 'action'];
    dangerousAttributes.forEach((attr) => {
      const value = element.getAttribute(attr);
      if (value && value.startsWith('javascript:')) {
        console.error('Dangerous JavaScript URL detected:', element);
        element.removeAttribute(attr);
      }
    });

    // Check for event handlers
    const eventHandlers = Array.from(element.attributes).filter((attr) =>
      attr.name.startsWith('on')
    );

    if (eventHandlers.length > 0) {
      console.warn('Inline event handlers detected:', element);
    }
  }
}

Conclusion

JavaScript security requires a multi-layered approach:

  • Input validation and sanitization
  • XSS prevention through proper encoding
  • CSRF protection with tokens
  • Secure authentication and authorization
  • HTTPS and secure communication
  • Content Security Policy implementation
  • Dependency security management

Key takeaways:

  • Never trust user input
  • Always encode output
  • Use security headers
  • Keep dependencies updated
  • Implement defense in depth
  • Regular security audits

Stay vigilant and keep learning about emerging security threats!