Skip to content

Commit d17c9ea

Browse files
committed
📝 Add ‘Securing the release workflow’
* Git tags replaced by commit SHA values * Beautify pyproject.toml files
1 parent b48f34d commit d17c9ea

File tree

11 files changed

+172
-67
lines changed

11 files changed

+172
-67
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,29 @@ jobs:
99
pre-commit:
1010
runs-on: ubuntu-latest
1111
steps:
12-
- uses: actions/checkout@v6
13-
- uses: actions/setup-python@v6
14-
- uses: actions/cache@v5
12+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
13+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
14+
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
1515
with:
1616
path: ~/.cache/pre-commit
1717
key: pre-commit|${{ env.pythonLocation }}|${{ hashFiles('.pre-commit-config.yaml') }}
18-
- uses: pre-commit/action@v3.0.1
18+
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
1919

2020
docs:
2121
name: Build docs and check links
2222
runs-on: ubuntu-latest
2323
steps:
24-
- uses: actions/checkout@v6
25-
- uses: pandoc/actions/setup@v1
26-
- uses: actions/setup-python@v6
24+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
25+
- uses: pandoc/actions/setup@86321b6dd4675f5014c611e05088e10d4939e09e # v1.1.1
26+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
2727
with:
2828
# Keep in sync with .readthedocs.yaml
2929
python-version-file: .python-version
3030
- name: Install plantuml
3131
run: |
3232
sudo apt install plantuml
3333
- name: Setup cached uv
34-
uses: hynek/setup-cached-uv@v2
34+
uses: hynek/setup-cached-uv@4300ec2180bc77d705e626a34e381b81a4772c51 # v2.5.0
3535
- name: Create venv and install docs dependencies
3636
run: |
3737
uv venv

docs/document/sphinx/test.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,13 @@ You can then define the following jobs for GitHub, for example:
121121
runs-on: ubuntu-latest
122122
steps:
123123
- name: Download pre-built packages
124-
uses: actions/download-artifact@v4
124+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
125125
with:
126126
name: Packages
127127
path: dist
128128
- run: tar xf dist/*.tar.gz --strip-components=1
129129
130-
- uses: actions/setup-python@v5
130+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
131131
with:
132132
# Keep in sync with tox.ini/docs and .readthedocs.yaml
133133
python-version: "3.12"

docs/packs/.github/workflows/build_wheels.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ jobs:
1616
os: [ubuntu-latest, windows-latest, macos-13, macos-14]
1717

1818
steps:
19-
- uses: actions/checkout@v4
19+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2020

2121
- name: Build wheels
22-
uses: pypa/cibuildwheel@v2.21.3
22+
uses: pypa/cibuildwheel@8d2b08b68458a16aeb24b64e68a09ab1c8e82084 # v3.4.1
2323

24-
- uses: actions/upload-artifact@v4
24+
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
2525
with:
2626
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
2727
path: ./wheelhouse/*.whl

docs/packs/dataprep/pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ dependencies = [
2525
"cython",
2626
"pandas",
2727
]
28-
2928
urls."Bug Tracker" = "https://github.com/veit/dataprep/issues"
3029
urls."Homepage" = "https://github.com/veit/dataprep"
3130
License-Expression = "BSD-3-Clause"

docs/packs/myapp/pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,5 @@ classifiers = [
1616
"Programming Language :: Python :: 3.13",
1717
"Programming Language :: Python :: 3.14",
1818
]
19-
dependencies = [ ]
20-
19+
dependencies = []
2120
scripts.myapp = "myapp:main"

docs/packs/mypack/pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,5 @@ classifiers = [
1616
"Programming Language :: Python :: 3.13",
1717
"Programming Language :: Python :: 3.14",
1818
]
19-
dependencies = [ ]
20-
19+
dependencies = []
2120
scripts.mypack = "mypack:main"

docs/packs/publish.rst

Lines changed: 137 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,9 @@ Finally, you can publish your package on PyPI:
160160
GitHub Action
161161
-------------
162162

163-
You can also create a GitHub action, which creates a package and uploads it to
164-
PyPI at every time a release is created. Such a
165-
:file:`.github/workflows/pypi.yml` file could look like this:
163+
You can also create a `GitHub Action <https://github.com/features/actions>`_,
164+
which creates a package and uploads it to PyPI at every time a release is
165+
created. Such a :file:`.github/workflows/pypi.yml` file could look like this:
166166

167167
.. code-block:: yaml
168168
:caption: .github/workflows/pypi.yml
@@ -183,16 +183,16 @@ PyPI at every time a release is created. Such a
183183
needs: [test]
184184
steps:
185185
- name: Checkout
186-
uses: actions/checkout@v4
186+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
187187
with:
188188
fetch-depth: 0
189189
- name: Set up Python
190-
uses: actions/setup-python@v5
190+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
191191
with:
192192
python-version-file: .python-version
193193
cache-dependency-path: '**/pyproject.toml'
194194
- name: Setup cached uv
195-
uses: hynek/setup-cached-uv@v2
195+
uses: hynek/setup-cached-uv@4300ec2180bc77d705e626a34e381b81a4772c51 # v2.5.0
196196
- name: Create venv
197197
run: |
198198
uv venv
@@ -203,9 +203,9 @@ PyPI at every time a release is created. Such a
203203
- name: Retrieve and publish
204204
steps:
205205
- name: Retrieve release distributions
206-
uses: actions/download-artifact@v4
206+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
207207
- name: Publish package distributions to PyPI
208-
uses: pypa/gh-action-pypi-publish@release/v1
208+
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
209209
with:
210210
username: __token__
211211
password: ${{ secrets.PYPI_TOKEN }}
@@ -218,26 +218,88 @@ Line 12
218218
Line 31
219219
Here :samp:`{mypack}` should be replaced by your package name.
220220
Line 36
221-
The GitHub action ``actions/download-artifact`` provides the built
221+
The GitHub action `actions/download-artifact
222+
<https://github.com/actions/download-artifact>`_ provides the built
222223
distribution packages.
223224
Lines 38–41
224-
The GitHub action ``pypa/gh-action-pypi-publish`` publishes the packages
225+
The GitHub action `pypa/gh-action-pypi-publish
226+
<https://github.com/pypa/gh-action-pypi-publish>`_ publishes the packages
225227
with the upload token on :term:`PyPI`.
226228

227229
.. seealso::
228230

229231
* `GitHub Actions <https://docs.github.com/en/actions>`_
232+
* :doc:`cibuildwheel`
233+
234+
.. _secure-release-workflow:
235+
236+
Securing the release workflow
237+
-----------------------------
238+
239+
Continuous deployment systems used to publish Python packages are a popular
240+
target for attacks. You can avoid many of these risks by following a few
241+
security recommendations:
242+
243+
Avoid insecure triggers
244+
Workflows that can be triggered by an attacker, particularly those that rely
245+
on inputs controlled by the attacker (such as :ref:`pull request
246+
<merge-pull-requests>` or :doc:`branch
247+
<Python4DataScience:productive/git/branch>` titles), have been used in the
248+
past to inject commands. In particular, the `pull_request_target
249+
<https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_target>`_
250+
trigger in :ref:`github-actions` should be avoided.
251+
252+
.. seealso::
253+
* `Keeping your GitHub Actions and workflows secure Part 2: Untrusted
254+
input
255+
<https://securitylab.github.com/resources/github-actions-untrusted-input/>`_
256+
* `Mitigating Attack Vectors in GitHub Workflows: Workflow triggers
257+
<https://openssf.org/blog/2024/08/12/mitigating-attack-vectors-in-github-workflows/>`_
258+
* `Misuse of pull_request_target trigger
259+
<https://securitylab.github.com/resources/github-actions-new-patterns-and-mitigations/#misuse-of-pull_request_target-trigger>`_
260+
261+
Sanitise parameters and inputs
262+
Any workflow parameter or input that can be expanded into an executable
263+
command has the potential to be exploited in attacks. Sanitise values by
264+
passing them to commands as environment variables to prevent
265+
`template injection <https://docs.zizmor.sh/audits/#template-injection>`_
266+
attacks.
267+
Avoid mutable references
268+
Fix your dependencies in workflows.
269+
270+
* Prefer Git commit `SHA
271+
<https://en.wikipedia.org/wiki/Secure_Hash_Algorithms>`_ values over
272+
:doc:`Git tags <Python4DataScience:productive/git/tag>`, as tags are
273+
mutable.
274+
275+
.. tip::
276+
* Für die Aktualisierung der GitHub Actions und `Dependency Cooldowns
277+
<https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns>`_
278+
könnt ihr `pinact <https://github.com/suzuki-shunsuke/pinact>`_
279+
verwenden, also :abbr:`z. B. (zum Beispiel)`:
280+
281+
.. code-block:: console
282+
283+
$ pinact run -u --min-age 7
284+
285+
* Use a :ref:`uv_lock` file for PyPI dependencies used in workflows.
286+
287+
Use verifiable deployments
288+
With :ref:`trusted_publishers`, you can use verifiable GitHub environments
289+
to build your Python packages. If you use GitHub Actions for continuous
290+
delivery, you should use :ref:`zizmorcore` to detect and fix insecure
291+
workflows.
230292

231293
.. _trusted_publishers:
232294

233295
Trusted Publishers
234-
------------------
296+
~~~~~~~~~~~~~~~~~~
235297

