Skip to content

Commit ddcda87

Browse files
committed
ci: harden GitHub Actions workflows and add zizmor audit job
SHA-pin all actions, scope permissions, move ref/repo into env vars, and run zizmor on every push so workflow regressions get caught in CI.
1 parent af0f1fc commit ddcda87

4 files changed

Lines changed: 70 additions & 23 deletions

File tree

.github/dependabot.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ updates:
44
directory: "/"
55
schedule:
66
interval: "weekly"
7+
cooldown:
8+
default-days: 7
79

810
- package-ecosystem: "pip"
911
directory: "/"
1012
schedule:
1113
interval: "weekly"
14+
cooldown:
15+
default-days: 7
1216
groups:
1317
python-deps:
1418
patterns:

.github/workflows/release.yml

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI
22

33
on: push
44

5+
concurrency:
6+
group: ${{ github.workflow }}-${{ github.ref }}
7+
cancel-in-progress: true
8+
9+
permissions:
10+
contents: read
11+
512
env:
613
# Change these for your project's URLs
714
PYPI_URL: https://pypi.org/p/django-tailwind-cli
@@ -14,9 +21,11 @@ jobs:
1421
runs-on: ubuntu-latest
1522

1623
steps:
17-
- uses: actions/checkout@v6
24+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
25+
with:
26+
persist-credentials: false
1827
- name: Set up Python
19-
uses: actions/setup-python@v6
28+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
2029
with:
2130
python-version: "3.x"
2231
- name: Install pypa/build
@@ -25,20 +34,21 @@ jobs:
2534
- name: Build a binary wheel and a source tarball
2635
run: python3 -m build
2736
- name: Store the distribution packages
28-
uses: actions/upload-artifact@v7
37+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
2938
with:
3039
name: python-package-distributions
3140
path: dist/
3241
- name: Extract changelog for tagged release
3342
if: startsWith(github.ref, 'refs/tags/')
43+
env:
44+
REF_NAME: ${{ github.ref_name }}
3445
run: |
35-
VERSION="${{ github.ref_name }}"
36-
VERSION="${VERSION#v}"
37-
awk "
38-
/^## ${VERSION}/ { found=1; next }
46+
VERSION="${REF_NAME#v}"
47+
awk -v ver="$VERSION" '
48+
$0 ~ "^## " ver { found=1; next }
3949
found && /^## / { exit }
4050
found { print }
41-
" CHANGELOG.md > release-notes.md
51+
' CHANGELOG.md > release-notes.md
4252
if [[ ! -s release-notes.md ]]; then
4353
echo "::error::No changelog section found for version ${VERSION}."
4454
echo "::error::Did you forget to rename '## Unreleased' in CHANGELOG.md before tagging?"
@@ -48,7 +58,7 @@ jobs:
4858
cat release-notes.md
4959
- name: Upload release notes
5060
if: startsWith(github.ref, 'refs/tags/')
51-
uses: actions/upload-artifact@v7
61+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
5262
with:
5363
name: release-notes
5464
path: release-notes.md
@@ -67,12 +77,12 @@ jobs:
6777
id-token: write # IMPORTANT: mandatory for trusted publishing
6878
steps:
6979
- name: Download all the dists
70-
uses: actions/download-artifact@v8
80+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
7181
with:
7282
name: python-package-distributions
7383
path: dist/
7484
- name: Publish distribution 📦 to PyPI
75-
uses: pypa/gh-action-pypi-publish@release/v1.14
85+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1.14
7686

7787
github-release:
7888
name: >-
@@ -88,38 +98,42 @@ jobs:
8898

8999
steps:
90100
- name: Download all the dists
91-
uses: actions/download-artifact@v8
101+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
92102
with:
93103
name: python-package-distributions
94104
path: dist/
95105
- name: Download release notes
96-
uses: actions/download-artifact@v8
106+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
97107
with:
98108
name: release-notes
99109
- name: Sign the dists with Sigstore
100-
uses: sigstore/gh-action-sigstore-python@v3.3.0
110+
uses: sigstore/gh-action-sigstore-python@04cffa1d795717b140764e8b640de88853c92acc # v3.3.0
101111
with:
102112
inputs: >-
103113
./dist/*.tar.gz
104114
./dist/*.whl
105115
- name: Create GitHub Release
106116
env:
107117
GITHUB_TOKEN: ${{ github.token }}
118+
REF_NAME: ${{ github.ref_name }}
119+
REPO: ${{ github.repository }}
108120
run: >-
109121
gh release create
110-
'${{ github.ref_name }}'
111-
--repo '${{ github.repository }}'
122+
"$REF_NAME"
123+
--repo "$REPO"
112124
--notes-file release-notes.md
113125
- name: Upload artifact signatures to GitHub Release
114126
env:
115127
GITHUB_TOKEN: ${{ github.token }}
128+
REF_NAME: ${{ github.ref_name }}
129+
REPO: ${{ github.repository }}
116130
# Upload to GitHub Release using the `gh` CLI.
117131
# `dist/` contains the built packages, and the
118132
# sigstore-produced signatures and certificates.
119133
run: >-
120134
gh release upload
121-
'${{ github.ref_name }}' dist/**
122-
--repo '${{ github.repository }}'
135+
"$REF_NAME" dist/**
136+
--repo "$REPO"
123137
124138
publish-to-testpypi:
125139
name: Publish Python 🐍 distribution 📦 to TestPyPI
@@ -137,12 +151,12 @@ jobs:
137151

138152
steps:
139153
- name: Download all the dists
140-
uses: actions/download-artifact@v8
154+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
141155
with:
142156
name: python-package-distributions
143157
path: dist/
144158
- name: Publish distribution 📦 to TestPyPI
145-
uses: pypa/gh-action-pypi-publish@release/v1.14
159+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1.14
146160
with:
147161
repository-url: https://test.pypi.org/legacy/
148162
skip-existing: true

.github/workflows/test.yml

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,48 @@ on:
66
- main
77
pull_request:
88

9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
permissions:
14+
contents: read
15+
916
jobs:
1017
build:
18+
name: Test (Python ${{ matrix.python-version }})
1119
runs-on: ubuntu-latest
1220
strategy:
1321
matrix:
1422
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
1523

1624
steps:
17-
- uses: actions/checkout@v6
25+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
26+
with:
27+
persist-credentials: false
1828

1929
- name: Set up Python ${{ matrix.python-version }}
20-
uses: actions/setup-python@v6
30+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
2131
with:
2232
python-version: ${{ matrix.python-version }}
2333
allow-prereleases: true
2434

2535
- name: Install uv
26-
uses: astral-sh/setup-uv@v7
36+
uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7
2737

2838
- name: Test with tox
2939
run: uvx --with tox-uv --with tox-gh-actions tox
40+
41+
zizmor:
42+
name: Audit workflows (zizmor)
43+
runs-on: ubuntu-latest
44+
permissions:
45+
contents: read
46+
actions: read # zizmor-action reads workflow definitions
47+
security-events: write # upload SARIF results to code scanning
48+
steps:
49+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
50+
with:
51+
persist-credentials: false
52+
53+
- uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### 🔧 Technical Improvements
6+
- **Hardened GitHub Actions workflows**: pinned all actions to commit SHAs, scoped top-level permissions, added concurrency groups, moved `github.ref_name` / `github.repository` out of shell interpolation into `env:` vars, and added a [zizmor](https://docs.zizmor.sh/) audit job to keep workflow security regressions out of CI.
7+
38
## 4.6.0 (2026-04-11)
49

510
### 💥 Breaking Changes

0 commit comments

Comments
 (0)