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.

I'm a solo developer in Vietnam. 50K Chrome extension users. $500K+ on Upwork. 5 Claude Max subscriptions running agent fleets in parallel. These are my actual CLAUDE.md templates, orchestration configs, and prompts. Not a course. Not theory. The files I copy into every project before I write a line of code. **[See what's inside →](https://zovo.one/lifetime?utm_source=ccg&utm_medium=cta-default&utm_campaign=claude-code-for-security-scan-automation)** $99 once. Free forever. 47/500 founding spots left.

Related Reading

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.