javascript

examples

examples.js
/**
 * 21.1 Security Fundamentals - Examples
 *
 * Demonstrates common security vulnerabilities and prevention techniques
 * WARNING: These examples show vulnerable code for educational purposes.
 * Never use vulnerable patterns in production!
 */

// ============================================
// XSS (CROSS-SITE SCRIPTING) EXAMPLES
// ============================================

console.log('=== XSS Prevention Examples ===\n');

/**
 * HTML Escaping - Prevents XSS by encoding special characters
 */
const HTMLEscaper = {
  // Character entity map
  entities: {
    '&': '&',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
    '/': '&#x2F;',
    '`': '&#x60;',
    '=': '&#x3D;',
  },

  // Escape HTML special characters
  escape(str) {
    if (typeof str !== 'string') return str;
    return str.replace(/[&<>"'`=/]/g, (char) => this.entities[char]);
  },

  // Unescape HTML entities
  unescape(str) {
    if (typeof str !== 'string') return str;

    const reverseEntities = {};
    for (const [char, entity] of Object.entries(this.entities)) {
      reverseEntities[entity] = char;
    }

    return str.replace(
      /&(?:amp|lt|gt|quot|#x27|#x2F|#x60|#x3D);/g,
      (entity) => reverseEntities[entity] || entity
    );
  },
};

// Demo
console.log('HTML Escaping:');
const maliciousInput = '<script>alert("XSS")</script>';
console.log('  Original:', maliciousInput);
console.log('  Escaped:', HTMLEscaper.escape(maliciousInput));
console.log('');

/**
 * Safe DOM manipulation utilities
 */
const SafeDOM = {
  // Create text node (always safe)
  createText(text) {
    return document.createTextNode(text);
  },

  // Set element text content safely
  setText(element, text) {
    element.textContent = text;
    return element;
  },

  // Create element with safe attributes
  createElement(tag, attributes = {}, textContent = '') {
    const element = document.createElement(tag);

    for (const [key, value] of Object.entries(attributes)) {
      // Validate attribute names
      if (!/^[a-zA-Z][\w-]*$/.test(key)) {
        throw new Error(`Invalid attribute name: ${key}`);
      }

      // Handle special attributes
      if (key === 'href' || key === 'src') {
        if (!this.isValidUrl(value)) {
          throw new Error(`Invalid URL for ${key}`);
        }
      }

      // Handle event handlers - reject them
      if (key.startsWith('on')) {
        throw new Error('Event handler attributes not allowed');
      }

      element.setAttribute(key, value);
    }

    if (textContent) {
      element.textContent = textContent;
    }

    return element;
  },

  // Validate URL (prevent javascript: protocol)
  isValidUrl(url) {
    if (!url) return false;

    try {
      const parsed = new URL(url, 'https://example.com');
      return ['http:', 'https:', 'mailto:'].includes(parsed.protocol);
    } catch {
      // Relative URLs are OK
      return !url.toLowerCase().includes('javascript:');
    }
  },

  // Sanitize HTML (very basic - use DOMPurify in production)
  sanitizeHTML(html) {
    // Remove script tags
    let clean = html.replace(
      /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
      ''
    );

    // Remove event handlers
    clean = clean.replace(/\s*on\w+\s*=\s*("[^"]*"|'[^']*'|[^\s>]*)/gi, '');

    // Remove javascript: URLs
    clean = clean.replace(/javascript\s*:/gi, '');

    return clean;
  },
};

console.log('Safe URL Validation:');
console.log(
  '  https://example.com:',
  SafeDOM.isValidUrl('https://example.com')
);
console.log(
  '  javascript:alert(1):',
  SafeDOM.isValidUrl('javascript:alert(1)')
);
console.log('  /relative/path:', SafeDOM.isValidUrl('/relative/path'));
console.log('');

// ============================================
// CSRF PROTECTION EXAMPLES
// ============================================

console.log('=== CSRF Protection Examples ===\n');

/**
 * CSRF Token Generator and Validator
 */
class CSRFProtection {
  constructor() {
    this.tokens = new Map();
    this.tokenLifetime = 3600000; // 1 hour
  }

  // Generate secure random token
  generateToken() {
    // In browser, use crypto.getRandomValues
    // In Node.js, use crypto.randomBytes
    const array = new Uint8Array(32);

    if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
      crypto.getRandomValues(array);
    } else {
      // Fallback for Node.js without webcrypto
      const nodeCrypto = require('crypto');
      const buffer = nodeCrypto.randomBytes(32);
      array.set(buffer);
    }

    return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join(
      ''
    );
  }

  // Create token for a session
  createToken(sessionId) {
    const token = this.generateToken();
    const expiry = Date.now() + this.tokenLifetime;

    this.tokens.set(sessionId, { token, expiry });

    return token;
  }

  // Validate token
  validateToken(sessionId, token) {
    const stored = this.tokens.get(sessionId);

    if (!stored) {
      return { valid: false, reason: 'No token found for session' };
    }

    if (Date.now() > stored.expiry) {
      this.tokens.delete(sessionId);
      return { valid: false, reason: 'Token expired' };
    }

    // Constant-time comparison to prevent timing attacks
    if (!this.timingSafeEqual(token, stored.token)) {
      return { valid: false, reason: 'Token mismatch' };
    }

    return { valid: true };
  }

  // Constant-time string comparison
  timingSafeEqual(a, b) {
    if (typeof a !== 'string' || typeof b !== 'string') {
      return false;
    }

    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;
  }

  // Rotate token (call after successful validation)
  rotateToken(sessionId) {
    if (this.tokens.has(sessionId)) {
      return this.createToken(sessionId);
    }
    return null;
  }

  // Cleanup expired tokens
  cleanup() {
    const now = Date.now();
    for (const [sessionId, data] of this.tokens) {
      if (now > data.expiry) {
        this.tokens.delete(sessionId);
      }
    }
  }
}

// Demo CSRF protection
const csrf = new CSRFProtection();
const sessionId = 'user-123';
const token = csrf.createToken(sessionId);

console.log('CSRF Token generated:', token.substring(0, 20) + '...');
console.log('Validate correct token:', csrf.validateToken(sessionId, token));
console.log(
  'Validate wrong token:',
  csrf.validateToken(sessionId, 'invalid-token')
);
console.log('');

/**
 * Fetch wrapper with CSRF protection
 */
function createSecureFetch(csrfToken) {
  return async function secureFetch(url, options = {}) {
    const isModifyingMethod = ['POST', 'PUT', 'DELETE', 'PATCH'].includes(
      options.method?.toUpperCase()
    );

    if (isModifyingMethod) {
      options.headers = {
        ...options.headers,
        'X-CSRF-Token': csrfToken,
      };
    }

    // Include credentials for same-origin requests
    options.credentials = options.credentials || 'same-origin';

    return fetch(url, options);
  };
}

console.log('Secure fetch wrapper created with CSRF token');
console.log('');

// ============================================
// INPUT VALIDATION AND SANITIZATION
// ============================================

console.log('=== Input Validation Examples ===\n');

/**
 * Comprehensive input validator
 */
class InputValidator {
  // Validate email
  static isValidEmail(email) {
    if (typeof email !== 'string') return false;
    // RFC 5322 simplified regex
    const emailRegex =
      /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
    return emailRegex.test(email) && email.length <= 254;
  }

  // Validate URL
  static isValidUrl(url, allowedProtocols = ['http:', 'https:']) {
    try {
      const parsed = new URL(url);
      return allowedProtocols.includes(parsed.protocol);
    } catch {
      return false;
    }
  }

  // Validate integer
  static isValidInteger(value, min = -Infinity, max = Infinity) {
    const num = Number(value);
    return Number.isInteger(num) && num >= min && num <= max;
  }

  // Validate string length
  static isValidLength(str, minLen = 0, maxLen = Infinity) {
    return (
      typeof str === 'string' && str.length >= minLen && str.length <= maxLen
    );
  }

  // Validate alphanumeric
  static isAlphanumeric(str) {
    return typeof str === 'string' && /^[a-zA-Z0-9]+$/.test(str);
  }

  // Validate against whitelist
  static isInWhitelist(value, whitelist) {
    return whitelist.includes(value);
  }

  // Validate JSON
  static isValidJSON(str) {
    try {
      JSON.parse(str);
      return true;
    } catch {
      return false;
    }
  }

  // Validate file extension
  static isValidFileExtension(filename, allowedExtensions) {
    const ext = filename.toLowerCase().split('.').pop();
    return allowedExtensions.includes(ext);
  }

  // Validate path (no traversal)
  static isValidPath(path) {
    if (typeof path !== 'string') return false;
    // Check for path traversal
    const normalized = path.replace(/\\/g, '/');
    return !normalized.includes('..') && !normalized.startsWith('/');
  }
}

console.log('Input Validation:');
console.log('  Valid email:', InputValidator.isValidEmail('user@example.com'));
console.log('  Invalid email:', InputValidator.isValidEmail('not-an-email'));
console.log('  Valid URL:', InputValidator.isValidUrl('https://example.com'));
console.log(
  '  JavaScript URL:',
  InputValidator.isValidUrl('javascript:alert(1)')
);
console.log(
  '  Path traversal:',
  InputValidator.isValidPath('../../../etc/passwd')
);
console.log('  Safe path:', InputValidator.isValidPath('uploads/image.jpg'));
console.log('');

/**
 * Input sanitizer
 */
class InputSanitizer {
  // Remove control characters
  static removeControlChars(str) {
    if (typeof str !== 'string') return str;
    // eslint-disable-next-line no-control-regex
    return str.replace(/[\x00-\x1F\x7F]/g, '');
  }

  // Trim and normalize whitespace
  static normalizeWhitespace(str) {
    if (typeof str !== 'string') return str;
    return str.trim().replace(/\s+/g, ' ');
  }

  // Remove null bytes
  static removeNullBytes(str) {
    if (typeof str !== 'string') return str;
    return str.replace(/\0/g, '');
  }

  // Truncate string
  static truncate(str, maxLength) {
    if (typeof str !== 'string') return str;
    return str.slice(0, maxLength);
  }

  // Sanitize filename
  static sanitizeFilename(filename) {
    if (typeof filename !== 'string') return '';

    // Remove path components
    filename = filename.split(/[\\/]/).pop() || '';

    // Remove dangerous characters
    filename = filename.replace(/[<>:"/\\|?*\x00-\x1F]/g, '');

    // Remove leading dots
    filename = filename.replace(/^\.+/, '');

    // Truncate
    filename = filename.slice(0, 255);

    return filename || 'unnamed';
  }

  // Sanitize for SQL LIKE clause
  static escapeSqlLike(str) {
    if (typeof str !== 'string') return str;
    return str.replace(/[%_\\]/g, '\\$&');
  }

  // Sanitize for regex
  static escapeRegex(str) {
    if (typeof str !== 'string') return str;
    return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  }
}

console.log('Input Sanitization:');
console.log(
  '  Control chars removed:',
  InputSanitizer.removeControlChars('Hello\x00World\x1F!')
);
console.log(
  '  Filename sanitized:',
  InputSanitizer.sanitizeFilename('../../../etc/passwd')
);
console.log(
  '  Dangerous filename:',
  InputSanitizer.sanitizeFilename('<script>alert("xss")</script>.js')
);
console.log('  SQL LIKE escaped:', InputSanitizer.escapeSqlLike('100%'));
console.log('');

// ============================================
// INJECTION PREVENTION
// ============================================

console.log('=== Injection Prevention Examples ===\n');

/**
 * SQL Query Builder (parameterized)
 */
class SafeQueryBuilder {
  constructor() {
    this.query = '';
    this.params = [];
    this.paramIndex = 0;
  }

  // Start SELECT
  select(...columns) {
    const safeColumns = columns.map((col) => {
      // Only allow valid column names
      if (!/^[\w.]+$/.test(col)) {
        throw new Error(`Invalid column name: ${col}`);
      }
      return col;
    });

    this.query = `SELECT ${safeColumns.join(', ')}`;
    return this;
  }

  // FROM clause
  from(table) {
    // Validate table name
    if (!/^\w+$/.test(table)) {
      throw new Error(`Invalid table name: ${table}`);
    }
    this.query += ` FROM ${table}`;
    return this;
  }

  // WHERE clause with parameterized values
  where(column, operator, value) {
    // Validate column
    if (!/^[\w.]+$/.test(column)) {
      throw new Error(`Invalid column name: ${column}`);
    }

    // Validate operator
    const allowedOperators = [
      '=',
      '!=',
      '<>',
      '>',
      '<',
      '>=',
      '<=',
      'LIKE',
      'IN',
    ];
    if (!allowedOperators.includes(operator.toUpperCase())) {
      throw new Error(`Invalid operator: ${operator}`);
    }

    // Use parameterized value
    this.paramIndex++;
    const placeholder = `$${this.paramIndex}`;

    this.query += ` WHERE ${column} ${operator} ${placeholder}`;
    this.params.push(value);

    return this;
  }

  // AND clause
  and(column, operator, value) {
    if (!/^[\w.]+$/.test(column)) {
      throw new Error(`Invalid column name: ${column}`);
    }

    this.paramIndex++;
    const placeholder = `$${this.paramIndex}`;

    this.query += ` AND ${column} ${operator} ${placeholder}`;
    this.params.push(value);

    return this;
  }

  // Build final query
  build() {
    return {
      query: this.query,
      params: this.params,
    };
  }
}

// Demo safe query building
const queryBuilder = new SafeQueryBuilder();
const result = queryBuilder
  .select('id', 'username', 'email')
  .from('users')
  .where('username', '=', 'john')
  .and('active', '=', true)
  .build();

console.log('Safe Query Builder:');
console.log('  Query:', result.query);
console.log('  Params:', result.params);
console.log('');

/**
 * Command execution sanitizer
 */
class SafeCommand {
  // Escape shell argument
  static escapeShellArg(arg) {
    if (typeof arg !== 'string') {
      arg = String(arg);
    }

    // In Unix: wrap in single quotes and escape single quotes
    return `'${arg.replace(/'/g, "'\\''")}'`;
  }

  // Validate command name against whitelist
  static isAllowedCommand(cmd, whitelist) {
    return whitelist.includes(cmd);
  }

  // Build safe command with arguments
  static buildCommand(cmd, args, whitelist) {
    if (!this.isAllowedCommand(cmd, whitelist)) {
      throw new Error(`Command not allowed: ${cmd}`);
    }

    const escapedArgs = args.map((arg) => this.escapeShellArg(arg));
    return `${cmd} ${escapedArgs.join(' ')}`;
  }
}

console.log('Safe Command Execution:');
const allowedCommands = ['ls', 'cat', 'echo'];
try {
  const safeCmd = SafeCommand.buildCommand(
    'echo',
    ['Hello; rm -rf /', '$(whoami)'],
    allowedCommands
  );
  console.log('  Safe command:', safeCmd);
} catch (e) {
  console.log('  Error:', e.message);
}
console.log('');

// ============================================
// CONTENT SECURITY POLICY BUILDER
// ============================================

console.log('=== Content Security Policy Builder ===\n');

/**
 * CSP Header Builder
 */
class CSPBuilder {
  constructor() {
    this.directives = new Map();
  }

  // Add directive
  addDirective(name, ...values) {
    const existing = this.directives.get(name) || [];
    this.directives.set(name, [...existing, ...values]);
    return this;
  }

  // Common presets
  defaultSrc(...values) {
    return this.addDirective('default-src', ...values);
  }

  scriptSrc(...values) {
    return this.addDirective('script-src', ...values);
  }

  styleSrc(...values) {
    return this.addDirective('style-src', ...values);
  }

  imgSrc(...values) {
    return this.addDirective('img-src', ...values);
  }

  connectSrc(...values) {
    return this.addDirective('connect-src', ...values);
  }

  fontSrc(...values) {
    return this.addDirective('font-src', ...values);
  }

  frameSrc(...values) {
    return this.addDirective('frame-src', ...values);
  }

  frameAncestors(...values) {
    return this.addDirective('frame-ancestors', ...values);
  }

  formAction(...values) {
    return this.addDirective('form-action', ...values);
  }

  baseUri(...values) {
    return this.addDirective('base-uri', ...values);
  }

  objectSrc(...values) {
    return this.addDirective('object-src', ...values);
  }

  reportUri(uri) {
    return this.addDirective('report-uri', uri);
  }

  upgradeInsecureRequests() {
    return this.addDirective('upgrade-insecure-requests');
  }

  // Generate nonce
  static generateNonce() {
    const array = new Uint8Array(16);
    if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
      crypto.getRandomValues(array);
    } else {
      const nodeCrypto = require('crypto');
      const buffer = nodeCrypto.randomBytes(16);
      array.set(buffer);
    }
    return Buffer.from(array).toString('base64');
  }

  // Build header value
  build() {
    const parts = [];

    for (const [directive, values] of this.directives) {
      if (values.length === 0) {
        parts.push(directive);
      } else {
        parts.push(`${directive} ${values.join(' ')}`);
      }
    }

    return parts.join('; ');
  }

  // Common secure presets
  static strict() {
    const nonce = CSPBuilder.generateNonce();

    return {
      policy: new CSPBuilder()
        .defaultSrc("'self'")
        .scriptSrc("'self'", `'nonce-${nonce}'`)
        .styleSrc("'self'", "'unsafe-inline'")
        .imgSrc("'self'", 'data:', 'https:')
        .fontSrc("'self'")
        .connectSrc("'self'")
        .frameAncestors("'none'")
        .baseUri("'self'")
        .formAction("'self'")
        .objectSrc("'none'")
        .build(),
      nonce,
    };
  }
}

// Demo CSP Builder
const cspBuilder = new CSPBuilder()
  .defaultSrc("'self'")
  .scriptSrc("'self'", "'nonce-abc123'")
  .styleSrc("'self'", "'unsafe-inline'")
  .imgSrc("'self'", 'data:', 'https://cdn.example.com')
  .connectSrc("'self'", 'https://api.example.com')
  .frameAncestors("'none'")
  .baseUri("'self'");

console.log('CSP Header:');
console.log('  ' + cspBuilder.build());
console.log('');

// ============================================
// SECURITY HEADERS CONFIGURATION
// ============================================

console.log('=== Security Headers Configuration ===\n');

/**
 * Security headers configuration for different environments
 */
const SecurityHeaders = {
  // Production headers (strict)
  production: {
    'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
    '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=()',
    'Content-Security-Policy': CSPBuilder.strict().policy,
  },

  // Development headers (relaxed)
  development: {
    'X-Content-Type-Options': 'nosniff',
    'X-Frame-Options': 'SAMEORIGIN',
  },

  // Apply headers to response
  apply(res, env = 'production') {
    const headers = this[env] || this.production;

    for (const [key, value] of Object.entries(headers)) {
      res.setHeader(key, value);
    }
  },

  // Generate middleware
  middleware(env = 'production') {
    const headers = this[env] || this.production;

    return (req, res, next) => {
      for (const [key, value] of Object.entries(headers)) {
        res.setHeader(key, value);
      }
      next();
    };
  },
};

console.log('Production Security Headers:');
for (const [header, value] of Object.entries(SecurityHeaders.production)) {
  const displayValue =
    value.length > 60 ? value.substring(0, 60) + '...' : value;
  console.log(`  ${header}: ${displayValue}`);
}
console.log('');

// ============================================
// SECURE DATA HANDLING
// ============================================

console.log('=== Secure Data Handling ===\n');

/**
 * Secure data masking for logs
 */
class DataMasker {
  static patterns = {
    email: {
      regex: /([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g,
      mask: (match, local, domain) => {
        const maskedLocal = local[0] + '*'.repeat(local.length - 1);
        return `${maskedLocal}@${domain}`;
      },
    },
    creditCard: {
      regex: /\b(\d{4})[\s-]?(\d{4})[\s-]?(\d{4})[\s-]?(\d{4})\b/g,
      mask: (match, g1, g2, g3, g4) => `****-****-****-${g4}`,
    },
    ssn: {
      regex: /\b(\d{3})[-\s]?(\d{2})[-\s]?(\d{4})\b/g,
      mask: () => '***-**-****',
    },
    phone: {
      regex: /\b(\+\d{1,3}[-\s]?)?(\(?\d{3}\)?[-\s]?)(\d{3}[-\s]?)(\d{4})\b/g,
      mask: (match, country, area, mid, last) =>
        `${country || ''}***-***-${last}`,
    },
    ipv4: {
      regex: /\b(\d{1,3}\.\d{1,3})\.\d{1,3}\.\d{1,3}\b/g,
      mask: (match, first) => `${first}.xxx.xxx`,
    },
  };

  static mask(text, types = Object.keys(this.patterns)) {
    let masked = text;

    for (const type of types) {
      const pattern = this.patterns[type];
      if (pattern) {
        masked = masked.replace(pattern.regex, pattern.mask);
      }
    }

    return masked;
  }

  static maskObject(
    obj,
    sensitiveFields = ['password', 'token', 'secret', 'key', 'auth']
  ) {
    if (typeof obj !== 'object' || obj === null) {
      return obj;
    }

    const masked = Array.isArray(obj) ? [] : {};

    for (const [key, value] of Object.entries(obj)) {
      const lowerKey = key.toLowerCase();

      // Check if field name indicates sensitive data
      const isSensitive = sensitiveFields.some((field) =>
        lowerKey.includes(field.toLowerCase())
      );

      if (isSensitive) {
        masked[key] = '[REDACTED]';
      } else if (typeof value === 'object' && value !== null) {
        masked[key] = this.maskObject(value, sensitiveFields);
      } else if (typeof value === 'string') {
        masked[key] = this.mask(value);
      } else {
        masked[key] = value;
      }
    }

    return masked;
  }
}

console.log('Data Masking:');
const sensitiveText = `
User: john@example.com
Card: 4111-1111-1111-1234
SSN: 123-45-6789
Phone: +1 (555) 123-4567
IP: 192.168.1.100
`;

console.log('Original:', sensitiveText);
console.log('Masked:', DataMasker.mask(sensitiveText));

const sensitiveObject = {
  user: 'john@example.com',
  password: 'secret123',
  apiKey: 'sk_live_1234567890',
  profile: {
    name: 'John Doe',
    authToken: 'bearer xyz123',
  },
};

console.log(
  '\nMasked Object:',
  JSON.stringify(DataMasker.maskObject(sensitiveObject), null, 2)
);
console.log('');

// ============================================
// SUMMARY
// ============================================

console.log('=== Security Fundamentals Summary ===\n');

console.log('Key Security Components Demonstrated:');
console.log('1. HTML Escaping - Prevent XSS attacks');
console.log('2. CSRF Protection - Token-based request validation');
console.log('3. Input Validation - Whitelist and type checking');
console.log('4. Input Sanitization - Clean and normalize input');
console.log('5. Injection Prevention - Parameterized queries');
console.log('6. CSP Builder - Content Security Policy configuration');
console.log('7. Security Headers - HTTP security headers');
console.log('8. Data Masking - Protect sensitive data in logs');

console.log('\nRemember: Security is a process, not a feature!');

// Export for testing
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    HTMLEscaper,
    SafeDOM,
    CSRFProtection,
    InputValidator,
    InputSanitizer,
    SafeQueryBuilder,
    SafeCommand,
    CSPBuilder,
    SecurityHeaders,
    DataMasker,
  };
}
Examples - JavaScript Tutorial | DeepML