Static Analysis and Security

Updated

September 8, 2026

Static Analysis and Security

The boring shipping bar is: ruff check is clean, dependencies are audited, and secrets are not in source. A linter will not make the desk secure. It will catch the mistakes that keep recurring.

Mental model

Static analysis reads the code without running it. ruff check finds unused imports, undefined names, and a pile of foot-guns. Run it in CI the same way you run tests.

Audit means you look up known vulnerabilities in the lockfile. This book’s toolchain command is uv audit. The older standalone tool is pip-audit. Both need a lockfile or an environment worth scanning. They need the network. They are not a substitute for ruff.

Secrets are values that grant access: API tokens, passwords, private keys. They live in the environment or a secret store. They do not live in .py files, not even “just for now.”

Worked examples

Case 1: A tiny program worth checking

Save as label.py. This is the clean baseline.

# label.py
def label(ticket_id: int, table: int) -> str:
    return f"ticket {ticket_id} → table {table}"


def main() -> None:
    print(label(7, 12))


if __name__ == "__main__":
    main()

Run:

uv run python label.py

Output:

ticket 7 → table 12

Then:

uv run ruff check label.py

On a clean file ruff prints nothing and exits zero. That silence is the success signal.

Case 2: ruff catches an unused import

Save as unused_import.py. The program still runs. ruff still fails the check.

# unused_import.py
import os


def label(ticket_id: int, table: int) -> str:
    return f"ticket {ticket_id} → table {table}"


def main() -> None:
    print(label(7, 12))


if __name__ == "__main__":
    main()

Run the program:

uv run python unused_import.py

Output:

ticket 7 → table 12

Run ruff:

uv run ruff check unused_import.py

Output:

F401 [*] `os` imported but unused
 --> unused_import.py:2:8
  |
1 | # unused_import.py
2 | import os
  |        ^^
help: Remove unused import: `os`
  |
1 | # unused_import.py
  - import os
2 |
  |

Found 1 error.
[*] 1 fixable with the `--fix` option.

ruff check --fix unused_import.py removes the import. Prefer understanding the diagnostic once, then let --fix take the obvious ones.

Case 3: Secrets from the environment

Save as env_key.py. Missing key is a hard exit, not a default token in source.

# env_key.py
import os
import sys


def main() -> None:
    key = os.environ.get("DESK_API_KEY")
    if key is None:
        print("DESK_API_KEY is missing")
        sys.exit(1)
    print("key length:", len(key))


if __name__ == "__main__":
    main()

Run (unset):

uv run python env_key.py

Output:

DESK_API_KEY is missing

Run with a value:

DESK_API_KEY=abc uv run python env_key.py

Output:

key length: 3

Never print the key itself.

Case 4: Audit commands (project directory)

In a project that has pyproject.toml and a lockfile:

uv lock
uv audit

uv audit talks to a vulnerability service (OSV by default). It is allowed to need the network. If you maintain an older pipeline, pip-audit against the same lockfile is the same idea.

Do not paste audit JSON into the book as a fixture: the database changes. Wire the command into CI and fail the build on known issues you have not ignored on purpose (uv audit --ignore ... is explicit).

The trap

A literal in the file “works” and leaks the first time the repo is copied.

Save as leaked_key.py:

# leaked_key.py
API_KEY = "not-a-real-key"


def main() -> None:
    print("would send", API_KEY)


if __name__ == "__main__":
    main()

Run:

uv run python leaked_key.py

Output:

would send not-a-real-key

Git history keeps it after you delete the line. Case 3 is the fix: read DESK_API_KEY from the environment. Rotate anything that ever sat in source.

The boring rule

  • uv run ruff check (and ruff format) on every change.
  • uv lock then uv audit (or pip-audit) in CI.
  • Secrets in the environment or a vault. Never in source, tests, or example configs you commit.
  • Do not disable a ruff rule globally to silence one line; use a targeted noqa with a reason, rarely.
  • Treat “the program ran” as necessary, not sufficient, to ship.

Try this

  1. Add an unused import json to label.py and run uv run ruff check label.py.
  2. Run ruff check --fix unused_import.py and confirm the import is gone.
  3. In env_key.py, reject an empty string (key is "") the same way you reject None.