38.5 Security gates and dependency scanning

Overview and links for this section of the guide.

Security Threats

AI-generated code can introduce security vulnerabilities:

Threat Example Detection
Injection SQL string concatenation Pattern matching, SAST
Secrets Hardcoded API keys Secret scanning
Insecure deps Using vulnerable packages npm audit, Snyk
Path traversal readFile(userInput) Taint analysis
XSS Unescaped user input in HTML Template analysis

Code Scanning

// security-scanner.ts
const DANGEROUS_PATTERNS = [
  { pattern: /eval\s*\(/g, severity: 'critical', message: 'eval() is dangerous' },
  { pattern: /innerHTML\s*=/g, severity: 'high', message: 'innerHTML can cause XSS' },
  { pattern: /exec\s*\(/g, severity: 'critical', message: 'exec() can run arbitrary code' },
  { pattern: /\$\{.*\}.*FROM|WHERE/gi, severity: 'critical', message: 'Potential SQL injection' },
  { pattern: /password\s*[:=]\s*['"][^'"]+['"]/gi, severity: 'critical', message: 'Hardcoded password' },
  { pattern: /sk-[a-zA-Z0-9]{32,}/g, severity: 'critical', message: 'Exposed API key' },
];

export class SecurityScanner {
  async scan(files: string[]): Promise {
    const findings: SecurityFinding[] = [];
    
    for (const file of files) {
      const content = await fs.readFile(file, 'utf8');
      
      for (const { pattern, severity, message } of DANGEROUS_PATTERNS) {
        pattern.lastIndex = 0;
        let match;
        while ((match = pattern.exec(content)) !== null) {
          findings.push({
            file,
            line: this.getLineNumber(content, match.index),
            severity,
            message,
            code: match[0],
            rule: pattern.source
          });
        }
      }
    }
    
    // Also run external tools
    const semgrepResults = await this.runSemgrep(files);
    findings.push(...semgrepResults);
    
    return {
      passed: findings.filter(f => f.severity === 'critical').length === 0,
      findings
    };
  }
  
  private async runSemgrep(files: string[]): Promise {
    const result = execSync(
      `semgrep --config=auto --json ${files.join(' ')}`,
      { encoding: 'utf8' }
    );
    
    const parsed = JSON.parse(result);
    return parsed.results.map(r => ({
      file: r.path,
      line: r.start.line,
      severity: r.extra.severity,
      message: r.extra.message,
      code: r.extra.lines,
      rule: r.check_id
    }));
  }
}

Dependency Scanning

// dependency-scanner.ts
export class DependencyScanner {
  async scan(packageJsonPath: string): Promise {
    // Run npm audit
    const auditResult = await this.runNpmAudit(packageJsonPath);
    
    // Check for new dependencies in the diff
    const newDeps = await this.detectNewDependencies();
    
    // Validate new dependencies
    const depChecks = await Promise.all(
      newDeps.map(dep => this.checkDependency(dep))
    );
    
    return {
      vulnerabilities: auditResult.vulnerabilities,
      newDependencies: depChecks,
      blockers: [
        ...auditResult.vulnerabilities.filter(v => v.severity === 'critical'),
        ...depChecks.filter(d => !d.approved)
      ]
    };
  }
  
  private async runNpmAudit(path: string): Promise {
    const result = execSync('npm audit --json', {
      cwd: dirname(path),
      encoding: 'utf8'
    });
    
    const parsed = JSON.parse(result);
    return {
      vulnerabilities: Object.values(parsed.vulnerabilities).map(v => ({
        package: v.name,
        severity: v.severity,
        title: v.title,
        fixAvailable: v.fixAvailable
      }))
    };
  }
  
  private async checkDependency(dep: string): Promise {
    // Check if it's a known/trusted package
    const packageInfo = await this.getPackageInfo(dep);
    
    return {
      name: dep,
      approved: packageInfo.downloads > 10000 && 
                packageInfo.lastPublish < 365 &&
                !packageInfo.deprecated,
      concerns: [
        packageInfo.downloads < 10000 ? 'Low download count' : null,
        packageInfo.lastPublish > 365 ? 'Not updated in a year' : null,
        packageInfo.deprecated ? 'Package is deprecated' : null
      ].filter(Boolean)
    };
  }
}

Implementation

// security-gate.ts
export class SecurityGate {
  async validate(diff: DiffBlock[]): Promise {
    const changedFiles = diff.map(d => d.file);
    
    // Run all security checks
    const [codeScan, depScan, secretScan] = await Promise.all([
      this.codeScanner.scan(changedFiles),
      this.depScanner.scan('package.json'),
      this.secretScanner.scan(changedFiles)
    ]);
    
    const criticalIssues = [
      ...codeScan.findings.filter(f => f.severity === 'critical'),
      ...depScan.blockers,
      ...secretScan.findings
    ];
    
    if (criticalIssues.length > 0) {
      return {
        approved: false,
        reason: 'Critical security issues found',
        issues: criticalIssues,
        mustFix: true
      };
    }
    
    const warnings = [
      ...codeScan.findings.filter(f => f.severity !== 'critical'),
      ...depScan.newDependencies.filter(d => d.concerns.length > 0)
    ];
    
    return {
      approved: true,
      warnings,
      requiresSecurityReview: warnings.length > 5
    };
  }
}
Block on Critical Issues

Never merge code with critical security findings. The agent should not be able to bypass this gate under any circumstances.

Where to go next