How to Enable Ruff Security Rules
Ruff’s S rule group implements flake8-bandit security checks, catching hardcoded passwords, injection vulnerabilities, insecure hashing, and unsafe deserialization before they reach production.
Enable the Full Set
Add the S group to the project’s pyproject.toml:
[tool.ruff.lint]
extend-select = [
"S", # flake8-bandit security rules
]This enables every stable flake8-bandit rule. The 13 S4xx suspicious-import rules (S401 through S415, which flag import telnetlib, import pickle, import subprocess, and similar) are preview-only and need preview = true in the same table.
Run the linter against a file with a hardcoded secret, an MD5 call, and a string-built SQL query:
import hashlib
password = "supersecret123"
digest = hashlib.md5(b"data").hexdigest()
def get_user(user_id):
query = "SELECT * FROM users WHERE id = " + user_id
return queryuv run ruff check --output-format concise .app.py:3:12: S105 Possible hardcoded password assigned to: "password"
app.py:4:10: S324 Probable use of insecure hash functions in `hashlib`: `md5`
app.py:8:13: S608 Possible SQL injection vector through string-based query construction
Found 3 errors.Without --output-format concise, Ruff prints the same three findings with a source snippet under each.
Fix What the Linter Finds
Load the secret from an environment variable (a .env file keeps it out of the repository), switch to SHA-256, and parameterize the query:
import hashlib
import os
password = os.environ["APP_PASSWORD"]
digest = hashlib.sha256(b"data").hexdigest()
def get_user(cursor, user_id):
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
return cursor.fetchone()Rerun uv run ruff check . and it prints All checks passed!.
Pick Specific Rules
For a more targeted approach, enable individual rules instead of the full group:
[tool.ruff.lint]
extend-select = [
"S105", # Hardcoded passwords in strings
"S106", # Hardcoded passwords in function arguments
"S107", # Hardcoded passwords in function defaults
"S108", # Insecure temp file usage
"S113", # HTTP requests without timeout
"S301", # Pickle deserialization
"S324", # Insecure hash functions
"S608", # SQL injection via string formatting
]These rules can be combined with the recommended Ruff defaults by adding them to the same extend-select list.
| Rule | Category | What it catches |
|---|---|---|
| S105, S106, S107 | Secrets | Hardcoded passwords in assignments, arguments, and defaults |
| S108 | File safety | Insecure use of temp files |
| S113 | Network | HTTP requests missing a timeout parameter |
| S301 | Deserialization | Use of pickle.loads and related functions |
| S324 | Cryptography | Insecure hash algorithms like MD5 and SHA1 |
| S608 | Injection | SQL queries built with string concatenation or formatting |
Tune the Rules
Two options under [tool.ruff.lint.flake8-bandit] change what the rules flag:
[tool.ruff.lint.flake8-bandit]
hardcoded-tmp-directory-extend = ["/scratch"] # S108 flags /scratch as well as /tmp, /var/tmp, /dev/shm
check-typed-exception = true # S110 and S112 also flag `except ValueError: pass`By default S110 and S112 only report except Exception: pass and except BaseException: pass; typed handlers pass silently.
Suppress False Positives
Some S rules flag code that is safe in context. S101 reports every assert, so with the full group enabled a test line needs a # noqa comment:
assert response.status_code == 200 # noqa: S101For broader suppression, use per-file ignores in pyproject.toml. This is common for test files, where assert statements (S101) and hardcoded test credentials (S105, S106) are expected:
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101", "S105", "S106"]To silence a single finding, see how to suppress a Ruff warning on one line; for a region of a file, see how to disable Ruff rules for a block of code.
Compare Ruff With bandit
Ruff’s S rules and bandit both inspect one file’s syntax tree at a time with no dataflow analysis, so neither traces a tainted value across function calls. bandit attaches a severity and a confidence level to each finding and reads # nosec comments; Ruff reports every finding at one level and reads # noqa.
Both tools check first-party code only. For vulnerabilities in dependencies, scan them with pip-audit or uv audit and harden installs against supply-chain attacks.