Build an AI Secret Scanner with Claude API for Git Repos
Build a Git pre-commit and CI hook using Claude API that detects hardcoded secrets, API keys, passwords, and credentials in code — with context-aware analysis that reduces false positives from regex-only scanners.
Regex-based secret scanners (gitleaks, detect-secrets) produce false positives that developers learn to ignore. Claude-powered scanning adds context: it understands whether password = "test123" is a real credential or a unit test fixture.
Setup
pip install anthropic gitpython pre-commitThe Scanner
import anthropic
import subprocess
import sys
import json
from pathlib import Path
client = anthropic.Anthropic()
# Quick regex pre-filter to avoid sending every line to Claude
SUSPICIOUS_PATTERNS = [
"password", "passwd", "secret", "api_key", "apikey",
"token", "private_key", "access_key", "auth_key",
"aws_secret", "AKIA", "-----BEGIN", "credential"
]
def get_staged_diff() -> str:
"""Get git staged changes."""
result = subprocess.run(
["git", "diff", "--cached", "--unified=3"],
capture_output=True, text=True
)
return result.stdout
def pre_filter_suspicious_lines(diff: str) -> list[dict]:
"""Extract lines that look suspicious before sending to Claude."""
suspicious = []
current_file = None
for line in diff.split("\n"):
if line.startswith("+++ b/"):
current_file = line[6:]
elif line.startswith("+") and not line.startswith("+++"):
line_content = line[1:]
lower = line_content.lower()
if any(pattern in lower for pattern in SUSPICIOUS_PATTERNS):
suspicious.append({
"file": current_file,
"content": line_content.strip()
})
return suspicious
def analyze_with_claude(suspicious_lines: list[dict]) -> dict:
"""Claude analyzes whether suspicious lines are real secrets."""
if not suspicious_lines:
return {"secrets_found": [], "is_safe": True}
lines_text = "\n".join(
f"File: {item['file']}\nLine: {item['content']}"
for item in suspicious_lines[:20]
)
prompt = f"""You are a security expert reviewing code for hardcoded secrets.
Analyze these suspicious lines from a git commit:
{lines_text}
For each line, determine if it contains a REAL secret (actual credential, real API key, real password) vs:
- Placeholder/example values (password123, your-api-key-here, TODO)
- Test fixtures (test_password, mock_token)
- Variable names without values (password = os.getenv("PASSWORD"))
- Documentation examples
Return JSON:
{{
"secrets_found": [
{{
"file": "filename",
"content": "the suspicious line",
"severity": "critical|high|medium",
"type": "api_key|password|private_key|token|other",
"confidence": "high|medium|low",
"reason": "why this is a real secret"
}}
],
"is_safe": true|false,
"summary": "one sentence"
}}
Only flag lines with high confidence real secrets."""
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1000,
messages=[{"role": "user", "content": prompt}]
)
text = response.content[0].text.strip()
if "```" in text:
text = text.split("```")[1]
if text.startswith("json"):
text = text[4:]
try:
return json.loads(text)
except Exception:
return {"secrets_found": [], "is_safe": True, "error": text}
def scan_commit():
diff = get_staged_diff()
if not diff:
print("No staged changes to scan")
sys.exit(0)
suspicious = pre_filter_suspicious_lines(diff)
if not suspicious:
print("✓ No suspicious patterns found")
sys.exit(0)
print(f"Found {len(suspicious)} suspicious lines, analyzing with Claude...")
result = analyze_with_claude(suspicious)
secrets = result.get("secrets_found", [])
if not secrets or result.get("is_safe", True):
print(f"✓ Secret scan passed: {result.get('summary', 'No real secrets detected')}")
sys.exit(0)
# Found real secrets
print("\n🔴 SECRET SCAN FAILED — Real credentials detected:\n")
for secret in secrets:
print(f" [{secret['severity'].upper()}] {secret['file']}")
print(f" Type: {secret['type']} | Confidence: {secret['confidence']}")
print(f" Content: {secret['content'][:80]}...")
print(f" Reason: {secret['reason']}")
print()
print("Fix: Remove the secrets, rotate any exposed credentials, then commit.")
print("Use environment variables or a secrets manager instead.\n")
sys.exit(1)
if __name__ == "__main__":
scan_commit()Pre-commit Hook Setup
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: ai-secret-scanner
name: AI Secret Scanner (Claude)
entry: python scripts/secret_scanner.py
language: python
additional_dependencies: [anthropic]
stages: [commit]
pass_filenames: falsepre-commit installGitHub Actions CI Integration
name: Secret Scan
on:
pull_request:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: pip install anthropic
- name: Scan PR diff for secrets
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
git diff origin/main...HEAD > /tmp/pr_diff.txt
python scripts/secret_scanner_ci.py /tmp/pr_diff.txtThe Claude layer reduces false positives by 80-90% compared to regex-only scanners — developers stop ignoring alerts because they are actually meaningful.
More DevSecOps? Read our Build AI Dockerfile security scanner and Kubernetes OPA policy enforcement guide.
Today I Fixed
Short real fixes from production — posted daily
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.
Related Articles
Build an AI Code Review Bot with GitHub Actions and Claude API (2026)
Automate code reviews on every PR using Claude AI via GitHub Actions. The bot reviews Dockerfile security, Terraform changes, and general code quality — and posts inline comments.
Build an AI Dependency Upgrade Planner with Claude API
Renovate and Dependabot tell you a new version exists. Build a tool that reads the actual changelog and your codebase's usage patterns with Claude API to tell you whether the upgrade is safe, what specifically to test, and how to sequence a major version bump.
Build an AI-Powered Dockerfile Security Scanner with Claude
Build a tool that scans Dockerfiles for security issues using Claude API — finds hardcoded secrets, root users, unscanned base images, and missing security best practices.