Get listed

Python security tools: Bandit, pip-audit, and Ruff S

Three teal tools on a bench with one dusty coral bottle.

A Python security tool is useful if it is maintained and it runs where you merge code.

Archived scanners still appear in lists. Bandit, pip-audit, and the current linters are the ones that still move a PR. A GitHub repo last pushed in 2021 is a bookmark, not a control.

The usual mistake is installing a dead CLI because a 2020 blog named it.

This page is the 2026 shortlist and the names to stop recommending.

hawkeyesec/scanner-cli is archived. GitHub last pushed it on 31 August 2021. As of 22 August 2026, API and archived is true. Bandit 1.9.4 published on 25 February 2026. pip-audit 2.10.1 published on 10 June 2026. Ruff 0.16.4 published on 20 August 2026.

A screenshot of a dashboard is not a control. The control is an AST pass you can run offline, an advisory check with a PyPA maintainer, and a lint rule that fires before the commit. Keep the secure coding checklist next to this shelf. Read Flask locks when the app is Pallets. Read injection when the string is SQL or a shell. Read the lockfile versus install-time malware split when the row is a surprise wheel.

The 2021 shelf aged in public

Seven names from that page still resolve in a browser today. One does not, as a living scanner. Hawkeye’s own README said it assumed package files sit at the repo root and that it wrapped Bandit, piprot, and Safety. piprot is not a 2026 control. I am not treating an archived Docker image as a shelf.

GuardRails on that list was the hosted GitHub App at guardrails.io, not the later Guardrails AI package.https://www.guardrails.io/ still answers. It is a commercial PR commenter. It is not a pip install you run on your laptop without an account. Do not confuse it with guardrails-ai on PyPI, which is an LLM validator and is a different product.

A package that still loads is not automatically a 2026 control. Hubble’s last code push I saw was 7 July 2023. Salus last pushed on 12 June 2025 and is not archived. Safety 3.8.1 published on 29 May 2026. secure 2.0.1 published on 22 April 2026. Those sit in the survivors table. They do not replace Bandit or pip-audit.

Bandit still walks the AST

Resource one is PyCQA/bandit. 1.9.4 requires Python 3.10 or newer. The README still says it processes each file, builds an AST, and runs plugins against the nodes. That is the same shape. The release date is not.

Bandit finds pickle.loads, subprocess with shell=True, eval, hardcoded passwords, and weak hashes in the files you point at. It does not read a lockfile. It does not match PYSEC ids. That second job is pip-audit. Run both.

# in the app repo you maintain. identifiers stay runSast
python -m pip install "bandit==1.9.4"
runSast() { bandit -r src -ll -f txt; }
runSast
# Expect: a clean run, or a B301 / B602 / B307 finding

B301 is pickle. B602 is shell=True. B307 is eval. Those three are the greps the language page already teaches as hatches. Bandit is how CI fails when someone adds one back. A # nosec comment is a written exception. Require a ticket id next to it, or the next weekly run will treat silence as a pass.

# pyproject.toml fragment this page will prove
[tool.bandit]
exclude_dirs = ["tests", "venv"]
skips = []

Leave skips empty until you have a named false positive. A skipped B301 on a cache loader that still calls pickle.loads on a byte string from disk is how the hatch survives the scanner.

pip-audit is the PyPA lockfile check

Resource two is pip-audit at 2.10.1. that page and the GitHub README. It audits an environment or a requirements-style file against the Python Packaging Advisory Database via the PyPI JSON API. It can --fix. The README is explicit: it reports known advisories. It does not defend you against a malicious wheel that has no advisory yet.

auditLock is the named helper. Point it at the file you actually install in the image, not at a loose requirements.in that never resolved. A Git URL or an editable path may not match an advisory. Pin those to a published version when you can.

# identifiers stay auditLock
python -m pip install "pip-audit==2.10.1"
auditLock() { pip-audit -r requirements.lock --strict; }
auditLock
# Expect: no PYSEC row, or an id you then bump

The --strict flag fails when the tool cannot audit a row, not only when it finds an advisory. That is the conservative CI default. A backport your distro already patched can still flag on the PyPI version number. Write the changelog line and close as not-affected the way the vuln playbook page does. Do not delete the row.

Ruff S is the fast Bandit subset

Resource three is Ruff’s flake8-bandit rules. the S rule table while checking the 0.16.4 notes. 0.16.4 had shipped two days earlier. S301 is suspicious pickle. S307 is eval. S608 is a hardcoded SQL expression. S113 is a request without a timeout. Those rules have been stable since the 0.0.2xx line. They are not a full Bandit plugin set. They are the pass you can run as you type.

# pyproject.toml. identifiers stay ruffS
[tool.ruff.lint]
select = ["S"]
ignore = []

# CI. ruffS is the named pass
ruffS() { ruff check --select S src; }
ruffS
# Expect: no S rule, or a ticket on the ignore

Use Ruff S in the editor. Use Bandit in CI on the same tree. The overlap is the point: the editor catches the hatch before the pull request, Bandit still runs the plugins Ruff has not copied. I did not find a first-party Ruff page that claims it replaced Bandit’s full plugin list, so this page does not claim that either.

