+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 354 of 355

πŸ”’ Code Security Review: Static Analysis for TypeScript

Master code security reviews using static analysis tools for TypeScript projects. Learn to identify vulnerabilities, implement security best practices, and automate security checks! πŸ›‘οΈ

πŸ’ŽAdvanced
35 min read

Prerequisites

  • Strong TypeScript fundamentals πŸ“
  • Understanding of security concepts πŸ”
  • Experience with TypeScript projects πŸ—οΈ
  • Basic knowledge of CI/CD pipelines πŸ”„

What you'll learn

  • Set up static analysis tools for security πŸ›‘οΈ
  • Identify common security vulnerabilities πŸ”
  • Implement automated security checks ⚑
  • Create security-focused code reviews πŸ“‹

🎯 Introduction

Welcome to the world of code security reviews! πŸ”’ In today’s digital landscape, security vulnerabilities can have devastating consequences. Static analysis tools are your first line of defense, helping you catch security issues before they reach production.

In this tutorial, we’ll explore how to implement comprehensive security reviews using static analysis tools specifically designed for TypeScript projects. Get ready to become a security champion! πŸ’ͺ

πŸ“š Understanding Static Security Analysis

Think of static analysis as having a tireless security expert reviewing every line of your code 24/7! πŸ€– These tools analyze your source code without executing it, identifying potential vulnerabilities based on patterns and rules.

Why Static Analysis for Security? πŸ€”

// 🚨 Without static analysis, these vulnerabilities might slip through:

// ❌ SQL Injection vulnerability
const getUserData = (userId: string) => {
  const query = `SELECT * FROM users WHERE id = '${userId}'`; // πŸ’₯ Dangerous!
  return database.execute(query);
};

// ❌ XSS vulnerability
const displayMessage = (message: string) => {
  document.innerHTML = message; // 🚨 Unescaped input!
};

// ❌ Path traversal vulnerability
const readFile = (filename: string) => {
  return fs.readFileSync(`./uploads/${filename}`); // πŸ“ No validation!
};

πŸ”§ Essential Security Analysis Tools

Let’s set up a comprehensive security toolkit for TypeScript! πŸ› οΈ

1. ESLint Security Plugin

# πŸ“¦ Install security-focused ESLint plugins
npm install --save-dev @typescript-eslint/eslint-plugin eslint-plugin-security

# πŸ”§ Configure .eslintrc.json
{
  "extends": [
    "plugin:@typescript-eslint/recommended",
    "plugin:security/recommended"
  ],
  "plugins": ["@typescript-eslint", "security"],
  "rules": {
    // πŸ›‘οΈ Security rules
    "security/detect-object-injection": "error",
    "security/detect-non-literal-regexp": "error",
    "security/detect-unsafe-regex": "error",
    "security/detect-buffer-noassert": "error",
    "security/detect-child-process": "warn",
    "security/detect-disable-mustache-escape": "error",
    "security/detect-eval-with-expression": "error",
    "security/detect-no-csrf-before-method-override": "error",
    "security/detect-non-literal-fs-filename": "warn",
    "security/detect-non-literal-require": "warn",
    "security/detect-possible-timing-attacks": "warn"
  }
}

2. Semgrep for TypeScript

# πŸš€ Install Semgrep
pip install semgrep

# πŸ“‹ Create .semgrep.yml configuration
# .semgrep.yml
rules:
  - id: typescript-sql-injection
    patterns:
      - pattern: |
          $QUERY = `... ${$USER_INPUT} ...`
          $DB.$METHOD($QUERY)
    message: "Potential SQL injection vulnerability"
    languages: [typescript]
    severity: ERROR

  - id: typescript-xss
    patterns:
      - pattern: |
          $ELEMENT.innerHTML = $USER_INPUT
    message: "Potential XSS vulnerability"
    languages: [typescript]
    severity: ERROR

  - id: typescript-hardcoded-secret
    patterns:
      - pattern-regex: '(password|secret|api[_-]?key)\s*[:=]\s*["\'][^"\']+["\']'
    message: "Hardcoded secret detected"
    languages: [typescript]
    severity: ERROR

3. SonarQube with TypeScript Support

// πŸ” sonar-project.properties
sonar.projectKey=my-typescript-project
sonar.sources=src
sonar.exclusions=**/*.test.ts,**/*.spec.ts
sonar.typescript.lcov.reportPaths=coverage/lcov.info
sonar.javascript.globals=angular,goog,google,moment,JSON

πŸ’‘ Practical Security Analysis Examples

Example 1: Authentication Vulnerability Scanner πŸ”

// πŸ“‚ security-scanner.ts
import { ESLint } from 'eslint';
import * as fs from 'fs';
import * as path from 'path';

interface SecurityIssue {
  file: string;
  line: number;
  column: number;
  severity: 'low' | 'medium' | 'high' | 'critical';
  message: string;
  rule: string;
}

class SecurityScanner {
  private eslint: ESLint;
  private customRules: Map<string, RegExp>;

