From code-security
Scans Python code for vulnerabilities using Bandit, pip-audit, ruff S-rules, detect-secrets, safety; adds LLM analysis for logic flaws, auth bypasses, race conditions.
npx claudepluginhub sam-dumont/claude-skills --plugin code-securityThis skill uses the workspace's default tool permissions.
This skill combines **static analysis tools** with **LLM-powered dynamic analysis** to provide
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
This skill combines static analysis tools with LLM-powered dynamic analysis to provide comprehensive security coverage. Static tools catch known patterns; Claude reasons about logic flaws, business rule bypasses, and context-dependent vulnerabilities that no static tool can detect.
┌─────────────────────────────────────────────────────────┐
│ Layer 1: Static Analysis (Automated) │
│ Bandit · pip-audit · ruff S-rules · secrets detection │
│ → Known vulnerability patterns, CVEs, exposed secrets │
├─────────────────────────────────────────────────────────┤
│ Layer 2: LLM Dynamic Analysis (Claude) │
│ Code path reasoning · business logic review │
│ → Logic flaws, auth bypasses, race conditions, │
│ context-dependent vulnerabilities │
└─────────────────────────────────────────────────────────┘
| Tool | Purpose | Invocation |
|---|---|---|
| Bandit | Python-specific security linter (SAST) | uvx bandit -r src/ |
| pip-audit | Dependency CVE scanner | uvx pip-audit |
| ruff S-rules | Bandit rules integrated in ruff | uv run ruff check --select S src/ |
| detect-secrets | Secrets/credentials in source code | uvx detect-secrets scan |
| safety | Alternative dependency vulnerability check | uvx safety check |
Add these targets to the project Makefile:
# =============================================================================
# Security
# =============================================================================
# Security lint (Bandit) — high severity only for CI gates
security-lint:
uvx bandit -r src/ --severity-level high -q
# Full security lint (all severities) — for thorough review
security-lint-full:
uvx bandit -r src/ -f json -o bandit-report.json || true
@echo "Full report: bandit-report.json"
uvx bandit -r src/
# Dependency vulnerability audit
pip-audit:
uvx pip-audit -r <(uv pip freeze | grep -v -e '^-e ' -e '^$(PROJECT_NAME)==') --strict
# Secrets detection
secrets-scan:
uvx detect-secrets scan --all-files --exclude-files '\.git/.*' | python -c "import sys,json; r=json.load(sys.stdin); findings=r.get('results',{}); exit(1) if findings else print('No secrets found.')"
# Combined security gate for CI
security-check: security-lint pip-audit secrets-scan
@echo "All security checks passed!"
Add to pyproject.toml:
[tool.bandit]
exclude_dirs = ["tests", "scripts"]
skips = [] # Don't skip anything by default — be explicit about exceptions
[tool.bandit.assert_used]
skips = ["*/test_*.py", "*_test.py"]
Bandit catches these categories:
| ID | Category | Example |
|---|---|---|
| B101 | assert usage | assert in production code |
| B102 | exec usage | exec() / eval() calls |
| B103 | set_bad_file_permissions | os.chmod(path, 0o777) |
| B104 | hardcoded_bind_all | Binding to 0.0.0.0 |
| B105-B107 | hardcoded_password | Passwords in source code |
| B108 | hardcoded_tmp_directory | Using /tmp directly |
| B301-B303 | pickle / md5 / sha1 | Insecure deserialization/hashing |
| B501-B503 | SSL/TLS issues | verify=False, no cert validation |
| B601-B602 | shell injection | subprocess.call(shell=True) |
| B608 | SQL injection | String-formatted SQL queries |
| B701 | jinja2 autoescape | Templates without autoescaping |
This is what makes this skill unique. When triggered, Claude performs reasoning-based security analysis that goes beyond pattern matching.
How to check:
Review all route/endpoint definitions. For each one:
1. Does it have authentication middleware/decorator?
2. Does it verify the authenticated user owns the requested resource?
3. Are role checks applied for privileged operations?
../../etc/passwd in file parameters)How to check:
Trace every user input from entry point to usage:
1. HTTP parameters, headers, body fields
2. File uploads (name, content, MIME type)
3. Environment variables from untrusted sources
4. Data from external APIs or databases (second-order injection)
os.path.join used with absolute user paths? (it ignores the base!)tempfile.mkstemp vs /tmp/myfile)How to check:
# VULNERABLE: os.path.join ignores base when second arg is absolute
path = os.path.join("/safe/dir", user_input) # user_input = "/etc/passwd" → "/etc/passwd"
# SAFE: resolve and check prefix
base = Path("/safe/dir").resolve()
target = (base / user_input).resolve()
if not str(target).startswith(str(base)):
raise ValueError("Path traversal detected")
.env files in .gitignore?DEBUG = True possible in production?How to check:
Search for patterns:
- "password", "secret", "api_key", "token", "credential" in source
- Base64-encoded strings that decode to credentials
- URLs with embedded credentials (postgres://user:pass@host)
- Default values in config that look like real secrets
How to check:
Look for patterns:
1. if file_exists(x): read(x) → file could be replaced between check and read
2. if user.balance >= amount: user.balance -= amount → without DB transaction lock
3. Check permission, then perform action in separate DB queries
requirements.txt used without hashes?uv.lock, poetry.lock) committed?When asked to perform a security review, follow this protocol:
# Run all static tools
make security-lint-full # Bandit full report
make pip-audit # Dependency CVEs
make secrets-scan # Exposed secrets
uv run ruff check --select S src/ # Ruff security rules
Systematically review these areas using parallel agents where possible:
Entry points audit: Map all HTTP endpoints, CLI commands, message handlers. For each: verify auth, input validation, output encoding.
Data flow tracing: Follow user input from entry to storage/output. Flag any point where input is used unsanitized.
Auth & access control review: Check every protected resource for IDOR, privilege escalation, missing auth checks.
File & path operations: Find all file I/O and verify paths are constrained.
Crypto & secrets review: Check for hardcoded secrets, weak algorithms, improper key management.
Configuration review: Check for debug modes, permissive CORS, missing security headers.
Structure findings using severity levels:
| Severity | Description | Example |
|---|---|---|
| CRITICAL | Exploitable now, data breach risk | SQL injection, auth bypass, RCE |
| HIGH | Exploitable with some effort | IDOR, path traversal, insecure deserialization |
| MEDIUM | Requires specific conditions | CSRF without state-changing impact, info disclosure |
| LOW | Best practice violation | Missing security headers, verbose errors in dev |
| INFO | Informational finding | Dependency update available, deprecated API usage |
For each finding, provide:
Add to the CI pipeline (GitHub Actions example):
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
- name: Security lint (Bandit)
run: uvx bandit -r src/ --severity-level high -q
- name: Dependency audit
run: uvx pip-audit -r <(uv pip freeze) --strict
- name: Secrets scan
run: |
uvx detect-secrets scan --all-files --exclude-files '\.git/.*' > /tmp/secrets.json
python -c "import json; r=json.load(open('/tmp/secrets.json')); exit(1) if r.get('results') else print('Clean')"
When setting up or reviewing a Python project, verify:
*)pip-audit passes with no known CVEs.env and secret files in .gitignore# VULNERABLE
@app.get("/files/{filename}")
def get_file(filename: str):
return FileResponse(f"/data/{filename}") # ../../etc/passwd
# SAFE
@app.get("/files/{filename}")
def get_file(filename: str):
base = Path("/data").resolve()
target = (base / filename).resolve()
if not target.is_relative_to(base):
raise HTTPException(403, "Access denied")
return FileResponse(target)
# VULNERABLE
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
# SAFE
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# VULNERABLE
os.system(f"convert {user_filename} output.png")
# SAFE
subprocess.run(["convert", user_filename, "output.png"], check=True)
# VULNERABLE
data = pickle.loads(user_data) # Arbitrary code execution
# SAFE
data = json.loads(user_data) # Only data, no code execution
# VULNERABLE
response = requests.get(user_provided_url) # Can hit internal services
# SAFE
parsed = urlparse(user_provided_url)
if parsed.hostname in ALLOWED_HOSTS:
response = requests.get(user_provided_url)
# nosec without justification: Every Bandit suppression needs a comment explaining whyverify=False in requests: Never disable SSL verification, even in devshell=True in subprocess: Almost never needed — use list argumentsexcept Exception: pass hides security errors