Ruff is also a formatter and a general linter. Those jobs are hygiene. This page only keeps the S group. A clean format run is not an advisory check. Do not drop auditLock because ruff check is green.

Safety 3 still exists. It wants an account

As of 22 August 2026, safety 3.8.1. The README now starts with pip install safety, then safety scan, then a login or register prompt. It still ships under the MIT license. The commercial copy says a new account gets a seven-day Team trial and then a Free plan limited to one user, and that the Free plan is not recommended for commercial use.

Safety is not dead. It changed shape. A tutorial that still says safety check against a local Safety DB with no login is describing Safety 2. project’s own “Breaking Changes in Safety 3” note from their docs hub. Use their current docs if you keep the product. Do not paste a 2021 invoke into a 2026 workflow and call it FOSS parity with pip-audit.

What from 2021 still loads

As of 22 August 2026, these project URLs. HTTP 200 is not a recommendation to install the wrapper.

2021 name22 Aug 2026
BanditAlive. 1.9.4 on 25 Feb 2026. Start here.
SafetyAlive as Safety CLI 3.8.1. Needs an account.
HawkeyeArchived. Last push 31 Aug 2021. Do not clone.
SalusRepo loads. Last push 12 Jun 2025. Prefer Bandit direct.
HubbleRepo loads. Last push 7 Jul 2023. Host compliance, not SAST.
GuardRails.ioSite loads. Hosted AppSec, not a local pip tool.
secure / Secure.pyAlive as secure 2.0.1 on 22 Apr 2026. Header pack.

Salus is a Docker coordinator. Coinbase’s README still says it picks scanners and compiles a report. That is useful if you already run it across many repos. It is optional if you can call runSast and auditLock yourself. I am not treating the June 2025 push as a reason to add a second container. Hubble is a host compliance agent that can ship events to Splunk or Logstash. It is not a replacement for Bandit on src/.

The April secure release is TypeError’s header helper. Secure.with_default_headers() sets a balanced pack on Flask or FastAPI. It is the Python cousin of Helmet. It does not walk an AST and it does not read a lockfile. If you already set the headers in middleware you own, you do not need the package. If you want one object, it still installs.

from secure import Secure

secure_headers = Secure.with_default_headers()

def after_request(response):
 secure_headers.set_headers(response)
 return response
Three living commands. Hawkeye is off the shelf.
SOURCE bandit -r src AST plugins
 ruffS editor S rules

LOCK pip-audit -r lock --strict
 known PYSEC / GHSA only

HEADERS TypeError secure optional
 Flask / FastAPI middleware

DEAD hawkeyesec/scanner-cli 31 Aug 2021
PAID safety scan account first

Prove the three commands

You are not scanning a foreign host. You are proving your own tree and your own lockfile.

  1. In the app repo, run runSast. A B301, B602, or B307 hit is a review. A clean run is not an advisory check.
  2. Run auditLock on the file the image installs. A known advisory is a fail. An unauditable Git pin is a fail under --strict.
  3. Run ruffS. Expect the same pickle and eval files Bandit named, or a written ignore with a ticket.
  4. Confirm CI does not invoke hawkeye, hawkeye-scan, or a coinbase/salus image you never pinned.
#.github/workflows/python-shelf.yml
name: python-shelf
on: [push, pull_request]
jobs:
 shelf:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v4
 - uses: actions/setup-python@v5
 with:
 python-version: "3.13"
 - run: pip install bandit pip-audit ruff
 - run: bandit -r src -ll
 - run: pip-audit -r requirements.lock --strict
 - run: ruff check --select S src

The workflow lines above are the one place the raw commands stay, so a reviewer can paste them. Elsewhere this page calls runSast, auditLock, and ruffS.

Grep the hatches this page names as greps:

rg -n "pickle\\.loads|subprocess\\.[A-Za-z]+\\(.*shell\\s*=\\s*True|hawkeye|safety check\\b" \
 --glob '!venv'

A hit on pickle.loads, on shell=True, on the archived scanner, or on the Safety 2 invoke is a review. A hit on runSast, auditLock, and ruffS is the path you want. The language hatches, including Jinja autoescape, live next to pickle on the Flask page. This page is the shelf that still has a maintainer.

Questions we keep getting

Is Ruff S enough if CI already runs Bandit?

Keep both. Ruff is the editor. Bandit is the plugin set. I could not find a first-party claim that the S group equals every Bandit test. A green ruff check is not auditLock.

Should I still install Safety?

Only if you already have an account and a policy you can name. The 2026 FOSS default on this page is the June pip-audit release. Safety CLI 3 still installs. It starts with a login.

Did Hubble and Salus die?

No. Hubble’s last push I saw was 7 July 2023. Salus pushed on 12 June 2025 and is not archived. Neither replaces Bandit plus pip-audit on a single Python app. Skip them until you have a fleet reason.

Guy Bar-Gil

Guy Bar-Gil / About Author

Guy is a product manager at WhiteSource, where we enable software development teams to integrate open source fearlessly and without compromising agility. Before WhiteSource, Guy worked for the IDF's intelligence division, where he spent time as a combat operator and project manager. Outside of work, you can find Guy reading (everything from fiction to physics), playing and watching sports, traveling the world, and spending time with friends and family. LinkedIn