Skip to content

Enable ruff security, logging and debugging rules (S, T20, LOG, G, DTZ, ERA, TD, FIX, EXE) - #785

Merged
anth-volk merged 16 commits into
mainfrom
ruff-security-logging
Oct 16, 2026
Merged

anth-volk merged 16 commits into
mainfrom
ruff-security-logging

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #784

Same-Repository Draft PR Check

  • This PR is a draft.
  • This PR is from a branch in PolicyEngine/policyengine-sim-api, not a personal fork.

Summary

Phase 1 step 1.8, group 6: security, logging and debugging leftovers. Bottom of the stack since #783 merged.

  • Rules: ruff.toml gains S, T20, LOG, G, DTZ, ERA, TD, FIX and EXE. Every finding is fixed (one commit per family), covered by a file-level exception in tests, CI/CD scripts or command-line scripts, or kept inline with a reason in application code. ERA, TD, FIX and EXE had no findings.
  • File-level exceptions (new policy): a ruff per-file-ignores entry is now allowed only for tests (tests or authenticated_tests directories), CI/CD scripts (.github) and command-line scripts (a scripts directory, or a module run through if __name__ == "__main__":).
    • Enforcement: the rules guard already required a registered rule for every entry. Now it also expands each entry's pattern the way ruff does and fails when the pattern covers any other file, is negated, or matches nothing. Ruff's * also matches /, and a pattern also matches a bare file name; the check follows both.

    • Where it lives: scripts/rules/file_exceptions.py. docs/engineering/skills/rules-registry.md has a new "File-level exceptions" section.

    • Codes, from the suppressions those files already carried:

      Files Codes added
      Tests, and the deployed-service tests S102, S105, S106, S108, S301, S603, S607, DTZ001, DTZ007 (added to their existing exemptions)
      .github/** T201
      **/scripts/** DTZ011, S603, S607, T201
      Programs under src: the Modal deployment utilities, and the entry and gateway OpenAPI and Stage 12 infrastructure commands S310, T201
    • Effect: this removes 75 inline codes from 44 files. Each of the five branches up to Register weighted-microdata rules, weight the builder tests, and check change weights and household counts #807 differs from its previous version in project Python only by noqa comments; a script checked each file line by line.

    • Registry: three new rules: sim.quality.ci-cd-scripts-ruff-exemptions, sim.quality.command-line-scripts-ruff-exemptions and sim.quality.src-programs-ruff-exemptions.

  • Behaviour changes (all deliberate):
    • Gateway auth misconfiguration log (G201): it now uses logger.exception("Gateway auth misconfigured"). Before, it logged "Gateway auth misconfigured: Gateway auth misconfigured: set GATEWAY_AUTH_ISSUER …", repeating the message; the traceback still ends with the full RuntimeError message. The 503 response is unchanged.
    • Logger name (LOG002): the executor's simulation.py uses getLogger(__name__) instead of getLogger(__file__).
    • Gateway image smoke: its local entrypoint raises RuntimeError with a message instead of failing a bare assert. The smoke script and the integration test only check the exit code.
    • Six f-string log calls become %s arguments with identical text (G004).
  • Asserts outside tests (S101):
    • Contract spm.py (4): the asserts couldn't fail, because _require_complete_selection already raises ValueError when either value is missing. It now returns them, so nothing new is raised and Pydantic validators still see ValueError.
    • Executor stage12_qualification.py: the closure captures a non-optional runtime instead of asserting it.
    • Test double in fixtures/: raises AssertionError explicitly, as the repo's other fakes do.
    • authenticated_tests: this is the deployed-service pytest suite (run by simulation-deploy.reusable.yml), which the **/tests/** glob didn't match. It gets the same exemptions as **/tests/**, registered as sim.quality.deployed-tests-ruff-exemptions.
    • Nothing in the repo runs Python with -O.
  • Prints in commands (T201): command-line scripts and Modal local entrypoints keep their output. Scripts, .github and the programs under src are covered by file-level exceptions. The Modal local entrypoints (smoke_app.py, v2_static_runtime_files_smoke.py) and the executor's import-time generate_openapi.py don't run through __main__, so they keep an inline reason. update_version_registry.py (22 prints) and .github/scripts/update_policyengine_package.py route output through one helper. No application code had a debug print.
  • Subprocess calls (S603, S607): each test module that runs repository scripts does it through one helper, for example _run_bash in test_modal_scripts.py (32 calls). Tests and scripts are covered by file-level exceptions; the one call in application code keeps an inline reason. Executables still come from the child's PATH, since several tests pass their own env.
  • Datetimes (DTZ): every value stays naive, matching .py's ParameterValue dates and its own period parsing. The 11 naive test dates go through one _naive_policy_date helper.
  • Other security findings: no vulnerability found.
    • Fixed: the GCP metadata probe builds its Request inline, with identical URL, headers and method.
    • Covered by the test exceptions: fake tokens (now module constants), image tests that exec repository-owned app source, and tests that pickle objects they built.
    • Kept inline with a reason, in application code:
      • Modal secret and display names;
      • the Modal container's fixed /tmp/policyengine-data paths;
      • the Hugging Face Hub URL built from HF_ENDPOINT and quoted path parts.
    • S110: each try/except/pass already carried a reasoned BLE001 suppression from group 5, which now lists S110 too.
  • Modal images: code changes in the executor image's simulation.py, simulation_runtime.py, stage12_qualification.py and update_version_registry.py, and in the shared contract, observability and fastapi libraries. src/modal/app.py changes only in comments.
  • Registry: sim.quality.ruff-lint now lists the nine families. The rules tool is synced from the Public API's group 6 (Enable ruff security, logging and debugging rules (S, T20, LOG, G, DTZ, ERA, TD, FIX, EXE) policyengine-api#3915), and both copies gain the same file-level exception check (file_exceptions.py is identical in both).

Testing

🤖 Generated with Claude Code

@anth-volk
anth-volk added this pull request to stack #755 October 13, 2026 17:53
@anth-volk
anth-volk force-pushed the ruff-security-logging branch 3 times, most recently from 403cbc2 to ab3ad4d Compare October 14, 2026 17:29
@anth-volk
anth-volk force-pushed the ruff-security-logging branch 3 times, most recently from d0d50d7 to 76c2cde Compare October 14, 2026 20:04
@anth-volk
anth-volk force-pushed the ruff-security-logging branch 6 times, most recently from a388172 to 94de8a7 Compare October 15, 2026 16:06
@anth-volk
anth-volk force-pushed the ruff-security-logging branch 4 times, most recently from f92c311 to ba82c0b Compare October 16, 2026 05:01
Base automatically changed from ruff-exceptions to main October 16, 2026 05:12
anth-volk and others added 6 commits October 16, 2026 09:12
Pass logging arguments instead of f-strings (G004); each message renders
to the same text. Name the economy-comparison router's logger after its
module rather than its file path (LOG002). Log the gateway's auth
misconfiguration with logger.exception and a fixed message (G201): the
traceback already ends with the exception and its message, which the old
call also repeated in the message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine.py parses policy period keys with strptime and no zone, so
its ParameterValue dates are naive, and the executor's period parsing and
the tests that compare against those dates must stay naive too. Mark each
kept naive datetime with a reasoned DTZ suppression, and route the policy
tests' expected dates through one helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deployed-service test suite in authenticated_tests is a pytest suite
that the tests glob misses, so give it the same test exemptions.

Elsewhere, drop the asserts that only narrowed types: the SPM selection
check now returns the hash and scenario it has already proved present,
and the qualification runner closes over a non-optional runtime. The
gateway image smoke raises RuntimeError when its report is incomplete, as
the executor's static-file smoke does, and the fake model output raises
AssertionError explicitly when the adapter asks for a mode other than
JSON.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every print the rule finds belongs to a command-line script or a Modal
local entrypoint, whose stdout is its output, so keep each print. Files
with one to three prints mark each with a reasoned suppression; the
routing-state publisher and the policyengine package updater send their
output through one small helper that holds the only print.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each test module that runs repository scripts now does so through one
helper that holds the only subprocess call, with reasoned suppressions:
the scripts are repository-owned, their arguments are built by the tests,
and bash and uv come from PATH as they do in the workflows. Resolving the
executables in Python instead could pick a different binary, because
subprocess searches the child's PATH, and some tests run scripts with an
empty environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Build the GCP metadata request inline from its constant http URL, with
the same Metadata-Flavor header, so S310 can see the scheme. Keep every
other finding with a reasoned suppression:

- S105/S106: fake tokens in tests (one module constant per test module
  where a value repeats), and names or fingerprints that only look like
  secrets.
- S108: the executor's fixed data directories inside the Modal container,
  and a lookup path the tests' fakes only pass along.
- S110: add S110 to the existing reasoned BLE001 suppressions.
- S102: the image tests execute repository-owned app source as Modal's
  /root/app.py.
- S301: the SPM tests round-trip errors they built or raised.
- S310: the Hub API request URL is built from the HF_ENDPOINT base and
  quoted path parts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 8 commits October 16, 2026 09:12
Select DTZ, ERA, EXE, FIX, G, LOG, S, T20 and TD. The earlier commits on
this branch fixed or suppressed, with a reason, every finding outside the
shared scripts/rules tool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sim.quality.ruff-lint now lists DTZ, ERA, EXE, FIX, G, LOG, S, T20 and
TD. The authenticated_tests exemption gets its own rule, since a ruff
per-file-ignore rule names one pattern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared scripts/rules tool takes the Public API's group 6 fixes:
explicit checks instead of asserts, and reasoned suppressions for its
git and shellcheck subprocess calls, prints and calendar dates. The
rendered rules page picks up the tool's new hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#743 and #744 landed on main after this stack branched. Group 6, which
this PR enables, flags their new code; each fix follows this PR's
conventions:

- T201: the routing publisher's new commands report through its _report
  helper, the package updater's dispatch check through _write_result, and
  the sentinel CLI gets one _report helper that holds its only print.
- S603/S607: the deployment-asset tests route #744's new bash calls
  through _run_bash, which now takes cwd; the rollback tests and the
  package-update helper tests get one helper each that holds the only
  subprocess call, with reasoned suppressions.
- S310: the sentinel's HTTP helper opens the deployed entrypoint or the
  test auth issuer URL that the workflow passes; reasoned suppressions.
- S106: a fake bearer token in the sentinel tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#795 landed on main after this stack branched. S110, which this PR
enables, flags the try-except-pass in its _cancel_call, which now holds
the best-effort cancel loop this PR suppressed; like the other reasoned
BLE001 suppressions, it gains S110.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#791 landed on main after this stack branched. S105, which this PR
enables, flags two values that only look like secrets, and each keeps a
reasoned suppression: the redaction pattern for secret-looking key names
in the Stage 12 failure-cause module, and the fake database password its
tests check redaction removes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#758 landed on main after this stack branched. S603, which this PR
enables, flags the two new deployment tests that run the Cloud Run deploy
script directly to check permission enforcement defaults and rejections.
They carry the same reasoned suppression as the existing direct run of
that script.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#781 landed on main after this stack branched. Group 6, which this PR
enables, flags two of its additions:

- S110: the Modal app's stage-duration telemetry swallows a failed
  set_context, so its suppression adds S110 beside BLE001, as the call
  attributes helper above it does;
- DTZ001: the contract test that the job record rejects a naive time
  builds that naive time on purpose, so it carries a reasoned
  suppression.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk force-pushed the ruff-security-logging branch from ba82c0b to fa3df58 Compare October 16, 2026 05:12
anth-volk and others added 2 commits October 16, 2026 09:54
…e scripts

A ruff per-file-ignores entry switches rules off for whole files. That is
now acceptable where it's the norm: tests, CI/CD scripts under .github,
and command-line scripts (under a scripts directory, or modules run
through if __name__ == "__main__"). Everywhere else a kept finding still
needs an inline suppression with its reason.

The rules guard already requires a per-file-ignore rule for every entry.
It now also expands each entry's pattern the way ruff does (globset
syntax, and a slash-less pattern also matches file names) and fails when
the pattern covers any other file, is negated, or matches nothing.
scripts/rules/file_exceptions.py holds the definitions, and the registry
doc describes the policy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests, CI/CD scripts and command-line scripts now take their deliberate
group 6 exceptions as registered per-file-ignores, each code chosen from
the suppressions those files already carried:

- tests and the deployed-service tests: S102, S105, S106, S108, S301,
  S603, S607, DTZ001 and DTZ007, added to their existing exemptions;
- .github: T201;
- scripts directories: DTZ011, S603, S607 and T201;
- programs under src (the Modal deployment utilities, and the OpenAPI
  and Stage 12 infrastructure commands): S310 and T201.

That removes 75 inline codes from 44 files. Application code keeps its
inline suppressions and reasons. Comments only: no code changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk marked this pull request as ready for review October 16, 2026 06:05
@anth-volk
anth-volk requested a review from a team as a code owner October 16, 2026 06:05
@anth-volk
anth-volk merged commit 60559fc into main Oct 16, 2026
14 checks passed
@anth-volk
anth-volk deleted the ruff-security-logging branch October 16, 2026 06:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable ruff security, logging and debugging rules (S, T20, LOG, G, DTZ, ERA, TD, FIX, EXE)

1 participant