Claude Code for Security Scan (2026)
Claude Code for Security Scan Automation
Security scanning automation has become essential for teams shipping code frequently. Claude Code provides a powerful foundation for building automated security workflows that catch vulnerabilities before they reach production. This guide shows you how to use Claude Code skills and hooks to create solid security scan pipelines. from dependency audits and secret detection through container scanning and full CI/CD integration.
Why Automate Security Scanning with Claude Code
Manual security reviews are slow and inconsistent. The same engineer who writes a feature rarely catches the subtle vulnerability they just introduced. Automated scanning solves the consistency problem, but most teams bolt it on as an afterthought. a GitHub Action that runs and whose results nobody reads.
Claude Code changes the equation by acting as an interpreter layer between raw scanner output and actionable decisions. Instead of reading a 200-line JSON blob from npm audit, you ask Claude to summarize the critical findings, identify which ones are exploitable in your specific context, and draft the remediation plan. The scan runs automatically; Claude Code makes the results useful.
The combination works particularly well across four scanning domains:
| Domain | Primary Tool | What Claude Code Adds |
|---|---|---|
| Dependency vulnerabilities | npm audit, pip-audit | Filters noise, ranks by exploitability |
| Secret detection | gitleaks, truffleHog | Explains what each secret type exposes |
| Static code analysis (SAST) | Semgrep, Bandit, ESLint | Maps findings to fix patterns in your codebase |
| Container scanning | Trivy, Grype | Prioritizes base image upgrades vs package fixes |
Setting Up Security Scan Skills
Claude Code works best for security automation when you configure dedicated skills for different scanning tasks. The tdd skill proves surprisingly useful here. while designed for test-driven development, its structured approach to running commands and validating outputs maps directly to security scan execution.
Start by creating a security scanning skill:
{
"name": "security-scanner",
"description": "Runs security scans on codebase",
"commands": [
{
"name": "scan-deps",
"command": "npm audit --json",
"validator": "exit_code"
},
{
"name": "scan-secrets",
"command": "gitleaks detect --report-format json"
}
]
}
This skill structure lets you run dependency audits and secret detection as discrete operations. The key advantage is consistency. each scan produces structured output you can parse and act upon.
For Python projects, extend the skill to cover multiple package managers:
{
"name": "security-scanner-python",
"description": "Runs security scans for Python projects",
"commands": [
{
"name": "scan-deps",
"command": "pip-audit --format json --output audit.json && cat audit.json",
"validator": "exit_code"
},
{
"name": "scan-bandit",
"command": "bandit -r . -f json -o bandit.json; cat bandit.json",
"validator": "none"
},
{
"name": "scan-safety",
"command": "safety check --json",
"validator": "exit_code"
}
]
}
Once these skills are in place, you can trigger a full security sweep from a single Claude Code prompt: “Run the security-scanner skill and tell me which findings need immediate attention.”
Automating Dependency Vulnerability Scans
Dependency scanning represents one of the highest-ROI security automations. Tools like npm audit, pip-audit, and OWASP Dependency-Check all produce machine-readable output that Claude Code can process.
Here’s a practical approach using a Claude hook:
#!/bin/bash
Pre-commit security hook
Place at .git/hooks/pre-commit or register via Claude Code hooks
echo "Running dependency vulnerability scan..."
AUDIT_OUTPUT=$(npm audit --json 2>/dev/null)
VULN_COUNT=$(echo "$AUDIT_OUTPUT" | jq '.metadata.vulnerabilities.total // 0')
CRITICAL_COUNT=$(echo "$AUDIT_OUTPUT" | jq '.metadata.vulnerabilities.critical // 0')
HIGH_COUNT=$(echo "$AUDIT_OUTPUT" | jq '.metadata.vulnerabilities.high // 0')
if [ "$CRITICAL_COUNT" -gt 0 ]; then
echo "BLOCKED: $CRITICAL_COUNT critical vulnerabilities found"
echo "$AUDIT_OUTPUT" | jq '[.vulnerabilities | to_entries[] | select(.value.severity == "critical") | {name: .key, severity: .value.severity, via: .value.via}] | .[0:5]'
exit 1
fi
if [ "$HIGH_COUNT" -gt 0 ]; then
echo "WARNING: $HIGH_COUNT high-severity vulnerabilities found"
echo "$AUDIT_OUTPUT" | jq '[.vulnerabilities | to_entries[] | select(.value.severity == "high") | {name: .key, severity: .value.severity}] | .[0:5]'
# Warn but do not block; adjust to exit 1 for stricter policy
fi
echo "Dependency scan passed (critical: $CRITICAL_COUNT, high: $HIGH_COUNT, total: $VULN_COUNT)"
exit 0
This hook runs before each commit, blocking builds that introduce known critical vulnerabilities. The integration with Claude Code hooks means you can trigger this scan automatically whenever you ask Claude to commit code changes.
For Python projects, the equivalent pip-audit hook:
#!/bin/bash
pip-audit pre-commit hook
echo "Running pip-audit..."
if ! command -v pip-audit &>/dev/null; then
echo "pip-audit not installed. Run: pip install pip-audit"
exit 1
fi
AUDIT_JSON=$(pip-audit --format json 2>/dev/null)
VULN_COUNT=$(echo "$AUDIT_JSON" | jq '[.[] | select(.vulns | length > 0)] | length')
if [ "$VULN_COUNT" -gt 0 ]; then
echo "BLOCKED: $VULN_COUNT packages with known vulnerabilities"
echo "$AUDIT_JSON" | jq '[.[] | select(.vulns | length > 0) | {name: .name, version: .version, vulns: [.vulns[].id]}]'
exit 1
fi
echo "pip-audit passed"
exit 0
Filtering Vulnerability Noise
Raw npm audit output often contains dozens of findings, most of which are transitive dependencies you cannot directly fix. Claude Code helps here by filtering to what matters:
// filter-audit.mjs
// Feed npm audit JSON; receive only direct + fixable findings
export function filterActionableVulns(auditJson) {
const parsed = JSON.parse(auditJson);
const vulns = Object.entries(parsed.vulnerabilities || {});
return vulns
.filter(([, v]) => v.isDirect && v.fixAvailable)
.map(([name, v]) => ({
package: name,
severity: v.severity,
fixedIn: typeof v.fixAvailable === "object" ? v.fixAvailable.version : "see npm audit fix",
via: v.via.filter(d => typeof d === "object").map(d => d.url || d.name)
}))
.sort((a, b) => {
const order = { critical: 0, high: 1, moderate: 2, low: 3 };
return (order[a.severity] ?? 4) - (order[b.severity] ?? 4);
});
}
Pass this to Claude Code and ask: “Which of these should I fix this sprint versus track in the backlog?”
Secret Detection in Codebases
Detecting secrets committed accidentally happens more often than teams realize. The supermemory skill offers an interesting approach. its document indexing capabilities can be adapted to track sensitive patterns across your codebase.
A practical secret detection setup uses gitleaks:
.gitleaks.toml
[rules]
[[rules.BasicAuth]]
description = "Basic Authorization Header"
regex = '''(authorization|Authorization)\s*:\s*[Bb]asic\s+[A-Za-z0-9+/=]+'''
[[rules.AWSAccessKey]]
description = "AWS Access Key"
regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}'''
[[rules.GitHubToken]]
description = "GitHub Token"
regex = '''gh[pousr]_[A-Za-z0-9_]{36,251}'''
[[rules.PrivateKey]]
description = "Private Key Block"
regex = '''-----BEGIN (RSA|EC|DSA|OPENSSH) PRIVATE KEY-----'''
[[rules.GenericSecret]]
description = "Generic high-entropy string assigned to secret variable"
regex = '''(?i)(secret|password|passwd|pwd|token|api_key)\s*[=:]\s*["'][A-Za-z0-9+/=_\-]{20,}["']'''
Combine this with a Claude Code skill that runs gitleaks on every pull request:
// security-skills/github-check.mjs
import { execSync } from 'child_process';
export async function runSecretScan(repoPath) {
let output;
try {
output = execSync('gitleaks detect --source . --report-format json --report-path /tmp/gitleaks.json', {
cwd: repoPath,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe']
});
} catch (err) {
// gitleaks exits 1 when findings exist; read the report regardless
output = err.stdout || '';
}
let findings = [];
try {
const fs = await import('fs');
findings = JSON.parse(fs.readFileSync('/tmp/gitleaks.json', 'utf-8')) || [];
} catch {
findings = [];
}
if (findings.length > 0) {
return {
blocked: true,
findings: findings.map(f => ({
file: f.File,
rule: f.RuleID,
line: f.StartLine,
secret: f.Secret ? f.Secret.slice(0, 4) + "" : "redacted"
}))
};
}
return { blocked: false };
}
What to Do When a Secret Is Found in History
Finding a secret in the current working tree is straightforward. remove it and rotate the credential. Finding one in git history is harder. Claude Code can guide you through the remediation:
Find the commit that introduced the secret
git log --all --oneline -S "AKIA" -- .
Identify all files it touched
git show <commit-sha> --stat
Use git-filter-repo to purge it from history (safer than filter-branch)
pip install git-filter-repo
git filter-repo --path-glob '/*.env' --invert-paths
git filter-repo --replace-text <(echo "AKIAXXXXXXXXXXXXXXXX==>REDACTED")
Force push all branches (coordinate with team first)
git push origin --force --all
git push origin --force --tags
Ask Claude Code to walk through this sequence with you. it can verify each step’s output and flag if the secret is still present in any branch before you push.
SAST Integration for Code Analysis
Static Application Security Testing (SAST) tools analyze source code for vulnerabilities. Tools like Semgrep, Bandit (Python), and ESLint (with security rules) fit well into Claude Code workflows.
The pdf skill. typically used for PDF manipulation. can actually help here. Many security reports come as PDF documents from commercial scanners. You can automate the extraction of vulnerability data from these reports:
// Extract findings from security PDF reports
import { readPdf } from 'claude-pdf-skill';
export async function parseSecurityReport(pdfPath) {
const text = await readPdf(pdfPath);
const vulnPattern = /CVE-\d{4}-\d{4,7}/g;
const cves = [...new Set(text.match(vulnPattern) || [])];
return {
cvssPattern: text.match(/CVSS[\s:]+[\d.]+/g) || [],
cveCount: cves.length,
cves
};
}
This becomes valuable when integrating with commercial scanners that produce PDF reports. Claude Code can parse these and extract actionable data.
Semgrep for Custom Rule Writing
Semgrep is exceptional for codebases with custom security requirements. Unlike generic SAST tools, you write rules in the same language as your source code:
.semgrep/no-hardcoded-hosts.yml
rules:
- id: no-hardcoded-production-host
patterns:
- pattern: $X = "prod.internal.company.com"
- pattern: $X = "10.0.0.$Y"
message: "Hardcoded production host found: $X. Use environment variables."
languages: [python, javascript, typescript]
severity: WARNING
- id: sql-injection-fstring
pattern: |
cursor.execute(f"... {$USERINPUT} ...")
message: "Possible SQL injection via f-string. Use parameterized queries."
languages: [python]
severity: ERROR
Run Semgrep in Claude Code by pointing it at your rules directory:
semgrep --config .semgrep/ --json --output semgrep-results.json .
Then use Claude Code to analyze the results file: “Read semgrep-results.json and group findings by severity. For the ERROR-level findings, show me the file and line, and suggest a fix for each one.”
Bandit for Python Security Analysis
Bandit integrates tightly with Python workflows:
Run Bandit and save structured output
bandit -r ./src -f json -o bandit-report.json -ll
Show summary
python3 - <<'EOF'
import json, sys
report = json.load(open("bandit-report.json"))
results = report["results"]
by_severity = {}
for r in results:
sev = r["issue_severity"]
by_severity.setdefault(sev, []).append(r)
for sev in ["HIGH", "MEDIUM", "LOW"]:
items = by_severity.get(sev, [])
print(f"{sev}: {len(items)} issues")
for item in items[:3]:
print(f" {item['filename']}:{item['line_number']}. {item['issue_text']}")
EOF
The most common Bandit findings and their fixes:
| Bandit Issue ID | Description | Fix |
|---|---|---|
| B101 | Use of assert for security checks |
Replace with explicit if + raise |
| B105 | Hardcoded password string | Move to environment variable |
| B301 | pickle.loads on untrusted data |
Use json or msgpack instead |
| B324 | Use of weak MD5/SHA1 hash | Switch to SHA-256 or bcrypt |
| B501 | TLS verification disabled | Remove verify=False from requests |
| B602 | subprocess with shell=True |
Use list args, set shell=False |
Container Security Scanning
Containerized applications require their own scanning layer. Trivy and Grype are popular open-source tools that integrate easily:
#!/bin/bash
Scan container image for vulnerabilities
IMAGE=$1
if [ -z "$IMAGE" ]; then
echo "Usage: $0 <image:tag>"
exit 1
fi
echo "Scanning $IMAGE..."
TRIVY_OUTPUT=$(trivy image --format json --severity HIGH,CRITICAL --quiet "$IMAGE")
CRITICAL=$(echo "$TRIVY_OUTPUT" | jq '[.Results[].Vulnerabilities // [] | .[] | select(.Severity == "CRITICAL")] | length')
HIGH=$(echo "$TRIVY_OUTPUT" | jq '[.Results[].Vulnerabilities // [] | .[] | select(.Severity == "HIGH")] | length')
echo "Critical: $CRITICAL | High: $HIGH"
if [ "$CRITICAL" -gt 0 ]; then
echo "BLOCKED: Critical vulnerabilities found"
echo "$TRIVY_OUTPUT" | jq '[.Results[] | select(.Vulnerabilities) | .Vulnerabilities[] | select(.Severity == "CRITICAL") | {id: .VulnerabilityID, pkg: .PkgName, installed: .InstalledVersion, fixed: .FixedVersion}] | .[0:10]'
exit 1
fi
echo "Container scan passed"
exit 0
In Claude Code, you can wrap this in a skill that accepts an image name and returns structured vulnerability data:
export async function scanContainer(imageName) {
const result = await exec(`trivy image --format json --quiet ${imageName}`);
const parsed = JSON.parse(result.stdout);
const allVulns = parsed.Results
.flatMap(r => r.Vulnerabilities || []);
const critical = allVulns.filter(v => v.Severity === 'CRITICAL');
const high = allVulns.filter(v => v.Severity === 'HIGH');
const fixable = allVulns.filter(v => v.FixedVersion);
return {
image: imageName,
criticalCount: critical.length,
highCount: high.length,
fixableCount: fixable.length,
criticalVulns: critical.map(v => ({
id: v.VulnerabilityID,
pkg: v.PkgName,
installed: v.InstalledVersion,
fixed: v.FixedVersion || "no fix available"
}))
};
}
Base Image Strategy
The single most impactful container security decision is your base image. Most container vulnerabilities come from the OS layer, not your application code:
| Base Image | Typical Vulnerability Count | Notes |
|---|---|---|
ubuntu:latest |
50–150+ | Large attack surface; convenient for dev |
ubuntu:22.04 (pinned) |
30–80 | Predictable; security updates via rebuild |
debian:slim |
20–60 | Smaller footprint than full Debian |
alpine:3.19 |
5–20 | Minimal, musl libc; some compatibility tradeoffs |
distroless/base |
0–5 | No shell, no package manager; harder to debug |
scratch |
0 | Binary only; requires statically linked apps |
Claude Code can help you evaluate base image tradeoffs for your specific application. Share your Trivy output and ask: “Which of these vulnerabilities are in the base image versus my application dependencies? What base image change would eliminate the most critical findings?”
CI/CD Pipeline Integration
Putting it all together, your CI/CD pipeline benefits from layered security scanning:
.github/workflows/security-scan.yml
name: Security Scans
on: [push, pull_request]
jobs:
dependency-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm audit --audit-level=high
- name: Upload audit results
if: failure()
uses: actions/upload-artifact@v4
with:
name: npm-audit
path: npm-audit.json
secret-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for git log scanning
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
sast-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: p/owasp-top-ten
- name: Run Bandit (Python)
if: hashFiles('/*.py') != ''
run: |
pip install bandit
bandit -r . -f json -o bandit.json -ll || true
- uses: actions/upload-artifact@v4
with:
name: sast-results
path: "*.json"
container-scan:
runs-on: ubuntu-latest
needs: build
steps:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: sarif
output: trivy-results.sarif
severity: HIGH,CRITICAL
exit-code: '1'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarif
Each stage runs in parallel where possible, giving you fast feedback. Claude Code hooks can trigger these scans automatically, ensuring security checks happen consistently without manual intervention.
Handling Pipeline Failures Gracefully
A common mistake is making all security findings pipeline-blocking from day one. Teams push back, start bypassing hooks, and the automation erodes. A graduated approach works better:
Phase 1: Warn only (week 1-2)
- run: npm audit --audit-level=critical || echo "::warning::Vulnerabilities found"
Phase 2: Block on critical (week 3-4)
- run: npm audit --audit-level=critical
Phase 3: Block on high (ongoing)
- run: npm audit --audit-level=high
Claude Code helps enforce this policy by tracking which findings existed before the policy change. Ask Claude to “compare this week’s audit output against last week’s baseline and show me only new findings.”
Building Custom Security Workflows
The real power of Claude Code for security automation comes from combining these tools into custom workflows. You can create skills that:
- Run multiple scanners in sequence, aggregating results into a unified severity report
- Filter findings based on your team’s risk tolerance and known-false-positive list
- Generate remediation suggestions automatically by matching CVE IDs against fix databases
- Track vulnerability trends over time by comparing scan results stored in your repo
Here is an aggregator script that combines npm audit, gitleaks, and Semgrep into a single prioritized report:
#!/usr/bin/env python3
"""
Aggregate security scan results from multiple tools.
Usage: python3 aggregate-security.py --npm audit.json --gitleaks gl.json --semgrep sg.json
"""
import json
import argparse
from dataclasses import dataclass, field
from typing import List
@dataclass
class Finding:
tool: str
severity: str
title: str
location: str
remediation: str = ""
def load_npm_audit(path: str) -> List[Finding]:
data = json.load(open(path))
findings = []
for name, vuln in data.get("vulnerabilities", {}).items():
if vuln.get("severity") in ("critical", "high"):
findings.append(Finding(
tool="npm-audit",
severity=vuln["severity"].upper(),
title=f"{name}. {', '.join(v.get('title', '') for v in vuln.get('via', []) if isinstance(v, dict))}",
location=f"package: {name}@{vuln.get('range', 'unknown')}",
remediation="Run: npm audit fix" if vuln.get("fixAvailable") else "No automatic fix available"
))
return findings
def load_gitleaks(path: str) -> List[Finding]:
data = json.load(open(path)) or []
return [Finding(
tool="gitleaks",
severity="CRITICAL",
title=f"Exposed secret: {f.get('RuleID', 'unknown rule')}",
location=f"{f.get('File', '?')}:{f.get('StartLine', '?')}",
remediation="Remove secret, rotate credential, purge from git history"
) for f in data]
def load_semgrep(path: str) -> List[Finding]:
data = json.load(open(path))
findings = []
for r in data.get("results", []):
sev = r.get("extra", {}).get("severity", "INFO")
if sev in ("ERROR", "WARNING"):
findings.append(Finding(
tool="semgrep",
severity="HIGH" if sev == "ERROR" else "MEDIUM",
title=r.get("check_id", "unknown"),
location=f"{r['path']}:{r['start']['line']}",
remediation=r.get("extra", {}).get("message", "See semgrep docs")
))
return findings
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--npm", help="npm audit JSON path")
parser.add_argument("--gitleaks", help="gitleaks JSON path")
parser.add_argument("--semgrep", help="semgrep JSON path")
args = parser.parse_args()
all_findings = []
if args.npm: all_findings += load_npm_audit(args.npm)
if args.gitleaks: all_findings += load_gitleaks(args.gitleaks)
if args.semgrep: all_findings += load_semgrep(args.semgrep)
order = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}
all_findings.sort(key=lambda f: order.get(f.severity, 4))
print(f"\n=== Security Scan Summary: {len(all_findings)} findings ===\n")
for f in all_findings:
print(f"[{f.severity}] ({f.tool}) {f.title}")
print(f" Location: {f.location}")
print(f" Remediation: {f.remediation}\n")
For teams using the frontend-design skill to build React applications, adding security scanning to the component generation workflow catches issues like unsafe DOM manipulation or missing CSRF protections early. Ask Claude Code to run the SAST scan immediately after generating a new component, before the code is committed.
Measuring Security Posture Over Time
Automation without measurement is incomplete. Track these metrics sprint over sprint:
| Metric | How to Measure | Target Direction |
|---|---|---|
| Mean time to patch critical CVEs | Date found → date fixed in main | Decreasing |
| Secrets found in PRs vs commits | gitleaks PR count vs commit count | PRs > commits (caught earlier) |
| New SAST findings per sprint | Semgrep delta between sprints | Decreasing or stable |
| Container vulnerability age | Average days since CVE published | Decreasing |
| % of builds with security gates | CI runs with security jobs / total | 100% |
Claude Code can generate a weekly security posture report from your scan artifacts:
Generate weekly delta report
Compare this week's aggregated findings against last week's snapshot
python3 aggregate-security.py \
--npm npm-audit-this-week.json \
--gitleaks gl-this-week.json \
--semgrep sg-this-week.json \
> this-week.txt
diff last-week.txt this-week.txt | grep "^[<>]"
Pass the diff output to Claude Code with the prompt: “Summarize what’s new and what’s been resolved compared to last week. Flag anything that’s been open for more than two weeks.”
The key is treating security scanning as code. version controlled, automated, and integrated into your development workflow. Claude Code provides the automation layer that makes this practical without adding friction to your development process.
Try it: Paste your error into our Error Diagnostic for an instant fix.
Related Reading
- Claude Code Security Code Review Checklist Automation
- AI Coding Tools Security Concerns Enterprise Guide
- Brave Search MCP Server for Research Automation
Built by theluckystrike. More at zovo.one
Find the right skill → Browse 155+ skills in our Skill Finder.
Configure MCP → Build your server config with our MCP Config Generator.