@@ -160,9 +160,9 @@ Finally, you can publish your package on PyPI:
160160GitHub 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,86 @@ Line 12
218218Line 31
219219 Here :samp: `{ mypack } ` should be replaced by your package name.
220220Line 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.
223224Lines 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+ Securing the release workflow
235+ -----------------------------
236+
237+ Continuous deployment systems used to publish Python packages are a popular
238+ target for attacks. You can avoid many of these risks by following a few
239+ security recommendations:
240+
241+ Avoid insecure triggers
242+ Workflows that can be triggered by an attacker, particularly those that rely
243+ on inputs controlled by the attacker (such as :ref: `pull request
244+ <merge-pull-requests>` or :doc: `branch
245+ <Python4DataScience:productive/git/branch>` titles), have been used in the
246+ past to inject commands. In particular, the `pull_request_target
247+ <https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_target> `_
248+ trigger in :ref: `github-actions ` should be avoided.
249+
250+ .. seealso ::
251+ * `Keeping your GitHub Actions and workflows secure Part 2: Untrusted
252+ input
253+ <https://securitylab.github.com/resources/github-actions-untrusted-input/> `_
254+ * `Mitigating Attack Vectors in GitHub Workflows: Workflow triggers
255+ <https://openssf.org/blog/2024/08/12/mitigating-attack-vectors-in-github-workflows/> `_
256+ * `Misuse of pull_request_target trigger
257+ <https://securitylab.github.com/resources/github-actions-new-patterns-and-mitigations/#misuse-of-pull_request_target-trigger> `_
258+
259+ Sanitise parameters and inputs
260+ Any workflow parameter or input that can be expanded into an executable
261+ command has the potential to be exploited in attacks. Sanitise values by
262+ passing them to commands as environment variables to prevent
263+ `template injection <https://docs.zizmor.sh/audits/#template-injection >`_
264+ attacks.
265+ Avoid mutable references
266+ Fix your dependencies in workflows.
267+
268+ * Prefer Git commit `SHA
269+ <https://en.wikipedia.org/wiki/Secure_Hash_Algorithms> `_ values over
270+ :doc: `Git tags <Python4DataScience:productive/git/tag >`, as tags are
271+ mutable.
272+
273+ .. tip ::
274+ * Für die Aktualisierung der GitHub Actions und `Dependency Cooldowns
275+ <https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns> `_
276+ könnt ihr `pinact <https://github.com/suzuki-shunsuke/pinact >`_
277+ verwenden, also :abbr: `z. B. ( zum Beispiel ) `:
278+
279+ .. code-block :: console
280+
281+ $ pinact run -u --min-age 7
282+
283+ * Use a :ref: `uv_lock ` file for PyPI dependencies used in workflows.
284+
285+ Use verifiable deployments
286+ With :ref: `trusted_publishers `, you can use verifiable GitHub environments
287+ to build your Python packages. If you use GitHub Actions for continuous
288+ delivery, you should use :ref: `zizmorcore ` to detect and fix insecure
289+ workflows.
230290
231291.. _trusted_publishers :
232292
233293Trusted Publishers
234- ------------------
294+ ~~~~~~~~~~~~~~~~~~
235295
236296`Trusted Publishers <https://docs.pypi.org/trusted-publishers/ >`_ is a procedure
237297for publishing packages on the :term: `PyPI `. It is based on OpenID Connect and
238298requires neither a password nor a token. Only the following steps are required:
239299
240- #. Add a *Trusted Publishers * on PyPI
300+ #. Add a *Trusted Publisher * on PyPI
241301
242302 Depending on whether you want to publish a new package or update an existing
243303 one, the process is slightly different:
@@ -276,7 +336,7 @@ requires neither a password nor a token. Only the following steps are required:
276336 .. code-block :: diff
277337 :caption: .github/workflows/pypi.yml
278338 :lineno-start: 10
279- :emphasize-lines: 3, 4 -5
339+ :emphasize-lines: 3-5
280340
281341 package-and-deploy:
282342 runs-on: ubuntu-latest
@@ -292,24 +352,19 @@ requires neither a password nor a token. Only the following steps are required:
292352 Lines 13–14
293353 The ``write `` authorisation is required for *Trusted Publishing *.
294354
295- Zeilen 42 –44
355+ Zeilen 40 –44
296356 ``username `` and ``password `` are no longer required for the GitHub
297357 action ``pypa/gh-action-pypi-publish ``.
298358
299- .. code-block :: diff
359+ .. code-block :: yaml
300360 :lineno-start : 40
301361 :emphasize-lines : 3-
302362
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- --------------------
363+ - name : Publish package distributions to PyPI
364+ uses : pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
365+ with :
366+ username : __token__
367+ password : ${{ secrets.PYPI_TOKEN }}
313368
314369 Since 14 November 2024, :term: `PyPI ` also supports :pep: `740 ` with `Digital
315370Attestations <https://docs.pypi.org/attestations/> `_. PyPI uses the
@@ -337,7 +392,7 @@ are used for publishing:
337392 id-token : write
338393 steps :
339394 - name : Publish package distributions to PyPI
340- uses : pypa/gh-action-pypi-publish@release/v1
395+ uses : pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
341396
342397 .. note ::
343398 Support for the automatic creation of digital attestations and publishing
@@ -346,3 +401,57 @@ are used for publishing:
346401.. seealso ::
347402 `PyPI now supports digital attestations
348403 <https://blog.pypi.org/posts/2024-11-14-pypi-now-supports-digital-attestations/> `_
404+
405+ .. _zizmorcore :
406+
407+ zizmor
408+ ~~~~~~
409+
410+ `zizmor <https://docs.zizmor.sh >`_ can detect and resolve many security issues
411+ in typical GitHub Actions CI/CD configurations. zizmor is designed to integrate
412+ with GitHub Actions. A typical GitHub Action we use for zizmor looks like this:
413+
414+ .. code-block :: yaml
415+ :caption : .github/workflows/zizmor.yml
416+
417+ # https://github.com/woodruffw/zizmor
418+ name : Zizmor
419+
420+ on :
421+ push :
422+ branches : ["main"]
423+ pull_request :
424+ branches : ["**"]
425+
426+ concurrency :
427+ group : ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
428+ cancel-in-progress : true
429+
430+ permissions : {}
431+
432+ jobs :
433+ zizmor :
434+ name : Run zizmor
435+ runs-on : ubuntu-latest
436+ permissions :
437+ security-events : write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
438+ steps :
439+ - name : Checkout repository
440+ uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
441+ with :
442+ persist-credentials : false
443+ - name : Run zizmor
444+ uses : zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
445+ with :
446+ persona : pedantic
447+
448+ .. _add_2fa :
449+
450+ 2FA for all development accounts
451+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
452+
453+ You should use two-factor authentication for all your accounts related to
454+ development – not just for :term: `PyPI `. Remember your version control accounts
455+ (`GitHub <https://github.com/ >`_, `GitLab <https://about.gitlab.com/ >`_,
456+ `Codeberg <https://codeberg.org/ >`_, `Forgejo <https://forgejo.org/ >`_) and
457+ email.
0 commit comments