chore(ci): Harden the supply chain — harden-runner, Dependabot, pinned dependencies - #80
Merged
Merged
Conversation
From https://docs.stepsecurity.io/harden-runner use harden-runner to test release process security
Check pip and github-actions dependencies every Monday, with all updates of an ecosystem grouped in a single PR. Commit prefixes follow the Angular convention so python-semantic-release keeps parsing the history. Security updates (immediate PRs on a CVE) are enabled in the repository settings, independently of this file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add requirements.lock.txt and requirements-dev.lock.txt: the full closure (direct + transitive) of the existing requirements files, pinned to the versions currently used for development. The unpinned requirements*.txt stay the declaration files and keep driving CI, which still covers Python 3.9 while the dev tooling now requires 3.10. The locks give GitHub's dependency graph exact versions and the transitive dependencies it cannot resolve on its own, which is what Dependabot needs to raise actionable security alerts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Some of the click APIs the CLI relies on are deprecated in click 9, so constrain the requirement to >=8,<9. setup.cfg reads install_requires from requirements.txt, so the constraint also ships in the package metadata. Tell Dependabot to skip major updates for click, otherwise it would keep proposing to widen the constraint once 9.0 is out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every dependency with a known advisory now has a lower bound in
requirements.txt, which setup.cfg feeds into install_requires: these floors
are the only thing that reaches users installing from PyPI.
urllib3>=2.7.0 6 advisories, 4 of them high
requests>=2.33.0 2 advisories
cryptography>=50.0.0 6 advisories, 4 of them high; not imported directly
but paramiko's own floor allows vulnerable versions
black>=26.3.1 1 high advisory
The fixed releases of urllib3, requests and black all require Python 3.10,
which forces the drop of 3.9. It has been end-of-life since October 2025.
requirements-dev.lock.txt is removed: dev tooling is lower risk and the file
was the one creating friction with the test matrix. requirements.lock.txt
stays, refreshed from the upgraded environment, as the only way GitHub's
dependency graph can see transitive dependencies.
No advisory remains on any pinned runtime or dev version.
BREAKING CHANGE: Python 3.9 is no longer supported, the minimum is 3.10.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lcaflc
force-pushed
the
chore/harden-ci
branch
from
September 17, 2026 13:48
f422ca1 to
2efb831
Compare
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
| plumber: | ||
| runs-on: ubuntu-24.04 | ||
| steps: | ||
| - uses: actions/checkout@v6 |
| with: | ||
| egress-policy: audit | ||
|
|
||
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 |
| with: | ||
| egress-policy: audit | ||
|
|
||
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 |
| with: | ||
| egress-policy: audit | ||
|
|
||
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 |
| with: | ||
| egress-policy: audit | ||
|
|
||
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 |
lcaflc
force-pushed
the
chore/harden-ci
branch
from
September 17, 2026 14:41
2b9b9ec to
fd74087
Compare
ci: Add python-semantic-release to plumber whitelist fix plumber e
lcaflc
force-pushed
the
chore/harden-ci
branch
from
September 17, 2026 14:44
fd74087 to
fc89745
Compare
lcaflc
marked this pull request as ready for review
September 17, 2026 14:45
lperdereau
approved these changes
Sep 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Started as harden-runner on the workflows (step-security docs), to audit outbound calls during the release process. Grew into the rest of the supply-chain hardening.
What is in here
chore(ci)auditci(deps)pipandgithub-actions, grouped into one PR per ecosystem. Commit prefixes follow the Angular convention so python-semantic-release keeps parsing the historybuild(deps)requirements.lock.txt: the resolved runtime closure, pinnedbuild(deps)click>=8,<9: the CLI relies onclick.MultiCommand, removed in click 9 — see #89build!Why the lock file
Dependabot's security updates rely on the dependency graph, and the graph could not see anything useful: unpinned requirements are recorded without a version, and GitHub does not resolve transitive pip dependencies without a lock. The repository showed 17 packages, none with a version, and no transitive dependency at all — so no alert could ever fire, including on
cryptography, which is where four high-severity advisories were sitting.The lock is the detection layer. It protects nobody downstream: it is not in the wheel, and pip never reads it. That is what the floors are for.
Python 3.9 is no longer supported, the minimum is 3.10. The fixed releases of
urllib3(2.7.0),requests(2.33.0) andblack(26.3.1) all require 3.10, so the floors below and 3.9 were mutually exclusive. Python 3.9 has been end-of-life since October 2025.setup.cfgand the CI matrix follow.Floors added to
requirements.txt, whichsetup.cfgfeeds intoinstall_requires— these are the only constraints that reach users installing from PyPI:urllib3>=2.7.0— 6 advisories, 4 highrequests>=2.33.0— 2 advisoriescryptography>=50.0.0— 6 advisories, 4 high; not imported directly, but paramiko's own>=3.3allows vulnerable versionsblack>=26.3.1— 1 high advisoryVerification
pytest: 116 passedblack --check: cleanpylint src/: 10.00/10Requires-Python: >=3.10and the floors are present inRequires-DistFollow-up
click.MultiCommandso the<9pin can be liftedrequirements*.txt; now that 3.9 is gone, it could install from the lock instead for reproducible builds🤖 Generated with Claude Code