| name | auditing-python-security |
| description | Audits Python libraries for security vulnerabilities using Bandit, pip-audit, Semgrep, and detect-secrets. Identifies SQL injection, command injection, hardcoded credentials, secrets exposed through tracebacks, weak cryptography, and insecure deserialization. Use when reviewing library security, setting up security scanning in CI, or implementing secure coding patterns. |
Python Security Auditing
Quick Start
uv run python scripts/security_scan.py .
uvx bandit -r src/ -ll
uvx pip-audit .
uvx semgrep --config auto src/
uvx detect-secrets scan > .secrets.baseline
Tool Configuration
Bandit (.bandit):
exclude_dirs: [tests/, docs/, .venv/]
skips: [B101]
pip-audit:
uvx pip-audit -r requirements.txt
uvx pip-audit --fix
Common Vulnerabilities
| Issue | Bandit ID | Fix |
|---|
| SQL injection | B608 | Use parameterized queries |
| Command injection | B602 | subprocess without shell=True |
| Hardcoded secrets | B105, B106 | Environment variables |
| Weak crypto | B303 | Use SHA-256+, bcrypt for passwords |
| Pickle untrusted data | B301 | Use JSON instead |
| Path traversal | B108 | Validate with Path.resolve() |
Secure Patterns
conn.execute("SELECT * FROM users WHERE id = ?", (user_id,))
subprocess.run(["cat", filename], check=True)
API_KEY = os.environ.get("API_KEY")
base = Path("/data").resolve()
file_path = (base / filename).resolve()
if not file_path.is_relative_to(base):
raise ValueError("Invalid path")
Tracebacks Must Not Dump Frame Locals
Rich exception renderers can print every local variable in every stack frame.
That turns an ordinary unhandled exception into a credential leak: API tokens,
authorization headers, request bodies, and decrypted configuration commonly live
in locals when the traceback is rendered to a terminal or CI log.
Keep local-variable rendering disabled anywhere logs can leave the developer's
machine:
from rich.traceback import install
install(show_locals=False)
Do not stop at asserting the configuration call. Exercise the installed exception
hook with a sentinel secret and inspect the rendered output. This catches a later
refactor that replaces the hook or re-enables locals elsewhere:
import sys
def test_unhandled_traceback_does_not_expose_locals(capsys):
sentinel = "sentinel-secret-that-must-not-appear"
try:
raise RuntimeError("boom")
except RuntimeError:
exc_type, exc, traceback = sys.exc_info()
sys.excepthook(exc_type, exc, traceback)
output = capsys.readouterr()
rendered = output.out + output.err
assert "RuntimeError: boom" in rendered
assert sentinel not in rendered
Use a unique sentinel, never a real credential. Assert both that the exception was
rendered and that the sentinel was absent; an empty or bypassed output path must
not make the security test pass vacuously.
CI Integration
- uses: astral-sh/setup-uv@v5
- run: uv run python scripts/security_scan.py . --output security-report.json
For detailed patterns, see:
- scripts/security_scan.py — runs all four scanners and exits non-zero on blocking findings (
uv run python scripts/security_scan.py .)
- VULNERABILITIES.md - Vulnerability classes with vulnerable→fixed pairs
- CI_SECURITY.md - Complete CI workflow, pre-commit, Dependabot, triage
Audit Checklist
Code:
- [ ] No SQL injection (parameterized queries)
- [ ] No command injection (no shell=True)
- [ ] No hardcoded secrets
- [ ] Exception and logging configuration cannot render frame locals containing secrets
- [ ] No weak crypto (MD5/SHA1)
- [ ] Input validation on external data
- [ ] Path traversal prevention
- [ ] SSRF fetches connect to the validated IP on every redirect hop (DNS rebinding safe)
- [ ] SSRF deny policy covers IPv4/IPv6 and CGNAT (`100.64.0.0/10`)
Dependencies:
- [ ] pip-audit clean
- [ ] Minimal dependencies
- [ ] From trusted sources
CI:
- [ ] Security scan on every PR
- [ ] Weekly dependency scan
Learn More
This skill is based on the Security section of the Guide to Developing High-Quality Python Libraries by Will McGinnis. See these posts for deeper coverage: