
hawkeyesec/scanner-cli is archived. GitHub last pushed it on 31 August 2021. I opened the API on 22 August 2026 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
I opened Bandit’s docs for the plugin ids. 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. I opened 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. I opened 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
I opened safety 3.8.1 on 22 August 2026. 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. I opened the 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
I fetched these URLs while compiling the table. HTTP 200 is not a recommendation to install the wrapper.
| 2021 name | 22 Aug 2026 |
|---|---|
| Bandit | Alive. 1.9.4 on 25 Feb 2026. Start here. |
| Safety | Alive as Safety CLI 3.8.1. Needs an account. |
| Hawkeye | Archived. Last push 31 Aug 2021. Do not clone. |
| Salus | Repo loads. Last push 12 Jun 2025. Prefer Bandit direct. |
| Hubble | Repo loads. Last push 7 Jul 2023. Host compliance, not SAST. |
| GuardRails.io | Site loads. Hosted AppSec, not a local pip tool. |
| secure / Secure.py | Alive 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
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.
- In the app repo, run
runSast. A B301, B602, or B307 hit is a review. A clean run is not an advisory check. - Run
auditLockon the file the image installs. A known advisory is a fail. An unauditable Git pin is a fail under--strict. - Run
ruffS. Expect the same pickle and eval files Bandit named, or a written ignore with a ticket. - Confirm CI does not invoke
hawkeye,hawkeye-scan, or acoinbase/salusimage 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.



