Home/
Part XIII — Expert Mode: Systems, Agents, and Automation/38. Building a Code-Change Agent Safely/38.5 Security gates and dependency scanning
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.