236298
`Trusted Publishers <https://docs.pypi.org/trusted-publishers/>`_ is a procedure
237299
for publishing packages on the :term:`PyPI`. It is based on OpenID Connect and
238300
requires neither a password nor a token. Only the following steps are required:
239301

240-
#. Add a *Trusted Publishers* on PyPI
302+
#. Add a *Trusted Publisher* on PyPI
241303

242304
Depending on whether you want to publish a new package or update an existing
243305
one, the process is slightly different:
@@ -276,7 +338,7 @@ requires neither a password nor a token. Only the following steps are required:
276338
.. code-block:: diff
277339
:caption: .github/workflows/pypi.yml
278340
:lineno-start: 10
279-
:emphasize-lines: 3, 4-5
341+
:emphasize-lines: 3-5
280342
281343
package-and-deploy:
282344
runs-on: ubuntu-latest
@@ -292,24 +354,19 @@ requires neither a password nor a token. Only the following steps are required:
292354
Lines 13–14
293355
The ``write`` authorisation is required for *Trusted Publishing*.
294356

295-
Zeilen 42–44
357+
Zeilen 40–44
296358
``username`` and ``password`` are no longer required for the GitHub
297359
action ``pypa/gh-action-pypi-publish``.
298360

299-
.. code-block:: diff
361+
.. code-block:: yaml
300362
:lineno-start: 40
301363
:emphasize-lines: 3-
302364
303-
- name: Publish package distributions to PyPI
304-
uses: pypa/gh-action-pypi-publish@release/v1
305-
- with:
306-
- username: __token__
307-
- password: ${{ secrets.PYPI_TOKEN }}
308-
309-
.. _digital-attestations:
310-
311-
Digital Attestations
312-
--------------------
365+
- name: Publish package distributions to PyPI
366+
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
367+
with:
368+
username: __token__
369+
password: ${{ secrets.PYPI_TOKEN }}
313370
314371
Since 14 November 2024, :term:`PyPI` also supports :pep:`740` with `Digital
315372
Attestations <https://docs.pypi.org/attestations/>`_. PyPI uses the
@@ -337,7 +394,7 @@ are used for publishing:
337394
id-token: write
338395
steps:
339396
- name: Publish package distributions to PyPI
340-
uses: pypa/gh-action-pypi-publish@release/v1
397+
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
341398
342399
.. note::
343400
Support for the automatic creation of digital attestations and publishing
@@ -346,3 +403,57 @@ are used for publishing:
346403
.. seealso::
347404
`PyPI now supports digital attestations
348405
<https://blog.pypi.org/posts/2024-11-14-pypi-now-supports-digital-attestations/>`_
406+
407+
.. _zizmorcore:
408+
409+
zizmor
410+
~~~~~~
411+
412+
`zizmor <https://docs.zizmor.sh>`_ can detect and resolve many security issues
413+
in typical GitHub Actions CI/CD configurations. zizmor is designed to integrate
414+
with GitHub Actions. A typical GitHub Action we use for zizmor looks like this:
415+
416+
.. code-block:: yaml
417+
:caption: .github/workflows/zizmor.yml
418+
419+
# https://github.com/woodruffw/zizmor
420+
name: Zizmor
421+
422+
on:
423+
push:
424+
branches: ["main"]
425+
pull_request:
426+
branches: ["**"]
427+
428+
concurrency:
429+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
430+
cancel-in-progress: true
431+
432+
permissions: {}
433+
434+
jobs:
435+
zizmor:
436+
name: Run zizmor
437+
runs-on: ubuntu-latest
438+
permissions:
439+
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
440+
steps:
441+
- name: Checkout repository
442+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
443+
with:
444+
persist-credentials: false
445+
- name: Run zizmor
446+
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
447+
with:
448+
persona: pedantic
449+
450+
.. _add_2fa:
451+
452+
2FA for all development accounts
453+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
454+
455+
You should use two-factor authentication for all your accounts related to
456+
development – not just for :term:`PyPI`. Remember your version control accounts
457+
(`GitHub <https://github.com/>`_, `GitLab <https://about.gitlab.com/>`_,
458+
`Codeberg <https://codeberg.org/>`_, `Forgejo <https://forgejo.org/>`_) and
459+
email.

docs/packs/pyproject.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
test-requires = "pytest"
33
test-command = "pytest {project}/tests"
44
build-verbosity = 1
5-
65
# support Universal2 for Apple Silicon:
7-
[tool.cibuildwheel.macos]
8-
archs = [ "auto", "universal2" ]
9-
test-skip = [ "*universal2:arm64" ]
6+
macos.archs = [ "auto", "universal2" ]
7+
macos.test-skip = [ "*universal2:arm64" ]

0 commit comments

Comments
 (0)