  constructor() {
    // πŸ”§ Initialize ESLint with security configuration
    this.eslint = new ESLint({
      overrideConfig: {
        extends: ['plugin:security/recommended'],
        plugins: ['security']
      }
    });

    // 🎯 Custom security patterns
    this.customRules = new Map([
      ['hardcoded-jwt-secret', /jwt\.sign\([^,]+,\s*["'][^"']+["']/g],
      ['weak-crypto', /crypto\.createHash\(['"]md5['"]\)/g],
      ['unsafe-random', /Math\.random\(\)/g],
      ['no-rate-limiting', /app\.(get|post|put|delete)\([^)]+\)\s*(?!.*rateLimit)/g]
    ]);
  }

  async scanProject(projectPath: string): Promise<SecurityIssue[]> {
    const issues: SecurityIssue[] = [];

    // πŸ” Run ESLint security checks
    const results = await this.eslint.lintFiles([`${projectPath}/**/*.ts`]);
    
    for (const result of results) {
      for (const message of result.messages) {
        if (message.ruleId?.includes('security')) {
          issues.push({
            file: result.filePath,
            line: message.line,
            column: message.column,
            severity: this.mapSeverity(message.severity),
            message: message.message,
            rule: message.ruleId
          });
        }
      }
    }

    // 🎯 Run custom security checks
    const customIssues = await this.runCustomChecks(projectPath);
    issues.push(...customIssues);

    return issues;
  }

  private async runCustomChecks(projectPath: string): Promise<SecurityIssue[]> {
    const issues: SecurityIssue[] = [];
    const files = await this.getTypeScriptFiles(projectPath);

    for (const file of files) {
      const content = await fs.promises.readFile(file, 'utf-8');
      const lines = content.split('\n');

      // πŸ” Check each custom rule
      for (const [ruleName, pattern] of this.customRules) {
        const matches = content.matchAll(pattern);
        
        for (const match of matches) {
          const position = this.getLineAndColumn(content, match.index!);
          issues.push({
            file,
            line: position.line,
            column: position.column,
            severity: this.getRuleSeverity(ruleName),
            message: this.getRuleMessage(ruleName),
            rule: ruleName
          });
        }
      }
    }

    return issues;
  }

  private mapSeverity(eslintSeverity: number): SecurityIssue['severity'] {
    return eslintSeverity === 2 ? 'high' : 'medium';
  }

  private getRuleSeverity(ruleName: string): SecurityIssue['severity'] {
    const criticalRules = ['hardcoded-jwt-secret', 'weak-crypto'];
    return criticalRules.includes(ruleName) ? 'critical' : 'high';
  }

  private getRuleMessage(ruleName: string): string {
    const messages: Record<string, string> = {
      'hardcoded-jwt-secret': 'JWT secret should not be hardcoded',
      'weak-crypto': 'MD5 is cryptographically broken, use SHA-256 or stronger',
      'unsafe-random': 'Math.random() is not cryptographically secure',
      'no-rate-limiting': 'API endpoint lacks rate limiting'
    };
    return messages[ruleName] || 'Security issue detected';
  }

  private getLineAndColumn(content: string, index: number): { line: number; column: number } {
    const lines = content.substring(0, index).split('\n');
    return {
      line: lines.length,
      column: lines[lines.length - 1].length + 1
    };
  }

  private async getTypeScriptFiles(dir: string): Promise<string[]> {
    const files: string[] = [];
    const entries = await fs.promises.readdir(dir, { withFileTypes: true });

    for (const entry of entries) {
      const fullPath = path.join(dir, entry.name);
      if (entry.isDirectory() && !entry.name.includes('node_modules')) {
        files.push(...await this.getTypeScriptFiles(fullPath));
      } else if (entry.isFile() && entry.name.endsWith('.ts')) {
        files.push(fullPath);
      }
    }

    return files;
  }
}

// πŸš€ Usage example
const scanner = new SecurityScanner();
scanner.scanProject('./src').then(issues => {
  console.log(`πŸ” Found ${issues.length} security issues`);
  
  // πŸ“Š Group by severity
  const bySeverity = issues.reduce((acc, issue) => {
    acc[issue.severity] = (acc[issue.severity] || 0) + 1;
    return acc;
  }, {} as Record<string, number>);

  console.log('πŸ“ˆ Issues by severity:', bySeverity);
  
  // 🎯 Show critical issues
  const critical = issues.filter(i => i.severity === 'critical');
  if (critical.length > 0) {
    console.log('\n🚨 Critical issues:');
    critical.forEach(issue => {
      console.log(`  ${issue.file}:${issue.line} - ${issue.message}`);
    });
  }
});

Example 2: Input Validation Analyzer πŸ›‘οΈ

// πŸ“‚ input-validation-analyzer.ts
import * as ts from 'typescript';

interface ValidationIssue {
  fileName: string;
  line: number;
  functionName: string;
  parameter: string;
  issue: string;
}

class InputValidationAnalyzer {
  private program: ts.Program;
  private checker: ts.TypeChecker;

  constructor(files: string[]) {
    // πŸ”§ Create TypeScript program for analysis
    this.program = ts.createProgram(files, {
      noEmit: true,
      allowJs: false,
      target: ts.ScriptTarget.ES2020
    });
    this.checker = this.program.getTypeChecker();
  }

  analyzeValidation(): ValidationIssue[] {
    const issues: ValidationIssue[] = [];

    // πŸ” Analyze each source file
    for (const sourceFile of this.program.getSourceFiles()) {
      if (sourceFile.isDeclarationFile) continue;
      
      ts.forEachChild(sourceFile, node => {
        this.visitNode(node, sourceFile, issues);
      });
    }

    return issues;
  }

  private visitNode(node: ts.Node, sourceFile: ts.SourceFile, issues: ValidationIssue[]): void {
    // 🎯 Look for function declarations
    if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
      this.analyzeFunctionValidation(node, sourceFile, issues);
    }

    // πŸ“ Recursively visit child nodes
    ts.forEachChild(node, child => this.visitNode(child, sourceFile, issues));
  }

  private analyzeFunctionValidation(
    node: ts.FunctionDeclaration | ts.MethodDeclaration,
    sourceFile: ts.SourceFile,
    issues: ValidationIssue[]
  ): void {
    const functionName = node.name?.getText() || 'anonymous';
    
    // πŸ” Check if function handles user input
    if (this.isUserInputHandler(node)) {
      const hasValidation = this.checkForValidation(node);
      
      if (!hasValidation) {
        const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
        
        // 🚨 Check each parameter
        node.parameters.forEach(param => {
          const paramName = param.name.getText();
          const paramType = this.checker.getTypeAtLocation(param);
          
          if (this.needsValidation(paramType)) {
            issues.push({
              fileName: sourceFile.fileName,
              line: line + 1,
              functionName,
              parameter: paramName,
              issue: 'User input parameter lacks validation'
            });
          }
        });
      }
    }
  }

  private isUserInputHandler(node: ts.FunctionDeclaration | ts.MethodDeclaration): boolean {
    const name = node.name?.getText() || '';
    
    // 🎯 Common patterns for user input handlers
    const patterns = [
      /^handle/i,
      /^process/i,
      /^submit/i,
      /^save/i,
      /^create/i,
      /^update/i,
      /Controller$/,
      /Route$/,
      /^api/i
    ];
    
    return patterns.some(pattern => pattern.test(name));
  }

  private checkForValidation(node: ts.FunctionDeclaration | ts.MethodDeclaration): boolean {
    let hasValidation = false;
    
    const checkNode = (n: ts.Node): void => {
      // πŸ” Look for validation patterns
      if (ts.isCallExpression(n)) {
        const expression = n.expression.getText();
        const validationPatterns = [
          /validate/i,
          /sanitize/i,
          /escape/i,
          /check/i,
          /assert/i,
          /\.trim\(\)/,
          /\.test\(/,
          /joi\./,
          /yup\./,
          /zod\./,
          /class-validator/
        ];
        
        if (validationPatterns.some(pattern => pattern.test(expression))) {
          hasValidation = true;
        }
      }
      
      // πŸ” Check for if statements that might be validation
      if (ts.isIfStatement(n)) {
        const condition = n.expression.getText();
        if (/length|match|test|includes/.test(condition)) {
          hasValidation = true;
        }
      }
      
      ts.forEachChild(n, checkNode);
    };
    
    if (node.body) {
      checkNode(node.body);
    }
    
    return hasValidation;
  }

  private needsValidation(type: ts.Type): boolean {
    const typeString = this.checker.typeToString(type);
    
    // 🎯 Types that typically need validation
    const needsValidationTypes = [
      'string',
      'any',
      'unknown',
      'object',
      'Record<'
    ];
    
    return needsValidationTypes.some(t => typeString.includes(t));
  }
}

// πŸš€ Usage
const analyzer = new InputValidationAnalyzer(['./src/**/*.ts']);
const validationIssues = analyzer.analyzeValidation();

if (validationIssues.length > 0) {
  console.log('⚠️ Input validation issues found:');
  validationIssues.forEach(issue => {
    console.log(`  ${issue.fileName}:${issue.line} - ${issue.functionName}(${issue.parameter}) - ${issue.issue}`);
  });
}

Example 3: Dependency Security Audit πŸ“¦

// πŸ“‚ dependency-security-audit.ts
import { execSync } from 'child_process';
import * as fs from 'fs';

interface Vulnerability {
  id: string;
  severity: 'low' | 'moderate' | 'high' | 'critical';
  title: string;
  packageName: string;
  installedVersion: string;
  fixedVersion?: string;
  path: string[];
}

class DependencySecurityAudit {
  private knownVulnerablePackages = new Set([
    'event-stream',  // 🚨 Known malicious package
    'flatmap-stream', // 🚨 Known malicious package
    'left-pad'       // πŸ“¦ Famously problematic
  ]);

  async runAudit(): Promise<{
    vulnerabilities: Vulnerability[];
    statistics: Record<string, number>;
    recommendations: string[];
  }> {
    const vulnerabilities: Vulnerability[] = [];
    
    // πŸ” Run npm audit
    try {
      const auditOutput = execSync('npm audit --json', { encoding: 'utf-8' });
      const auditData = JSON.parse(auditOutput);
      
      // 🎯 Process vulnerabilities
      if (auditData.vulnerabilities) {
        for (const [pkgName, vulnData] of Object.entries(auditData.vulnerabilities as any)) {
          vulnData.via.forEach((via: any) => {
            if (typeof via === 'object') {
              vulnerabilities.push({
                id: via.id || 'unknown',
                severity: via.severity,
                title: via.title,
                packageName: pkgName,
                installedVersion: vulnData.version,
                fixedVersion: vulnData.fixedIn?.[0],
                path: vulnData.paths?.[0] || []
              });
            }
          });
        }
      }
    } catch (error) {
      console.warn('⚠️ npm audit failed, trying alternative methods...');
    }

    // πŸ” Check for known vulnerable packages
    const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
    const allDeps = {
      ...packageJson.dependencies,
      ...packageJson.devDependencies
    };

    for (const [pkg, version] of Object.entries(allDeps)) {
      if (this.knownVulnerablePackages.has(pkg)) {
        vulnerabilities.push({
          id: 'known-vulnerable',
          severity: 'critical',
          title: `Known vulnerable package: ${pkg}`,
          packageName: pkg,
          installedVersion: version as string,
          path: ['direct']
        });
      }
    }

    // πŸ“Š Generate statistics
    const statistics = this.generateStatistics(vulnerabilities);
    
    // πŸ’‘ Generate recommendations
    const recommendations = this.generateRecommendations(vulnerabilities, allDeps);

    return { vulnerabilities, statistics, recommendations };
  }

  private generateStatistics(vulnerabilities: Vulnerability[]): Record<string, number> {
    const stats: Record<string, number> = {
      total: vulnerabilities.length,
      critical: 0,
      high: 0,
      moderate: 0,
      low: 0
    };

    vulnerabilities.forEach(vuln => {
      stats[vuln.severity]++;
    });

    return stats;
  }

  private generateRecommendations(
    vulnerabilities: Vulnerability[],
    dependencies: Record<string, string>
  ): string[] {
    const recommendations: string[] = [];

    // 🚨 Critical vulnerabilities
    const criticalVulns = vulnerabilities.filter(v => v.severity === 'critical');
    if (criticalVulns.length > 0) {
      recommendations.push(
        `🚨 URGENT: Fix ${criticalVulns.length} critical vulnerabilities immediately!`
      );
      criticalVulns.forEach(vuln => {
        if (vuln.fixedVersion) {
          recommendations.push(
            `  β†’ Update ${vuln.packageName} to ${vuln.fixedVersion} or higher`
          );
        } else {
          recommendations.push(
            `  β†’ Remove or replace ${vuln.packageName} (no fix available)`
          );
        }
      });
    }

    // πŸ” Check for outdated packages
    const majorOutdated = this.checkOutdatedPackages(dependencies);
    if (majorOutdated.length > 0) {
      recommendations.push(
        `πŸ“¦ Consider updating ${majorOutdated.length} packages with major version updates`
      );
    }

    // πŸ›‘οΈ Security best practices
    recommendations.push(
      'πŸ›‘οΈ Security best practices:',
      '  β†’ Run npm audit fix regularly',
      '  β†’ Use npm audit in CI/CD pipeline',
      '  β†’ Consider using Snyk or similar tools',
      '  β†’ Review and minimize dependencies',
      '  β†’ Use lockfiles (package-lock.json)'
    );

    return recommendations;
  }

  private checkOutdatedPackages(dependencies: Record<string, string>): string[] {
    // πŸ” This is a simplified check - in real world, use npm outdated
    const outdated: string[] = [];
    
    for (const [pkg, version] of Object.entries(dependencies)) {
      // Check if using very old syntax (e.g., ~0.x.x or ^1.x.x for major packages)
      if (/^[~^]?[0-1]\./.test(version) && this.isCommonPackage(pkg)) {
        outdated.push(pkg);
      }
    }
    
    return outdated;
  }

  private isCommonPackage(pkg: string): boolean {
    const commonPackages = [
      'react', 'vue', 'angular', 'express', 'lodash',
      'axios', 'moment', 'webpack', 'babel', 'eslint'
    ];
    return commonPackages.some(common => pkg.includes(common));
  }
}

// πŸš€ Run security audit
const audit = new DependencySecurityAudit();
audit.runAudit().then(results => {
  console.log('\nπŸ”’ Dependency Security Audit Report\n');
  console.log('πŸ“Š Statistics:', results.statistics);
  
  if (results.vulnerabilities.length > 0) {
    console.log('\n⚠️ Vulnerabilities found:');
    results.vulnerabilities
      .sort((a, b) => {
        const severityOrder = { critical: 0, high: 1, moderate: 2, low: 3 };
        return severityOrder[a.severity] - severityOrder[b.severity];
      })
      .forEach(vuln => {
        const icon = vuln.severity === 'critical' ? '🚨' : 
                    vuln.severity === 'high' ? '⚠️' : 
                    vuln.severity === 'moderate' ? '⚑' : 'πŸ’‘';
        console.log(`${icon} [${vuln.severity.toUpperCase()}] ${vuln.packageName}@${vuln.installedVersion}`);
        console.log(`   ${vuln.title}`);
        if (vuln.fixedVersion) {
          console.log(`   βœ… Fix available: upgrade to ${vuln.fixedVersion}`);
        }
      });
  }
  
  console.log('\nπŸ’‘ Recommendations:');
  results.recommendations.forEach(rec => console.log(rec));
});

πŸš€ Advanced Security Analysis Techniques

Taint Analysis for Data Flow 🌊

// πŸ“‚ taint-analysis.ts
import * as ts from 'typescript';

class TaintAnalysis {
  private sourceFile: ts.SourceFile;
  private checker: ts.TypeChecker;
  private taintedVariables = new Set<string>();
  private sinkFunctions = new Set([
    'eval',
    'innerHTML',
    'outerHTML',
    'document.write',
    'execSync',
    'exec',
    'spawn',
    'query',  // Database queries
    'execute' // Database execution
  ]);

  constructor(program: ts.Program, sourceFile: ts.SourceFile) {
    this.sourceFile = sourceFile;
    this.checker = program.getTypeChecker();
  }

  analyze(): Array<{ line: number; message: string }> {
    const issues: Array<{ line: number; message: string }> = [];
    
    // πŸ” First pass: identify tainted sources
    this.identifyTaintedSources(this.sourceFile);
    
    // πŸ” Second pass: check for tainted data reaching sinks
    this.checkTaintFlow(this.sourceFile, issues);
    
    return issues;
  }

  private identifyTaintedSources(node: ts.Node): void {
    // 🎯 Common taint sources
    if (ts.isPropertyAccessExpression(node)) {
      const text = node.getText();
      const taintSources = [
        'req.body',
        'req.query',
        'req.params',
        'req.headers',
        'process.argv',
        'location.search',
        'window.location',
        'document.URL'
      ];
      
      if (taintSources.some(source => text.includes(source))) {
        // Mark variable as tainted
        const parent = node.parent;
        if (ts.isVariableDeclaration(parent)) {
          const varName = parent.name.getText();
          this.taintedVariables.add(varName);
        }
      }
    }
    
    ts.forEachChild(node, child => this.identifyTaintedSources(child));
  }

  private checkTaintFlow(node: ts.Node, issues: Array<{ line: number; message: string }>): void {
    // 🚨 Check if tainted data reaches dangerous sinks
    if (ts.isCallExpression(node)) {
      const funcName = node.expression.getText();
      
      if (this.isSinkFunction(funcName)) {
        // Check if any argument is tainted
        node.arguments.forEach(arg => {
          if (this.isTainted(arg)) {
            const { line } = this.sourceFile.getLineAndCharacterOfPosition(node.getStart());
            issues.push({
              line: line + 1,
              message: `Tainted data flows to dangerous sink: ${funcName}`
            });
          }
        });
      }
    }
    
    ts.forEachChild(node, child => this.checkTaintFlow(child, issues));
  }

  private isSinkFunction(funcName: string): boolean {
    return Array.from(this.sinkFunctions).some(sink => funcName.includes(sink));
  }

  private isTainted(node: ts.Node): boolean {
    if (ts.isIdentifier(node)) {
      return this.taintedVariables.has(node.getText());
    }
    
    // πŸ” Check for tainted property access
    if (ts.isPropertyAccessExpression(node)) {
      return this.isTainted(node.expression);
    }
    
    // πŸ” Check for tainted template literals
    if (ts.isTemplateExpression(node)) {
      return node.templateSpans.some(span => this.isTainted(span.expression));
    }
    
    return false;
  }
}

⚠️ Common Security Pitfalls and Solutions

1. Hardcoded Secrets πŸ”‘

// ❌ WRONG: Hardcoded secrets
const config = {
  apiKey: 'sk_live_abcd1234efgh5678',  // 🚨 Never do this!
  dbPassword: 'super_secret_password'   // πŸ’₯ Security disaster!
};

// βœ… CORRECT: Use environment variables
const config = {
  apiKey: process.env.API_KEY || '',
  dbPassword: process.env.DB_PASSWORD || ''
};

// πŸ›‘οΈ Even better: Use a secrets manager
import { SecretsManager } from 'aws-sdk';

class SecureConfig {
  private secretsManager = new SecretsManager();
  
  async getApiKey(): Promise<string> {
    const secret = await this.secretsManager.getSecretValue({
      SecretId: 'api-keys/main'
    }).promise();
    
    return JSON.parse(secret.SecretString!).apiKey;
  }
}

2. Injection Vulnerabilities πŸ’‰

// ❌ WRONG: SQL injection vulnerability
const getUser = async (userId: string) => {
  const query = `SELECT * FROM users WHERE id = '${userId}'`;
  return db.query(query);  // 🚨 Vulnerable to SQL injection!
};

// βœ… CORRECT: Use parameterized queries
const getUser = async (userId: string) => {
  const query = 'SELECT * FROM users WHERE id = $1';
  return db.query(query, [userId]);  // πŸ›‘οΈ Safe from injection
};

// 🎯 Using query builders for extra safety
import { QueryBuilder } from 'typeorm';

const getUser = async (userId: string) => {
  return await userRepository
    .createQueryBuilder('user')
    .where('user.id = :id', { id: userId })
    .getOne();
};

3. Insecure Random Values 🎲

// ❌ WRONG: Math.random() for security
const generateToken = () => {
  return Math.random().toString(36).substring(2);  // 🚨 Predictable!
};

// βœ… CORRECT: Use crypto for randomness
import { randomBytes } from 'crypto';

const generateToken = (): string => {
  return randomBytes(32).toString('hex');  // πŸ›‘οΈ Cryptographically secure
};

// 🎯 For UUIDs
import { randomUUID } from 'crypto';

const generateId = (): string => {
  return randomUUID();  // πŸ” Secure UUID v4
};

πŸ› οΈ Best Practices for Security Reviews

1. Automated Security Pipeline πŸ”„

// πŸ“‚ security-pipeline.ts
import { execSync } from 'child_process';

class SecurityPipeline {
  async run(): Promise<boolean> {
    console.log('πŸ”’ Starting security pipeline...\n');
    
    const steps = [
      {
        name: 'TypeScript Strict Mode',
        command: 'tsc --noEmit --strict',
        critical: true
      },
      {
        name: 'ESLint Security',
        command: 'eslint . --ext .ts,.tsx',
        critical: true
      },
      {
        name: 'Dependency Audit',
        command: 'npm audit --audit-level=moderate',
        critical: false
      },
      {
        name: 'License Check',
        command: 'license-checker --onlyAllow "MIT;Apache-2.0;BSD"',
        critical: false
      },
      {
        name: 'Secrets Scan',
        command: 'truffleHog --regex --entropy=False .',
        critical: true
      }
    ];
    
    let allPassed = true;
    
    for (const step of steps) {
      console.log(`πŸ” Running: ${step.name}`);
      
      try {
        execSync(step.command, { stdio: 'inherit' });
        console.log(`βœ… ${step.name} passed\n`);
      } catch (error) {
        console.log(`❌ ${step.name} failed\n`);
        if (step.critical) {
          allPassed = false;
        }
      }
    }
    
    return allPassed;
  }
}

// πŸš€ Run in CI/CD
const pipeline = new SecurityPipeline();
pipeline.run().then(success => {
  if (!success) {
    console.error('🚨 Security pipeline failed!');
    process.exit(1);
  }
  console.log('βœ… All security checks passed!');
});

2. Security Review Checklist πŸ“‹

// πŸ“‚ security-checklist.ts
interface SecurityCheckItem {
  category: string;
  checks: Array<{
    item: string;
    automated: boolean;
    severity: 'low' | 'medium' | 'high' | 'critical';
  }>;
}

const securityChecklist: SecurityCheckItem[] = [
  {
    category: 'πŸ” Authentication & Authorization',
    checks: [
      { item: 'No hardcoded credentials', automated: true, severity: 'critical' },
      { item: 'Proper session management', automated: false, severity: 'high' },
      { item: 'Rate limiting implemented', automated: true, severity: 'medium' },
      { item: 'RBAC properly configured', automated: false, severity: 'high' }
    ]
  },
  {
    category: 'πŸ’‰ Input Validation',
    checks: [
      { item: 'All inputs validated', automated: true, severity: 'high' },
      { item: 'SQL injection prevention', automated: true, severity: 'critical' },
      { item: 'XSS prevention', automated: true, severity: 'critical' },
      { item: 'File upload restrictions', automated: false, severity: 'high' }
    ]
  },
  {
    category: 'πŸ”’ Cryptography',
    checks: [
      { item: 'Strong algorithms used', automated: true, severity: 'high' },
      { item: 'Secure random generation', automated: true, severity: 'high' },
      { item: 'Proper key management', automated: false, severity: 'critical' },
      { item: 'TLS properly configured', automated: false, severity: 'high' }
    ]
  },
  {
    category: 'πŸ“¦ Dependencies',
    checks: [
      { item: 'No known vulnerabilities', automated: true, severity: 'high' },
      { item: 'Licenses reviewed', automated: true, severity: 'low' },
      { item: 'Minimal dependencies', automated: false, severity: 'medium' },
      { item: 'Lock files committed', automated: true, severity: 'medium' }
    ]
  }
];

// 🎯 Generate review report
function generateSecurityReport(checklist: SecurityCheckItem[]): void {
  console.log('πŸ“‹ Security Review Checklist\n');
  
  let totalChecks = 0;
  let automatedChecks = 0;
  
  checklist.forEach(category => {
    console.log(`\n${category.category}`);
    category.checks.forEach(check => {
      totalChecks++;
      if (check.automated) automatedChecks++;
      
      const icon = check.automated ? 'πŸ€–' : 'πŸ‘€';
      const severity = check.severity.toUpperCase();
      console.log(`  ${icon} [${severity}] ${check.item}`);
    });
  });
  
  console.log(`\nπŸ“Š Summary: ${automatedChecks}/${totalChecks} checks automated`);
  console.log('πŸ’‘ Tip: Automate more checks to improve coverage!');
}

generateSecurityReport(securityChecklist);

πŸ§ͺ Hands-On Exercise: Build Your Security Scanner

Your challenge: Create a comprehensive security scanner that combines multiple analysis techniques! 🎯

The Challenge πŸ†

Build a security scanner that:

  1. Detects hardcoded secrets using regex patterns
  2. Identifies SQL injection vulnerabilities
  3. Finds insecure randomness usage
  4. Checks for missing input validation
  5. Generates a security report with severity levels
πŸ’‘ Solution
// πŸ“‚ comprehensive-security-scanner.ts
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';

interface SecurityFinding {
  type: 'secret' | 'injection' | 'crypto' | 'validation';
  severity: 'low' | 'medium' | 'high' | 'critical';
  file: string;
  line: number;
  message: string;
  code?: string;
}

class ComprehensiveSecurityScanner {
  private findings: SecurityFinding[] = [];
  
  // πŸ” Regex patterns for secrets
  private secretPatterns = [
    { pattern: /api[_-]?key\s*[:=]\s*["'][^"']{20,}["']/gi, type: 'API Key' },
    { pattern: /secret\s*[:=]\s*["'][^"']{10,}["']/gi, type: 'Secret' },
    { pattern: /password\s*[:=]\s*["'][^"']+["']/gi, type: 'Password' },
    { pattern: /token\s*[:=]\s*["'][^"']{20,}["']/gi, type: 'Token' },
    { pattern: /private[_-]?key\s*[:=]\s*["'][^"']+["']/gi, type: 'Private Key' }
  ];
  
  // 🎯 SQL injection patterns
  private sqlInjectionPatterns = [
    /query\s*\([`"'].*\$\{[^}]+\}.*[`"']\)/g,
    /execute\s*\([`"'].*\+.*[`"']\)/g,
    /\.query\s*\(\s*[`"'].*\+/g
  ];

  async scanDirectory(dirPath: string): Promise<SecurityFinding[]> {
    this.findings = [];
    await this.walkDirectory(dirPath);
    return this.findings;
  }

  private async walkDirectory(dir: string): Promise<void> {
    const files = await fs.promises.readdir(dir);
    
    for (const file of files) {
      const filePath = path.join(dir, file);
      const stat = await fs.promises.stat(filePath);
      
      if (stat.isDirectory() && !file.includes('node_modules')) {
        await this.walkDirectory(filePath);
      } else if (file.endsWith('.ts') || file.endsWith('.tsx')) {
        await this.scanFile(filePath);
      }
    }
  }

  private async scanFile(filePath: string): Promise<void> {
    const content = await fs.promises.readFile(filePath, 'utf-8');
    const lines = content.split('\n');
    
    // πŸ” Scan for hardcoded secrets
    this.scanForSecrets(content, filePath, lines);
    
    // πŸ” Scan for SQL injections
    this.scanForSQLInjection(content, filePath, lines);
    
    // πŸ” Scan for weak crypto
    this.scanForWeakCrypto(content, filePath, lines);
    
    // πŸ” Advanced TypeScript analysis
    this.performTypeScriptAnalysis(filePath, content);
  }

  private scanForSecrets(content: string, filePath: string, lines: string[]): void {
    for (const { pattern, type } of this.secretPatterns) {
      const matches = content.matchAll(pattern);
      
      for (const match of matches) {
        const lineNumber = this.getLineNumber(content, match.index!);
        this.findings.push({
          type: 'secret',
          severity: 'critical',
          file: filePath,
          line: lineNumber,
          message: `Hardcoded ${type} detected`,
          code: lines[lineNumber - 1].trim()
        });
      }
    }
  }

  private scanForSQLInjection(content: string, filePath: string, lines: string[]): void {
    for (const pattern of this.sqlInjectionPatterns) {
      const matches = content.matchAll(pattern);
      
      for (const match of matches) {
        const lineNumber = this.getLineNumber(content, match.index!);
        this.findings.push({
          type: 'injection',
          severity: 'critical',
          file: filePath,
          line: lineNumber,
          message: 'Potential SQL injection vulnerability',
          code: lines[lineNumber - 1].trim()
        });
      }
    }
  }

  private scanForWeakCrypto(content: string, filePath: string, lines: string[]): void {
    const weakCryptoPatterns = [
      { pattern: /Math\.random\(\)/g, message: 'Insecure randomness: Math.random()' },
      { pattern: /createHash\(['"]md5['"]\)/g, message: 'Weak hash algorithm: MD5' },
      { pattern: /createHash\(['"]sha1['"]\)/g, message: 'Weak hash algorithm: SHA1' }
    ];
    
    for (const { pattern, message } of weakCryptoPatterns) {
      const matches = content.matchAll(pattern);
      
      for (const match of matches) {
        const lineNumber = this.getLineNumber(content, match.index!);
        this.findings.push({
          type: 'crypto',
          severity: 'high',
          file: filePath,
          line: lineNumber,
          message,
          code: lines[lineNumber - 1].trim()
        });
      }
    }
  }

  private performTypeScriptAnalysis(filePath: string, content: string): void {
    const sourceFile = ts.createSourceFile(
      filePath,
      content,
      ts.ScriptTarget.Latest,
      true
    );
    
    const visit = (node: ts.Node): void => {
      // πŸ” Check for missing validation
      if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
        const name = node.name?.getText() || '';
        if (/^handle|^process|^api/i.test(name)) {
          const hasValidation = this.checkForValidation(node);
          if (!hasValidation) {
            const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
            this.findings.push({
              type: 'validation',
              severity: 'medium',
              file: filePath,
              line: line + 1,
              message: `Function '${name}' may lack input validation`
            });
          }
        }
      }
      
      ts.forEachChild(node, visit);
    };
    
    visit(sourceFile);
  }

  private checkForValidation(node: ts.FunctionDeclaration | ts.MethodDeclaration): boolean {
    let hasValidation = false;
    
    const checkNode = (n: ts.Node): void => {
      if (ts.isCallExpression(n)) {
        const text = n.expression.getText();
        if (/validate|check|assert|test/.test(text)) {
          hasValidation = true;
        }
      }
      ts.forEachChild(n, checkNode);
    };
    
    if (node.body) checkNode(node.body);
    return hasValidation;
  }

  private getLineNumber(content: string, index: number): number {
    return content.substring(0, index).split('\n').length;
  }

  generateReport(): string {
    const report: string[] = ['# πŸ”’ Security Scan Report\n'];
    
    // πŸ“Š Summary
    const summary = {
      total: this.findings.length,
      critical: this.findings.filter(f => f.severity === 'critical').length,
      high: this.findings.filter(f => f.severity === 'high').length,
      medium: this.findings.filter(f => f.severity === 'medium').length,
      low: this.findings.filter(f => f.severity === 'low').length
    };
    
    report.push('## πŸ“Š Summary\n');
    report.push(`- Total findings: ${summary.total}`);
    report.push(`- Critical: ${summary.critical} 🚨`);
    report.push(`- High: ${summary.high} ⚠️`);
    report.push(`- Medium: ${summary.medium} ⚑`);
    report.push(`- Low: ${summary.low} πŸ’‘\n`);
    
    // 🎯 Findings by type
    const byType = this.findings.reduce((acc, f) => {
      acc[f.type] = (acc[f.type] || 0) + 1;
      return acc;
    }, {} as Record<string, number>);
    
    report.push('## 🎯 Findings by Type\n');
    Object.entries(byType).forEach(([type, count]) => {
      report.push(`- ${type}: ${count}`);
    });
    report.push('');
    
    // πŸ“‹ Detailed findings
    report.push('## πŸ“‹ Detailed Findings\n');
    
    const sortedFindings = [...this.findings].sort((a, b) => {
      const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
      return severityOrder[a.severity] - severityOrder[b.severity];
    });
    
    sortedFindings.forEach((finding, index) => {
      const icon = finding.severity === 'critical' ? '🚨' : 
                  finding.severity === 'high' ? '⚠️' : 
                  finding.severity === 'medium' ? '⚑' : 'πŸ’‘';
      
      report.push(`### ${index + 1}. ${icon} ${finding.message}`);
      report.push(`- **File**: ${finding.file}`);
      report.push(`- **Line**: ${finding.line}`);
      report.push(`- **Severity**: ${finding.severity.toUpperCase()}`);
      report.push(`- **Type**: ${finding.type}`);
      if (finding.code) {
        report.push(`- **Code**: \`${finding.code}\``);
      }
      report.push('');
    });
    
    // πŸ’‘ Recommendations
    report.push('## πŸ’‘ Recommendations\n');
    if (summary.critical > 0) {
      report.push('1. **URGENT**: Address all critical findings immediately!');
    }
    report.push('2. Implement automated security scanning in CI/CD');
    report.push('3. Use environment variables for all secrets');
    report.push('4. Enable TypeScript strict mode');
    report.push('5. Regular dependency updates and audits');
    report.push('6. Code review all security-sensitive changes');
    
    return report.join('\n');
  }
}

// πŸš€ Usage
async function runSecurityScan() {
  const scanner = new ComprehensiveSecurityScanner();
  
  console.log('πŸ” Starting security scan...\n');
  const findings = await scanner.scanDirectory('./src');
  
  const report = scanner.generateReport();
  
  // πŸ’Ύ Save report
  await fs.promises.writeFile('security-report.md', report);
  console.log('πŸ“„ Report saved to security-report.md');
  
  // 🎯 Exit with error if critical findings
  const criticalCount = findings.filter(f => f.severity === 'critical').length;
  if (criticalCount > 0) {
    console.error(`\n🚨 Found ${criticalCount} critical security issues!`);
    process.exit(1);
  }
  
  console.log('\nβœ… Security scan completed!');
}

runSecurityScan().catch(console.error);

πŸŽ“ Key Takeaways

You’ve mastered code security reviews with static analysis! Here’s what you’ve learned:

πŸ” Static Analysis Tools - ESLint, Semgrep, and custom analyzers for security πŸ›‘οΈ Vulnerability Detection - Identifying injection, XSS, and other security flaws πŸ“Š Security Metrics - Tracking and reporting security issues effectively πŸ€– Automation - Integrating security checks into CI/CD pipelines 🎯 Best Practices - Implementing comprehensive security reviews

Remember:

  • 🚨 Shift Left - Catch security issues early in development
  • πŸ”„ Automate Everything - Manual reviews miss things
  • πŸ“š Stay Updated - Security threats evolve constantly
  • 🀝 Team Effort - Security is everyone’s responsibility

🀝 Next Steps

Ready to enhance your security game? Here’s what’s next:

  1. πŸ“– Explore penetration testing techniques for TypeScript apps
  2. πŸ›‘οΈ Implement security headers and CSP policies
  3. πŸ” Master OAuth and modern authentication patterns
  4. πŸš€ Build a security-first TypeScript application

Keep securing! Your code (and users) will thank you! πŸ’ͺ✨


Remember: Security is not a feature, it’s a mindset! Always think like an attacker to build better defenses. πŸ”’πŸš€