Skip to content

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:

app.py
import hashlib

password = "supersecret123"
digest = hashlib.md5(b"data").hexdigest()


def get_user(user_id):
    query = "SELECT * FROM users WHERE id = " + user_id
    return query
uv 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:

app.py
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: S101

For 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.

Last updated on