JavaScript Security

JavaScript Security Best Practices: Protecting Your Applications

Essential JavaScript security practices to protect against XSS, CSRF, injection attacks, and other vulnerabilities. Build secure, robust web applications.

By JavaScript Document Team
securityxsscsrfvalidationsanitization

JavaScript security is crucial for protecting web applications from various attacks and vulnerabilities. This guide covers essential security practices, common attack vectors, and defensive coding techniques to build secure applications.

Common Security Vulnerabilities

Cross-Site Scripting (XSS)

XSS attacks inject malicious scripts into web pages, potentially stealing user data or performing unauthorized actions.

// Vulnerable code - DON'T DO THIS
function displayUserContent(content) {
  document.getElementById('content').innerHTML = content; // Dangerous!
}

// Secure approach - Sanitize and escape content
class XSSProtection {
  static escapeHTML(str) {
    const div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
  }

  static sanitizeHTML(html) {
    // Create a temporary DOM element
    const temp = document.createElement('div');
    temp.innerHTML = html;

    // Remove dangerous elements and attributes
    const dangerous = temp.querySelectorAll(
      'script, object, embed, link, style, iframe'
    );
    dangerous.forEach((el) => el.remove());

    // Remove dangerous attributes
    const allElements = temp.querySelectorAll('*');
    allElements.forEach((el) => {
      for (let i = el.attributes.length - 1; i >= 0; i--) {
        const attr = el.attributes[i];
        if (
          attr.name.startsWith('on') ||
          (attr.name === 'href' && attr.value.startsWith('javascript:'))
        ) {
          el.removeAttribute(attr.name);
        }
      }
    });

    return temp.innerHTML;
  }

  static safeSetHTML(element, content, allowHTML = false) {
    if (allowHTML) {
      element.innerHTML = this.sanitizeHTML(content);
    } else {
      element.textContent = content; // Safe by default
    }
  }

  // Content Security Policy helper
  static setupCSP() {
    const cspMeta = document.createElement('meta');
    cspMeta.httpEquiv = 'Content-Security-Policy';
    cspMeta.content = [
      "default-src 'self'",
      "script-src 'self' 'unsafe-inline'", // Avoid 'unsafe-inline' in production
      "style-src 'self' 'unsafe-inline'",
      "img-src 'self' data: https:",
      "font-src 'self'",
      "connect-src 'self'",
      "frame-ancestors 'none'",
    ].join('; ');

    document.head.appendChild(cspMeta);
  }
}

// Safe content rendering
function displayUserContentSafely(content, allowHTML = false) {
  const container = document.getElementById('content');
  XSSProtection.safeSetHTML(container, content, allowHTML);
}

// Input validation and sanitization
class InputValidator {
  static validateEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email) && email.length <= 254;
  }

  static validateURL(url) {
    try {
      const urlObj = new URL(url);
      return ['http:', 'https:'].includes(urlObj.protocol);
    } catch {
      return false;
    }
  }

  static sanitizeInput(input, maxLength = 1000) {
    if (typeof input !== 'string') {
      return '';
    }

    return input
      .trim()
      .slice(0, maxLength)
      .replace(/[<>'"&]/g, (char) => {
        const map = {
          '<': '&lt;',
          '>': '&gt;',
          '"': '&quot;',
          "'": '&#x27;',
          '&': '&amp;',
        };
        return map[char];
      });
  }

  static validateAndSanitize(input, type, options = {}) {
    let sanitized = this.sanitizeInput(input, options.maxLength);

    switch (type) {
      case 'email':
        return this.validateEmail(sanitized) ? sanitized : null;
      case 'url':
        return this.validateURL(sanitized) ? sanitized : null;
      case 'alphanumeric':
        return /^[a-zA-Z0-9]+$/.test(sanitized) ? sanitized : null;
      case 'number':
        const num = parseFloat(sanitized);
        return !isNaN(num) && isFinite(num) ? num : null;
      default:
        return sanitized;
    }
  }
}

Cross-Site Request Forgery (CSRF)

CSRF attacks trick users into performing unintended actions on websites where they're authenticated.

class CSRFProtection {
  constructor() {
    this.token = this.generateToken();
    this.tokenExpiry = Date.now() + 30 * 60 * 1000; // 30 minutes
  }

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

  getToken() {
    // Refresh token if expired
    if (Date.now() > this.tokenExpiry) {
      this.token = this.generateToken();
      this.tokenExpiry = Date.now() + 30 * 60 * 1000;
    }
    return this.token;
  }

  // Add CSRF token to all forms
  protectForms() {
    const forms = document.querySelectorAll('form');
    forms.forEach((form) => {
      // Skip forms that already have CSRF token
      if (form.querySelector('input[name="csrf_token"]')) {
        return;
      }

      const csrfInput = document.createElement('input');
      csrfInput.type = 'hidden';
      csrfInput.name = 'csrf_token';
      csrfInput.value = this.getToken();

      form.appendChild(csrfInput);
    });
  }

  // Protect AJAX requests
  protectAjaxRequests() {
    const originalFetch = window.fetch;

    window.fetch = async (url, options = {}) => {
      // Add CSRF token to non-GET requests
      if (options.method && options.method.toUpperCase() !== 'GET') {
        options.headers = {
          ...options.headers,
          'X-CSRF-Token': this.getToken(),
        };
      }

      return originalFetch(url, options);
    };

    // Protect XMLHttpRequest
    const originalOpen = XMLHttpRequest.prototype.open;
    const originalSend = XMLHttpRequest.prototype.send;

    XMLHttpRequest.prototype.open = function (method, url, ...args) {
      this._method = method;
      return originalOpen.call(this, method, url, ...args);
    };

    XMLHttpRequest.prototype.send = function (data) {
      if (this._method && this._method.toUpperCase() !== 'GET') {
        this.setRequestHeader('X-CSRF-Token', csrfProtection.getToken());
      }
      return originalSend.call(this, data);
    };
  }

  // Verify referrer
  verifyReferrer(allowedDomains = []) {
    const referrer = document.referrer;
    const currentDomain = window.location.hostname;

    if (!referrer) {
      return false; // No referrer (suspicious)
    }

    try {
      const referrerDomain = new URL(referrer).hostname;
      return (
        referrerDomain === currentDomain ||
        allowedDomains.includes(referrerDomain)
      );
    } catch {
      return false;
    }
  }
}

// Initialize CSRF protection
const csrfProtection = new CSRFProtection();
csrfProtection.protectForms();
csrfProtection.protectAjaxRequests();

SQL Injection Prevention

While JavaScript doesn't directly interact with databases, it's important to understand injection prevention when building APIs.

class APISecurityHelper {
  // Validate and sanitize API parameters
  static validateAPIRequest(params, schema) {
    const errors = [];
    const sanitized = {};

    for (const [key, rules] of Object.entries(schema)) {
      const value = params[key];

      // Check required fields
      if (
        rules.required &&
        (value === undefined || value === null || value === '')
      ) {
        errors.push(`${key} is required`);
        continue;
      }

      // Skip validation for optional empty fields
      if (
        !rules.required &&
        (value === undefined || value === null || value === '')
      ) {
        continue;
      }

      // Type validation
      if (rules.type === 'string' && typeof value !== 'string') {
        errors.push(`${key} must be a string`);
        continue;
      }

      if (
        rules.type === 'number' &&
        (typeof value !== 'number' || isNaN(value))
      ) {
        errors.push(`${key} must be a number`);
        continue;
      }

      if (rules.type === 'email' && !InputValidator.validateEmail(value)) {
        errors.push(`${key} must be a valid email`);
        continue;
      }

      // Length validation
      if (rules.maxLength && value.length > rules.maxLength) {
        errors.push(`${key} cannot exceed ${rules.maxLength} characters`);
        continue;
      }

      if (rules.minLength && value.length < rules.minLength) {
        errors.push(`${key} must be at least ${rules.minLength} characters`);
        continue;
      }

      // Pattern validation
      if (rules.pattern && !rules.pattern.test(value)) {
        errors.push(`${key} format is invalid`);
        continue;
      }

      // Sanitize and store
      sanitized[key] =
        rules.type === 'string'
          ? InputValidator.sanitizeInput(value, rules.maxLength)
          : value;
    }

    return {
      isValid: errors.length === 0,
      errors,
      sanitized,
    };
  }

