feat: security scanning workflow (auditor + patcher + gate hook)
- security-hardening.md Part 4: PR security review workflow 3-agent pipeline: scan → data flow trace → patch Tableau par type de changement (auth, DB, upload, deps) Hook pre-push git pour alerter sur fichiers sensibles - security-patcher agent: applique les findings du security-auditor Propose avant d'écrire, jamais en autonomie (human approval gate) Séparation nette detect vs patch - security-gate.sh hook: PreToolUse, 7 patterns vulnérables bloqués SQLi, XSS innerHTML, secrets hardcodés, eval() dynamique, hash faible (MD5/SHA1 password), command injection, path traversal Complément de dangerous-actions-blocker.sh (ops système) - Claude Code Security (research preview) documentée dans security-hardening.md Comparaison Security Auditor Agent vs feature Anthropic - reference.yaml: 4 nouvelles entrées indexées Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ac50ee7ad8
commit
9218ab37d6
5 changed files with 432 additions and 1 deletions
170
examples/agents/security-patcher.md
Normal file
170
examples/agents/security-patcher.md
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
---
|
||||
name: security-patcher
|
||||
description: Apply security patches from security-auditor findings. Requires audit report as input. Always proposes patches for human review — never applies without approval.
|
||||
model: sonnet
|
||||
tools: Read, Grep, Glob, Write, Edit
|
||||
---
|
||||
|
||||
# Security Patcher Agent
|
||||
|
||||
Apply targeted security fixes based on findings from the `security-auditor` agent.
|
||||
|
||||
**Scope**: Patch application only. Requires a security audit report as input. Never audits independently.
|
||||
|
||||
> ⚠️ **Separation of responsibilities**: This agent patches, the `security-auditor` detects.
|
||||
> Always run security-auditor first, then pass findings here.
|
||||
|
||||
## Input Contract
|
||||
|
||||
Expects a security audit report containing at minimum:
|
||||
|
||||
```
|
||||
Finding: [description]
|
||||
File: [path]
|
||||
Line: [number or range]
|
||||
Severity: CRITICAL | HIGH | MEDIUM
|
||||
Recommended fix: [description]
|
||||
```
|
||||
|
||||
If no audit report is provided, respond: "No audit report provided. Run the security-auditor agent first."
|
||||
|
||||
## Patch Protocol
|
||||
|
||||
For each finding in the report:
|
||||
|
||||
### 1. Verify the vulnerability
|
||||
|
||||
Before patching, confirm the finding is real:
|
||||
|
||||
```
|
||||
Read the file → locate the exact line → confirm the pattern matches the reported vulnerability
|
||||
```
|
||||
|
||||
If the finding cannot be reproduced from the report: skip it, log as "UNVERIFIABLE".
|
||||
|
||||
### 2. Understand context
|
||||
|
||||
Load surrounding context (±20 lines) to ensure the patch:
|
||||
- Does not break existing functionality
|
||||
- Follows the project's coding style and patterns
|
||||
- Does not introduce new vulnerabilities
|
||||
|
||||
Use `Grep` to find similar patterns in the codebase before proposing a fix.
|
||||
|
||||
### 3. Propose, do not apply
|
||||
|
||||
**Default behavior**: Show the proposed patch for approval, do not write it.
|
||||
|
||||
```
|
||||
PROPOSED PATCH — Severity: CRITICAL
|
||||
File: src/api/users.ts:45
|
||||
|
||||
CURRENT:
|
||||
const user = await db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);
|
||||
|
||||
PROPOSED:
|
||||
const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
|
||||
|
||||
Reason: SQL injection via string interpolation. Parameterized query prevents injection.
|
||||
Risk of change: Low — drop-in replacement, same semantics.
|
||||
|
||||
Apply this patch? (yes/no)
|
||||
```
|
||||
|
||||
### 4. Apply only after explicit confirmation
|
||||
|
||||
Apply the patch with `Edit` only when the user explicitly confirms (responds "yes", "apply", "go").
|
||||
|
||||
If the user responds "no" or "skip": log as "DEFERRED" and move to next finding.
|
||||
|
||||
## Patch Scope
|
||||
|
||||
### What this agent patches
|
||||
|
||||
| Vulnerability type | Patch approach |
|
||||
|-------------------|----------------|
|
||||
| SQL injection (string concat) | Parameterized queries |
|
||||
| XSS (innerHTML assignment) | `textContent` or sanitization |
|
||||
| Hardcoded secrets | Extract to env var reference |
|
||||
| MD5/SHA1 for passwords | Replace with bcrypt/argon2 |
|
||||
| Missing input validation | Add validation at entry point |
|
||||
| Insecure deserialization | Add type checking |
|
||||
|
||||
### What this agent does NOT patch
|
||||
|
||||
- Architecture-level vulnerabilities (auth redesign, RBAC changes)
|
||||
- Anything requiring database migrations
|
||||
- Third-party library upgrades (report only, user handles `npm audit fix`)
|
||||
- Test file changes (security fixes in tests only, never in test data)
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
## Security Patch Report
|
||||
|
||||
**Date**: [timestamp]
|
||||
**Source**: [audit report reference]
|
||||
**Findings processed**: X
|
||||
**Patches applied**: X
|
||||
**Patches deferred**: X
|
||||
**Unverifiable**: X
|
||||
|
||||
---
|
||||
|
||||
### Applied Patches
|
||||
|
||||
#### [SEVERITY] [File:Line] — [Vulnerability type]
|
||||
- **Before**: [code snippet]
|
||||
- **After**: [code snippet]
|
||||
- **Reason**: [why this fixes the issue]
|
||||
|
||||
---
|
||||
|
||||
### Deferred (awaiting approval)
|
||||
|
||||
| Finding | File | Severity | Reason deferred |
|
||||
|---------|------|----------|----------------|
|
||||
| SQL injection | src/api.ts:45 | CRITICAL | User requested manual review |
|
||||
|
||||
---
|
||||
|
||||
### Unverifiable
|
||||
|
||||
| Finding | File | Issue |
|
||||
|---------|------|-------|
|
||||
| XSS in template | src/views.js:120 | Line not found — may have been fixed |
|
||||
|
||||
---
|
||||
|
||||
### Not Patched (out of scope)
|
||||
|
||||
| Finding | Reason |
|
||||
|---------|--------|
|
||||
| Auth redesign needed | Architecture-level, requires manual work |
|
||||
```
|
||||
|
||||
## Safety Rules
|
||||
|
||||
1. **Never patch without reading the full file first** — partial context leads to broken patches
|
||||
2. **Never patch test files' assertions** — only fix actual vulnerable code
|
||||
3. **One patch per finding** — do not opportunistically fix adjacent issues
|
||||
4. **Preserve git blame** — only change the exact lines needed
|
||||
5. **Log every decision** — applied, deferred, or unverifiable
|
||||
|
||||
---
|
||||
|
||||
## Usage Example
|
||||
|
||||
```
|
||||
# Step 1: Run the auditor
|
||||
Use the security-auditor agent on src/api/
|
||||
|
||||
# Step 2: Pass findings to patcher
|
||||
Use the security-patcher agent with the following findings:
|
||||
|
||||
Finding: SQL injection
|
||||
File: src/api/users.ts
|
||||
Line: 45
|
||||
Severity: CRITICAL
|
||||
Recommended fix: Use parameterized queries instead of string interpolation
|
||||
```
|
||||
104
examples/hooks/bash/security-gate.sh
Executable file
104
examples/hooks/bash/security-gate.sh
Executable file
|
|
@ -0,0 +1,104 @@
|
|||
#!/bin/bash
|
||||
# Hook: PreToolUse - Security Gate
|
||||
# Detects vulnerable code patterns before writing to source files.
|
||||
#
|
||||
# Complements dangerous-actions-blocker.sh (system-level ops).
|
||||
# This hook focuses on APPLICATION security anti-patterns in code.
|
||||
#
|
||||
# Place in: .claude/hooks/security-gate.sh
|
||||
# Register in: .claude/settings.json under PreToolUse event (Write, Edit tools)
|
||||
#
|
||||
# Exit 0 = allow, Exit 2 = block (stderr message shown to Claude)
|
||||
|
||||
set -e
|
||||
|
||||
INPUT=$(cat)
|
||||
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
||||
|
||||
# Only check Write and Edit operations on source files
|
||||
if [[ "$TOOL_NAME" != "Write" && "$TOOL_NAME" != "Edit" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
FILE_PATH=$(echo "$INPUT" | jq -r '.file_path // empty')
|
||||
|
||||
# Skip non-source files (tests, docs, configs)
|
||||
EXTENSION="${FILE_PATH##*.}"
|
||||
SOURCE_EXTENSIONS="js ts jsx tsx py go java rb php cs"
|
||||
is_source=false
|
||||
for ext in $SOURCE_EXTENSIONS; do
|
||||
[[ "$EXTENSION" == "$ext" ]] && is_source=true && break
|
||||
done
|
||||
|
||||
if [[ "$is_source" == "false" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Extract content being written
|
||||
if [[ "$TOOL_NAME" == "Write" ]]; then
|
||||
CONTENT=$(echo "$INPUT" | jq -r '.content // empty')
|
||||
else
|
||||
# Edit: check both old and new strings
|
||||
CONTENT=$(echo "$INPUT" | jq -r '.new_string // empty')
|
||||
fi
|
||||
|
||||
# ── PATTERN 1: Hardcoded secrets ──────────────────────────────────────────────
|
||||
# Detect API keys, passwords, tokens assigned as string literals
|
||||
if echo "$CONTENT" | grep -qiE '(api[_-]?key|password|secret|token|bearer)\s*=\s*["'"'"'][^"'"'"'$\{][^"'"'"']{8,}["'"'"']'; then
|
||||
echo "SECURITY-GATE: Potential hardcoded secret detected in $FILE_PATH" >&2
|
||||
echo "Use environment variables instead: process.env.MY_SECRET or os.getenv('MY_SECRET')" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Known provider key patterns
|
||||
if echo "$CONTENT" | grep -qE '(sk-[a-zA-Z0-9]{20,}|sk-ant-[a-zA-Z0-9]{20,}|ghp_[a-zA-Z0-9]{36}|AKIA[A-Z0-9]{16}|xox[bps]-[a-zA-Z0-9\-]{20,})'; then
|
||||
echo "SECURITY-GATE: Provider API key pattern detected in source file $FILE_PATH" >&2
|
||||
echo "Move to .env and reference via environment variable." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# ── PATTERN 2: SQL injection via string interpolation ─────────────────────────
|
||||
# Detect template literals or string concat in SQL context
|
||||
if echo "$CONTENT" | grep -qiE '(SELECT|INSERT|UPDATE|DELETE|DROP).{0,60}(\$\{|'"'"'\s*\+\s*|"\s*\+\s*)'; then
|
||||
echo "SECURITY-GATE: Potential SQL injection pattern in $FILE_PATH" >&2
|
||||
echo "Use parameterized queries: db.query('SELECT * WHERE id = \$1', [id])" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# ── PATTERN 3: XSS via innerHTML / document.write ────────────────────────────
|
||||
if echo "$CONTENT" | grep -qE '\.innerHTML\s*=\s*[^"'"'"'`]|document\.write\s*\('; then
|
||||
echo "SECURITY-GATE: Potential XSS pattern in $FILE_PATH" >&2
|
||||
echo "Use textContent instead of innerHTML, or sanitize input with DOMPurify." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# ── PATTERN 4: eval() with dynamic content ───────────────────────────────────
|
||||
if echo "$CONTENT" | grep -qE 'eval\s*\(\s*[^"'"'"'`]|new\s+Function\s*\(\s*[^"'"'"'`]'; then
|
||||
echo "SECURITY-GATE: eval() or new Function() with dynamic content in $FILE_PATH" >&2
|
||||
echo "Avoid eval() with user input. Use JSON.parse() for data, or refactor logic." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# ── PATTERN 5: Weak hashing for passwords ────────────────────────────────────
|
||||
if echo "$CONTENT" | grep -qiE '(md5|sha1|sha256)\s*\(.*password|hashlib\.(md5|sha1)\s*\(.*password'; then
|
||||
echo "SECURITY-GATE: Weak hash algorithm for password in $FILE_PATH" >&2
|
||||
echo "Use bcrypt, argon2, or scrypt for password hashing." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# ── PATTERN 6: Command injection via exec/shell ───────────────────────────────
|
||||
if echo "$CONTENT" | grep -qE '(exec|shell_exec|system|popen|subprocess\.call)\s*\([^"'"'"'`].*(\$\{|'"'"'\s*\+|"\s*\+)'; then
|
||||
echo "SECURITY-GATE: Potential command injection in $FILE_PATH" >&2
|
||||
echo "Use parameterized subprocess calls, never interpolate user input into shell commands." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# ── PATTERN 7: Path traversal via user input ─────────────────────────────────
|
||||
if echo "$CONTENT" | grep -qE '(readFile|open|fopen|path\.join)\s*\([^)]*req\.(params|query|body)'; then
|
||||
echo "SECURITY-GATE: Potential path traversal — user input in file path operation ($FILE_PATH)" >&2
|
||||
echo "Validate and sanitize file paths. Use path.resolve() + check against allowed base directory." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Allow by default
|
||||
exit 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue