SuperAPI AI Directory

auditing-python-security

Coding & development

View repository

Updated 10-16 00:24

About this skill

Python Security Auditing

Quick Start

# Run all four scanners (gates CI): exit 1 on a blocking finding, exit 2 when a
# requested scanner could not run, exit 0 only when every scanner ran clean:
uv run python scripts/security_scan.py .

# Tolerate scanners that are missing or hung (restores exit 0 in that case):
uv run python scripts/security_scan.py . --allow-scanner-failure

# Or individually:
uvx bandit -r src/ -ll                       # High-severity static analysis
uvx pip-audit .                              # This project's dependencies
uvx semgrep --config auto src/               # Pattern-based SAST
uvx detect-secrets scan > .secrets.baseline  # Secrets detection

Tool Configuration

Bandit (.bandit):

exclude_dirs: [tests/, docs/, .venv/]
skips: [B101]  # assert_used - OK in tests

pip-audit:

uvx pip-audit -r requirements.txt     # Scan requirements
uvx pip-audit --fix                   # Auto-fix vulnerabilities

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

# SQL - Parameterized query
conn.execute("SELECT * FROM users WHERE id = ?", (user_id,))

# Commands - No shell
subprocess.run(["cat", filename], check=True)

# Secrets - Environment
API_KEY = os.environ.get("API_KEY")

# Paths - Validate
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  # proves the hook 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

# .github/workflows/security.yml — full workflow in CI_SECURITY.md
- 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 (exit 1) or on a requested scanner that could not run (exit 2, opt out with --allow-scanner-failure) (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: