Python security scanning
Local Bandit workflow, report consumption, and dismissal policy.
Bandit is the local static security scanner for first-party Python code in this monorepo. The setup is intentionally local-only for now; there is no CI or workflow automation attached to it.
What Bandit scans
Run Bandit from the repository root. The root .bandit profile points Bandit at
the Python code we own:
servicesjobspackages/python/common/srcdb/scriptsscriptsapps/raven/tools/release
The shared pyproject.toml Bandit config excludes generated code, tests,
virtualenvs, package caches, and build output. In particular,
packages/python/nest_protos is generated protobuf/Connect code and should not
be reviewed through Bandit.
Local commands
Use this command for the normal local review queue:
uvx bandit --ini .bandit --severity-level low --confidence-level highThat keeps low-severity findings visible while filtering to high-confidence results.
For a reusable report, generate Bandit's JSON and then render the local reports:
uvx bandit --ini .bandit -f json -o .bandit-results.json --exit-zero
uv run python scripts/summarize_bandit.py .bandit-results.jsonThe parser writes all supported local report formats next to the JSON file:
| File | Purpose |
|---|---|
.bandit-results.txt | Compact terminal-style review summary |
.bandit-results.md | Markdown report for notes or comments |
.bandit-results.html | Offline visual report with grouped summaries and findings |
To print the text report while generating all files:
uv run python scripts/summarize_bandit.py .bandit-results.json --print-textTo review lower-confidence findings as a secondary sweep:
uv run python scripts/summarize_bandit.py .bandit-results.json --min-confidence mediumUse --limit 0 when you want every matching finding in the generated reports.
How to triage findings
Treat Bandit findings as prompts for review, not as automatic proof of a vulnerability.
- Confirm whether the flagged line is reachable production/runtime code, developer tooling, one-shot migration code, or test-only code.
- Check whether the issue is a real vulnerability, a reliability concern, an accepted operational tradeoff, or a static-analysis false positive.
- Prefer a code change when it makes the intent safer or clearer.
- Use a targeted dismissal only after the finding has been reviewed.
Preferred fixes
Apply a code fix when the finding points at behavior that can be made safer without obscuring intent.
For B324 on MD5 used as a non-security checksum, make the non-security intent
explicit:
digest = hashlib.md5(value.encode("utf-8"), usedforsecurity=False).hexdigest()For B101 on runtime assert, use an explicit check and exception. Assertions
can be removed by optimized Python execution.
if self._statsig is None:
raise RuntimeError("Statsig client is not initialized")For B310 on urllib.request.urlopen, validate the URL scheme and expected host
before making the request. Bandit may still flag the call because the rule is
call-based; if the guard is correct, a targeted dismissal can be appropriate.
Dismissal policy
Bandit supports native inline suppressions with # nosec. Use the specific rule
ID and include a short reason.
with urllib.request.urlopen( # nosec B310: validated HTTPS issuer URL
discovery_url,
timeout=5,
) as resp:
payload = resp.read()Use this policy for dismissals:
- Prefer a code fix over a suppression.
- Suppress only the specific rule ID, for example
# nosec B101. - Add a short reason after the suppression.
- Keep suppressions on the line that Bandit flags.
- Do not use plain
# nosecunless every Bandit finding on that line is intentionally accepted. - Do not suppress generated files; exclude generated paths in config instead.
- Do not suppress test fixtures unless tests are intentionally included in a separate advisory scan.
Config-level skips and excludes
Bandit also supports config-level rule skips and path excludes.
Use path excludes for directories that should never be scanned, such as generated stubs, virtualenvs, caches, and build output.
Avoid global rule skips unless a rule is clearly not useful anywhere in this monorepo. A global skip such as this hides future legitimate findings:
[tool.bandit]
skips = ["B101"]Prefer a targeted line-level # nosec BXXX: reason for reviewed false
positives or accepted risks.
Baselines
Bandit supports JSON baselines:
uvx bandit --ini .bandit -f json -o bandit-baseline.json --exit-zero
uvx bandit --ini .bandit --baseline bandit-baseline.jsonUse baselines only as a temporary migration tool when existing findings are being burned down. Do not treat a baseline as the long-term dismissal record. Inline suppressions are clearer because they keep the review decision next to the code.
What to commit
Commit source fixes, targeted # nosec BXXX: reason comments, and configuration
changes when they are intentional.
Do not commit local report outputs:
.bandit-results.json.bandit-results.txt.bandit-results.md.bandit-results.html
These files are ignored by git and should stay local review artifacts.
Last updated on