  // Rate limiting helper
  static createRateLimiter(maxRequests = 100, windowMs = 60000) {
    const requests = new Map();

    return function rateLimiter(identifier) {
      const now = Date.now();
      const windowStart = now - windowMs;

      // Clean old entries
      for (const [key, timestamps] of requests.entries()) {
        const validTimestamps = timestamps.filter((t) => t > windowStart);
        if (validTimestamps.length === 0) {
          requests.delete(key);
        } else {
          requests.set(key, validTimestamps);
        }
      }

      // Check current identifier
      const userRequests = requests.get(identifier) || [];
      const recentRequests = userRequests.filter((t) => t > windowStart);

      if (recentRequests.length >= maxRequests) {
        return {
          allowed: false,
          remaining: 0,
          resetTime: Math.min(...recentRequests) + windowMs,
        };
      }

      // Add current request
      recentRequests.push(now);
      requests.set(identifier, recentRequests);

      return {
        allowed: true,
        remaining: maxRequests - recentRequests.length,
        resetTime: now + windowMs,
      };
    };
  }
}

// Example API request validation
const userSchema = {
  username: {
    type: 'string',
    required: true,
    minLength: 3,
    maxLength: 20,
    pattern: /^[a-zA-Z0-9_]+$/,
  },
  email: {
    type: 'email',
    required: true,
    maxLength: 254,
  },
  age: {
    type: 'number',
    required: false,
    min: 13,
    max: 120,
  },
};

function handleUserRegistration(userData) {
  const validation = APISecurityHelper.validateAPIRequest(userData, userSchema);

  if (!validation.isValid) {
    return {
      success: false,
      errors: validation.errors,
    };
  }

  // Proceed with sanitized data
  const { sanitized } = validation;
  // ... register user with sanitized data
}

Secure Authentication

class SecureAuth {
  constructor() {
    this.tokenKey = 'auth_token';
    this.refreshKey = 'refresh_token';
    this.maxLoginAttempts = 5;
    this.lockoutDuration = 15 * 60 * 1000; // 15 minutes
  }

  // Secure password validation
  validatePassword(password) {
    const requirements = {
      minLength: password.length >= 8,
      hasUppercase: /[A-Z]/.test(password),
      hasLowercase: /[a-z]/.test(password),
      hasNumbers: /\d/.test(password),
      hasSpecialChars: /[!@#$%^&*(),.?":{}|<>]/.test(password),
      noCommonPatterns: !this.isCommonPassword(password),
    };

    const score = Object.values(requirements).filter(Boolean).length;

    return {
      isValid: score >= 5,
      score,
      requirements,
      strength: score < 3 ? 'weak' : score < 5 ? 'medium' : 'strong',
    };
  }

  isCommonPassword(password) {
    const common = [
      'password',
      '123456',
      'password123',
      'admin',
      'qwerty',
      'letmein',
      'welcome',
      'monkey',
      '1234567890',
    ];
    return common.includes(password.toLowerCase());
  }

  // Secure token storage
  setToken(token, refreshToken = null) {
    // Use secure storage if available
    if (window.crypto && window.crypto.subtle) {
      this.storeTokenSecurely(token, refreshToken);
    } else {
      // Fallback to sessionStorage (more secure than localStorage for tokens)
      sessionStorage.setItem(this.tokenKey, token);
      if (refreshToken) {
        sessionStorage.setItem(this.refreshKey, refreshToken);
      }
    }
  }

  async storeTokenSecurely(token, refreshToken) {
    try {
      // Generate encryption key
      const key = await window.crypto.subtle.generateKey(
        { name: 'AES-GCM', length: 256 },
        false,
        ['encrypt', 'decrypt']
      );

      // Store key reference securely
      const keyData = await window.crypto.subtle.exportKey('raw', key);
      const keyArray = new Uint8Array(keyData);

      // Encrypt token
      const iv = window.crypto.getRandomValues(new Uint8Array(12));
      const encodedToken = new TextEncoder().encode(token);

      const encryptedToken = await window.crypto.subtle.encrypt(
        { name: 'AES-GCM', iv },
        key,
        encodedToken
      );

      // Store encrypted data
      const tokenData = {
        encrypted: Array.from(new Uint8Array(encryptedToken)),
        iv: Array.from(iv),
        key: Array.from(keyArray),
      };

      sessionStorage.setItem(this.tokenKey, JSON.stringify(tokenData));
    } catch (error) {
      console.error('Failed to encrypt token:', error);
      // Fallback to plain storage
      sessionStorage.setItem(this.tokenKey, token);
    }
  }

  getToken() {
    const stored = sessionStorage.getItem(this.tokenKey);

    if (!stored) return null;

    try {
      const tokenData = JSON.parse(stored);
      if (tokenData.encrypted) {
        return this.decryptToken(tokenData);
      }
      return stored; // Plain token
    } catch {
      return stored; // Plain token
    }
  }

  async decryptToken(tokenData) {
    try {
      const key = await window.crypto.subtle.importKey(
        'raw',
        new Uint8Array(tokenData.key),
        { name: 'AES-GCM' },
        false,
        ['decrypt']
      );

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

      return new TextDecoder().decode(decrypted);
    } catch (error) {
      console.error('Failed to decrypt token:', error);
      return null;
    }
  }

  // Login attempt tracking
  trackLoginAttempt(username, success) {
    const key = `login_attempts_${username}`;
    const stored = localStorage.getItem(key);
    let attempts = stored
      ? JSON.parse(stored)
      : { count: 0, lastAttempt: 0, lockedUntil: 0 };

    if (success) {
      // Reset on successful login
      localStorage.removeItem(key);
      return { allowed: true };
    }

    // Check if still locked out
    if (attempts.lockedUntil > Date.now()) {
      return {
        allowed: false,
        lockedUntil: attempts.lockedUntil,
        reason: 'Account temporarily locked',
      };
    }

    // Increment failed attempts
    attempts.count++;
    attempts.lastAttempt = Date.now();

    // Lock account if max attempts reached
    if (attempts.count >= this.maxLoginAttempts) {
      attempts.lockedUntil = Date.now() + this.lockoutDuration;
      localStorage.setItem(key, JSON.stringify(attempts));

      return {
        allowed: false,
        lockedUntil: attempts.lockedUntil,
        reason: 'Too many failed attempts',
      };
    }

    localStorage.setItem(key, JSON.stringify(attempts));

    return {
      allowed: true,
      attemptsRemaining: this.maxLoginAttempts - attempts.count,
    };
  }

  // Secure logout
  logout() {
    // Clear all auth data
    sessionStorage.removeItem(this.tokenKey);
    sessionStorage.removeItem(this.refreshKey);
    localStorage.removeItem('user_preferences');

    // Clear any auth cookies
    document.cookie.split(';').forEach((cookie) => {
      const eqPos = cookie.indexOf('=');
      const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
      document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
    });

    // Redirect to login
    window.location.href = '/login';
  }

  // Token validation
  isTokenValid(token) {
    if (!token) return false;

    try {
      // Basic JWT validation (for client-side checks only)
      const parts = token.split('.');
      if (parts.length !== 3) return false;

      const payload = JSON.parse(atob(parts[1]));
      return payload.exp * 1000 > Date.now();
    } catch {
      return false;
    }
  }
}

Secure Data Handling

class SecureDataHandler {
  // Encrypt sensitive data before storing
  static async encryptData(data, password) {
    const encoder = new TextEncoder();
    const dataBuffer = encoder.encode(JSON.stringify(data));

    // Derive key from password
    const keyMaterial = await window.crypto.subtle.importKey(
      'raw',
      encoder.encode(password),
      { name: 'PBKDF2' },
      false,
      ['deriveKey']
    );

    const salt = window.crypto.getRandomValues(new Uint8Array(16));

    const key = await window.crypto.subtle.deriveKey(
      {
        name: 'PBKDF2',
        salt,
        iterations: 100000,
        hash: 'SHA-256',
      },
      keyMaterial,
      { name: 'AES-GCM', length: 256 },
      false,
      ['encrypt']
    );

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

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

  // Decrypt stored data
  static async decryptData(encryptedData, password) {
    const encoder = new TextEncoder();

    const keyMaterial = await window.crypto.subtle.importKey(
      'raw',
      encoder.encode(password),
      { name: 'PBKDF2' },
      false,
      ['deriveKey']
    );

    const key = await window.crypto.subtle.deriveKey(
      {
        name: 'PBKDF2',
        salt: new Uint8Array(encryptedData.salt),
        iterations: 100000,
        hash: 'SHA-256',
      },
      keyMaterial,
      { name: 'AES-GCM', length: 256 },
      false,
      ['decrypt']
    );

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

    const decoder = new TextDecoder();
    return JSON.parse(decoder.decode(decrypted));
  }

  // Secure random string generation
  static generateSecureRandom(length = 32) {
    const array = new Uint8Array(length);
    window.crypto.getRandomValues(array);
    return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join(
      ''
    );
  }

  // Hash sensitive data
  static async hashData(data) {
    const encoder = new TextEncoder();
    const dataBuffer = encoder.encode(data);
    const hashBuffer = await window.crypto.subtle.digest('SHA-256', dataBuffer);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
  }

  // Secure comparison to prevent timing attacks
  static secureCompare(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;
  }

  // Clear sensitive data from memory
  static clearSensitiveData(obj) {
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        if (typeof obj[key] === 'string') {
          // Overwrite string (though this may not work in all JS engines)
          obj[key] = '0'.repeat(obj[key].length);
        }
        delete obj[key];
      }
    }
  }
}

Content Security Policy

class CSPManager {
  static generateCSP(options = {}) {
    const defaultCSP = {
      'default-src': ["'self'"],
      'script-src': ["'self'"],
      'style-src': ["'self'", "'unsafe-inline'"],
      'img-src': ["'self'", 'data:', 'https:'],
      'font-src': ["'self'"],
      'connect-src': ["'self'"],
      'frame-src': ["'none'"],
      'object-src': ["'none'"],
      'base-uri': ["'self'"],
      'form-action': ["'self'"],
      'frame-ancestors': ["'none'"],
      'upgrade-insecure-requests': [],
    };

    const csp = { ...defaultCSP, ...options };

    return Object.entries(csp)
      .map(([directive, sources]) => {
        if (sources.length === 0) {
          return directive;
        }
        return `${directive} ${sources.join(' ')}`;
      })
      .join('; ');
  }

  static applyCSP(cspString) {
    const meta = document.createElement('meta');
    meta.httpEquiv = 'Content-Security-Policy';
    meta.content = cspString;
    document.head.appendChild(meta);
  }

  static setupSecurityHeaders() {
    // These would typically be set by the server, but can be enforced client-side too
    const securityMeta = [
      {
        httpEquiv: 'X-Content-Type-Options',
        content: 'nosniff',
      },
      {
        httpEquiv: 'X-Frame-Options',
        content: 'DENY',
      },
      {
        httpEquiv: 'X-XSS-Protection',
        content: '1; mode=block',
      },
      {
        httpEquiv: 'Referrer-Policy',
        content: 'strict-origin-when-cross-origin',
      },
    ];

    securityMeta.forEach((meta) => {
      const element = document.createElement('meta');
      element.httpEquiv = meta.httpEquiv;
      element.content = meta.content;
      document.head.appendChild(element);
    });
  }
}

Security Monitoring

class SecurityMonitor {
  constructor() {
    this.violations = [];
    this.suspiciousActivity = [];
    this.setupMonitoring();
  }

  setupMonitoring() {
    // CSP violation monitoring
    document.addEventListener('securitypolicyviolation', (event) => {
      this.handleCSPViolation(event);
    });

    // Monitor for suspicious DOM manipulation
    this.setupDOMMonitoring();

    // Monitor network requests
    this.setupNetworkMonitoring();

    // Monitor for unusual user behavior
    this.setupBehaviorMonitoring();
  }

  handleCSPViolation(event) {
    const violation = {
      type: 'csp_violation',
      directive: event.violatedDirective,
      blockedURI: event.blockedURI,
      sourceFile: event.sourceFile,
      lineNumber: event.lineNumber,
      timestamp: new Date().toISOString(),
    };

    this.violations.push(violation);
    console.warn('CSP Violation:', violation);

    // Report to server
    this.reportViolation(violation);
  }

  setupDOMMonitoring() {
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.type === 'childList') {
          mutation.addedNodes.forEach((node) => {
            if (node.nodeType === Node.ELEMENT_NODE) {
              this.checkSuspiciousElement(node);
            }
          });
        }
      });
    });

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

  checkSuspiciousElement(element) {
    // Check for suspicious scripts
    if (element.tagName === 'SCRIPT') {
      const suspicious = {
        type: 'suspicious_script',
        src: element.src,
        innerHTML: element.innerHTML.substring(0, 100),
        timestamp: new Date().toISOString(),
      };

      this.suspiciousActivity.push(suspicious);
      console.warn('Suspicious script detected:', suspicious);
    }

    // Check for suspicious iframes
    if (element.tagName === 'IFRAME') {
      const suspicious = {
        type: 'suspicious_iframe',
        src: element.src,
        timestamp: new Date().toISOString(),
      };

      this.suspiciousActivity.push(suspicious);
      console.warn('Suspicious iframe detected:', suspicious);
    }
  }

  setupNetworkMonitoring() {
    // Monitor fetch requests
    const originalFetch = window.fetch;
    window.fetch = async (...args) => {
      const url = args[0];
      const options = args[1] || {};

      // Check for suspicious requests
      if (this.isSuspiciousRequest(url, options)) {
        const suspicious = {
          type: 'suspicious_request',
          url: url.toString(),
          method: options.method || 'GET',
          timestamp: new Date().toISOString(),
        };

        this.suspiciousActivity.push(suspicious);
        console.warn('Suspicious request detected:', suspicious);
      }

      return originalFetch(...args);
    };
  }

  isSuspiciousRequest(url, options) {
    const urlString = url.toString();

    // Check for common attack patterns
    const suspiciousPatterns = [
      /javascript:/i,
      /data:text\/html/i,
      /vbscript:/i,
      /<script/i,
      /eval\(/i,
      /document\.write/i,
    ];

    return suspiciousPatterns.some((pattern) => pattern.test(urlString));
  }

  setupBehaviorMonitoring() {
    let clickCount = 0;
    let keyCount = 0;
    const timeWindow = 5000; // 5 seconds

    document.addEventListener('click', () => {
      clickCount++;
      setTimeout(() => clickCount--, timeWindow);

      if (clickCount > 20) {
        // Unusually high click rate
        this.reportSuspiciousBehavior('high_click_rate', { clickCount });
      }
    });

    document.addEventListener('keydown', () => {
      keyCount++;
      setTimeout(() => keyCount--, timeWindow);

      if (keyCount > 100) {
        // Unusually high key press rate
        this.reportSuspiciousBehavior('high_key_rate', { keyCount });
      }
    });
  }

  reportSuspiciousBehavior(type, data) {
    const behavior = {
      type,
      data,
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent,
      url: window.location.href,
    };

    this.suspiciousActivity.push(behavior);
    console.warn('Suspicious behavior detected:', behavior);
  }

  async reportViolation(violation) {
    try {
      await fetch('/api/security/violations', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(violation),
      });
    } catch (error) {
      console.error('Failed to report violation:', error);
    }
  }

  getSecurityReport() {
    return {
      violations: this.violations,
      suspiciousActivity: this.suspiciousActivity,
      generatedAt: new Date().toISOString(),
    };
  }
}

Best Practices Summary

// Security checklist and implementation
class SecurityChecklist {
  static getChecklist() {
    return {
      inputValidation: {
        description: 'Validate and sanitize all user inputs',
        implemented: false,
        priority: 'critical',
      },
      outputEncoding: {
        description: 'Encode output to prevent XSS',
        implemented: false,
        priority: 'critical',
      },
      csrfProtection: {
        description: 'Implement CSRF tokens',
        implemented: false,
        priority: 'high',
      },
      secureAuthentication: {
        description: 'Use secure authentication methods',
        implemented: false,
        priority: 'critical',
      },
      httpsOnly: {
        description: 'Use HTTPS for all communications',
        implemented: false,
        priority: 'critical',
      },
      cspImplemented: {
        description: 'Implement Content Security Policy',
        implemented: false,
        priority: 'high',
      },
      secureStorage: {
        description: 'Store sensitive data securely',
        implemented: false,
        priority: 'high',
      },
      rateLimiting: {
        description: 'Implement rate limiting',
        implemented: false,
        priority: 'medium',
      },
      securityHeaders: {
        description: 'Set appropriate security headers',
        implemented: false,
        priority: 'high',
      },
      dependencyChecks: {
        description: 'Regularly check for vulnerable dependencies',
        implemented: false,
        priority: 'medium',
      },
    };
  }

  static auditSecurity() {
    const checklist = this.getChecklist();

    // Check for XSS vulnerabilities
    checklist.outputEncoding.implemented = this.checkXSSProtection();

    // Check for CSRF protection
    checklist.csrfProtection.implemented = this.checkCSRFProtection();

    // Check HTTPS usage
    checklist.httpsOnly.implemented = location.protocol === 'https:';

    // Check CSP implementation
    checklist.cspImplemented.implemented = this.checkCSP();

    return checklist;
  }

  static checkXSSProtection() {
    // Basic check for innerHTML usage
    return !document.body.innerHTML.includes('<script>');
  }

  static checkCSRFProtection() {
    // Check if CSRF tokens are present in forms
    const forms = document.querySelectorAll('form');
    return Array.from(forms).some((form) =>
      form.querySelector('input[name="csrf_token"]')
    );
  }

  static checkCSP() {
    const cspMeta = document.querySelector(
      'meta[http-equiv="Content-Security-Policy"]'
    );
    return !!cspMeta;
  }
}

// Initialize security measures
document.addEventListener('DOMContentLoaded', () => {
  // Setup CSP
  CSPManager.setupSecurityHeaders();

  // Initialize security monitoring
  const securityMonitor = new SecurityMonitor();

  // Setup CSRF protection
  const csrfProtection = new CSRFProtection();
  csrfProtection.protectForms();
  csrfProtection.protectAjaxRequests();

  // Run security audit
  const audit = SecurityChecklist.auditSecurity();
  console.log('Security Audit:', audit);
});

Conclusion

JavaScript security requires a multi-layered approach combining input validation, output encoding, secure authentication, and proper data handling. By implementing these security practices, monitoring for threats, and regularly auditing your applications, you can build robust defenses against common web vulnerabilities. Remember that security is an ongoing process that requires constant vigilance and updates as new threats emerge.