Skip to content

Commit baaae33

Browse files
brynpickeringclaude
andcommitted
Fix workflows following security review.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent c8b3355 commit baaae33

15 files changed

Lines changed: 304 additions & 111 deletions

File tree

‎.github/actionlint.yaml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ paths:
33
ignore:
44
- 'specifying action "\$/[^"]+" in invalid format because ref is missing'
55
- 'reusable workflow call "\$/[^"]+" at "uses" is not following the format'
6+
.github/workflows/release.yml:
7+
ignore:
8+
- 'property "pypsa_bot_private_key" is not defined'

‎.github/actions/pypsa-eur-swap/action.yml‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ inputs:
1111
description: Commit SHA (40 hex chars) or branch to install.
1212
required: true
1313
also-upgrade:
14-
description: Space-separated packages to upgrade afterwards.
14+
description: Space-separated packages to upgrade afterwards. They and the swapped package skip the pypsa-eur exclude-newer cooldown.
1515
default: ''
1616
working-directory:
1717
description: pypsa-eur checkout.
@@ -30,7 +30,16 @@ runs:
3030
run: |
3131
set -euo pipefail
3232
PACKAGE="${PACKAGE:-${GITHUB_REPOSITORY#*/}}"
33-
sed -i.bak '/^exclude-newer[[:space:]]*=/d' pixi.toml && rm pixi.toml.bak
33+
awk -v pkgs="$PACKAGE $ALSO_UPGRADE" '
34+
function exempt() { for (i = 1; i <= n; i++) print "\"" p[i] "\" = \"0d\""; done = 1 }
35+
BEGIN { n = split(pkgs, p, " "); for (i = 1; i <= n; i++) skip[p[i]] = 1 }
36+
/^\[/ { section = $1 }
37+
section == "[exclude-newer]" { key = $1; gsub(/"/, "", key); if (key in skip) next }
38+
{ print }
39+
$1 == "[exclude-newer]" { exempt() }
40+
END { if (!done) { print ""; print "[exclude-newer]"; exempt() } }
41+
' pixi.toml >pixi.toml.new
42+
mv pixi.toml.new pixi.toml
3443
pixi remove "$PACKAGE"
3544
if [[ "$REV" =~ ^[0-9a-f]{40}$ ]]; then ref_flag=--rev; else ref_flag=--branch; fi
3645
pixi add --pypi --git "https://github.com/$REPOSITORY.git" "$PACKAGE" "$ref_flag" "$REV"

‎.github/actions/secret-env/action.yml‎

Lines changed: 0 additions & 36 deletions
This file was deleted.

‎.github/actions/setup-env/action.yml‎

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ inputs:
1616
uv-locked:
1717
description: Library mode only. Install dependencies from uv.lock with uv sync before the wheel.
1818
default: 'false'
19+
cooldown:
20+
description: Library mode only. Ignore conda and PyPI releases newer than this age, e.g. 7d. Empty disables. uv-locked installs ignore it.
21+
default: 7d
1922
wheel-dir:
2023
description: Directory, relative to working-directory or absolute, that holds exactly one wheel.
2124
default: dist
@@ -28,14 +31,11 @@ inputs:
2831
pixi-version:
2932
description: pixi version.
3033
default: v0.81.0
31-
secrets-json:
32-
description: Pass toJSON(secrets).
33-
default: '{}'
3434
vars-json:
3535
description: Pass toJSON(vars).
3636
default: '{}'
37-
secret-env:
38-
description: Space-separated secret or variable names to export as env vars.
37+
vars-env:
38+
description: Space-separated variable names to export as env vars.
3939
default: ''
4040
working-directory:
4141
description: Directory of the caller package or project.
@@ -54,12 +54,11 @@ runs:
5454
echo "::error::mode must be library or project" >&2
5555
exit 1
5656
57-
- if: inputs.secret-env != ''
58-
uses: $/.github/actions/secret-env
57+
- if: inputs.vars-env != ''
58+
uses: $/.github/actions/vars-env
5959
with:
60-
secrets-json: ${{ inputs.secrets-json }}
6160
vars-json: ${{ inputs.vars-json }}
62-
names: ${{ inputs.secret-env }}
61+
names: ${{ inputs.vars-env }}
6362

6463
- name: Prepare library environment
6564
id: library-prep
@@ -68,9 +67,11 @@ runs:
6867
env:
6968
PYTHON_VERSION: ${{ inputs.python-version }}
7069
PIXI_ADD: ${{ inputs.pixi-add }}
70+
COOLDOWN: ${{ inputs.cooldown }}
7171
run: |
7272
set -euo pipefail
7373
[ -n "$PYTHON_VERSION" ] || { echo "::error::python-version is required in library mode" >&2; exit 1; }
74+
[[ "$COOLDOWN" =~ ^([0-9]+d)?$ ]] || { echo "::error::cooldown must be empty or a number of days such as 7d, got '$COOLDOWN'" >&2; exit 1; }
7475
case "$RUNNER_OS-$RUNNER_ARCH" in
7576
Linux-X64) platform=linux-64 ;;
7677
Linux-ARM64) platform=linux-aarch64 ;;
@@ -87,6 +88,7 @@ runs:
8788
done
8889
mkdir -p "$temp/env"
8990
printf '[workspace]\nname = "env"\nchannels = [%s]\nplatforms = ["%s"]\n' "$channels" "$platform" >"$temp/env/pixi.toml"
91+
[ -z "$COOLDOWN" ] || printf 'exclude-newer = "%s"\n' "$COOLDOWN" >>"$temp/env/pixi.toml"
9092
cat "$temp/env/pixi.toml"
9193
{
9294
echo "PIXI_CACHE_DIR=$temp/pixi-cache"
@@ -140,6 +142,7 @@ runs:
140142
EXTRAS: ${{ inputs.extras }}
141143
UV_LOCKED: ${{ inputs.uv-locked }}
142144
WHEEL_DIR: ${{ inputs.wheel-dir }}
145+
COOLDOWN: ${{ inputs.cooldown }}
143146
run: |
144147
set -euo pipefail
145148
read -ra specs <<<"$PIXI_ADD"
@@ -156,10 +159,9 @@ runs:
156159
UV_PROJECT_ENVIRONMENT="$(dirname "$MANIFEST")/.pixi/envs/default" \
157160
$run uv sync --locked --inexact --no-install-project ${extra_args[@]+"${extra_args[@]}"}
158161
$run uv pip install --no-deps "$wheel"
159-
elif [ -n "$EXTRAS" ]; then
160-
$run uv pip install "$wheel[$EXTRAS]"
161162
else
162-
$run uv pip install "$wheel"
163+
[ -z "$COOLDOWN" ] || export UV_EXCLUDE_NEWER="$COOLDOWN"
164+
$run uv pip install "$wheel${EXTRAS:+[$EXTRAS]}"
163165
fi
164166
echo "run=$run" >>"$GITHUB_OUTPUT"
165167
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Export variable env
2+
description: Export named repository or organization variables as env vars. A missing name fails.
3+
inputs:
4+
vars-json:
5+
description: Pass toJSON(vars).
6+
default: '{}'
7+
names:
8+
description: Space-separated variable names.
9+
required: true
10+
runs:
11+
using: composite
12+
steps:
13+
- name: Export variable env
14+
shell: bash
15+
env:
16+
VARS_JSON: ${{ inputs.vars-json }}
17+
NAMES: ${{ inputs.names }}
18+
run: |
19+
set -euo pipefail
20+
set -f
21+
for name in $NAMES; do
22+
[[ "$name" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || { echo "::error::vars-env: invalid name '$name'" >&2; exit 1; }
23+
jq -e --arg n "$name" 'has($n)' <<<"$VARS_JSON" >/dev/null || { echo "::error::vars-env: '$name' is not a variable" >&2; exit 1; }
24+
value=$(jq -r --arg n "$name" '.[$n]' <<<"$VARS_JSON")
25+
delim="EOF_$(openssl rand -hex 16)"
26+
printf '%s<<%s\n%s\n%s\n' "$name" "$delim" "$value" "$delim" >>"$GITHUB_ENV"
27+
done

‎.github/workflows/benchmark.yml‎

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ on:
2323
type: string
2424
default: ""
2525
description: Space-separated conda-forge specs to add to the env.
26+
cooldown:
27+
type: string
28+
default: 7d
29+
description: Ignore conda and PyPI releases newer than this age, e.g. 7d. Empty disables. uv-locked installs ignore it.
2630
pytest-args:
2731
type: string
2832
default: ""
@@ -39,14 +43,23 @@ on:
3943
type: string
4044
default: ""
4145
description: Fail if a benchmark mean regresses by more than this percent. Empty never fails (compare mode only).
42-
secret-env:
46+
vars-env:
4347
type: string
4448
default: ""
45-
description: Space-separated secret or variable names to export to the job env.
49+
description: Space-separated repository or organization variable (`vars`) names to export to the job env.
4650
working-directory:
4751
type: string
4852
default: .
4953
description: Directory of the package, relative to the repo root.
54+
secrets:
55+
CDSAPI_TOKEN:
56+
required: false
57+
EARTHDATAHUB_API_KEY:
58+
required: false
59+
MSK_LICENSE:
60+
required: false
61+
GUROBI_LIC:
62+
required: false
5063

5164
permissions: {}
5265

@@ -59,6 +72,13 @@ jobs:
5972
outputs:
6073
legs: ${{ steps.legs.outputs.legs }}
6174
steps:
75+
- uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
76+
with:
77+
egress-policy: audit
78+
- if: github.event_name == 'pull_request_target'
79+
run: |
80+
echo "::error::pull_request_target runs fork code with secrets. Use pull_request." >&2
81+
exit 1
6282
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
6383
with:
6484
fetch-depth: 0
@@ -92,6 +112,10 @@ jobs:
92112
matrix:
93113
include: ${{ fromJSON(needs.build.outputs.legs) }}
94114
steps:
115+
- uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
116+
if: matrix.runner != 'codspeed-macro'
117+
with:
118+
egress-policy: audit
95119
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
96120
with:
97121
persist-credentials: false
@@ -105,15 +129,19 @@ jobs:
105129
python-version: ${{ inputs.python-version }}
106130
pixi-add: ${{ inputs.pixi-add }}
107131
extras: ${{ inputs.extras }}
132+
cooldown: ${{ inputs.cooldown }}
108133
wheel-dir: ${{ runner.temp }}/wheels
109-
secrets-json: ${{ toJSON(secrets) }}
110134
vars-json: ${{ toJSON(vars) }}
111-
secret-env: ${{ inputs.secret-env }}
135+
vars-env: ${{ inputs.vars-env }}
112136
working-directory: ${{ inputs.working-directory }}
113137
- uses: CodSpeedHQ/action@c4fd08a3a159bd0cc208da1e0edf32b8c47d75e5 # v5.4.0
114138
env:
115139
BENCHMARK_PATH: ${{ inputs.benchmark-path }}
116140
PYTEST_ARGS: ${{ inputs.pytest-args }}
141+
CDSAPI_TOKEN: ${{ secrets.CDSAPI_TOKEN }}
142+
EARTHDATAHUB_API_KEY: ${{ secrets.EARTHDATAHUB_API_KEY }}
143+
MSK_LICENSE: ${{ secrets.MSK_LICENSE }}
144+
GUROBI_LIC: ${{ secrets.GUROBI_LIC }}
117145
with:
118146
mode: ${{ matrix.mode }}
119147
working-directory: ${{ inputs.working-directory }}
@@ -131,6 +159,13 @@ jobs:
131159
EXTRAS: ${{ inputs.extras }}
132160
IS_PR: ${{ github.event_name == 'pull_request' }}
133161
steps:
162+
- uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
163+
with:
164+
egress-policy: audit
165+
- if: github.event_name == 'pull_request_target'
166+
run: |
167+
echo "::error::pull_request_target runs fork code with secrets. Use pull_request." >&2
168+
exit 1
134169
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
135170
with:
136171
ref: ${{ github.event.pull_request.head.sha || github.sha }}
@@ -189,13 +224,18 @@ jobs:
189224
python-version: ${{ inputs.python-version }}
190225
pixi-add: ${{ inputs.pixi-add }}
191226
extras: ${{ inputs.extras }}
227+
cooldown: ${{ inputs.cooldown }}
192228
wheel-dir: ${{ steps.wheels.outputs.first }}
193-
secrets-json: ${{ toJSON(secrets) }}
194229
vars-json: ${{ toJSON(vars) }}
195-
secret-env: ${{ inputs.secret-env }}
230+
vars-env: ${{ inputs.vars-env }}
196231
working-directory: ${{ steps.base.outputs.present == 'true' && 'base' || 'head' }}/${{ inputs.working-directory }}
197232
- if: steps.base.outputs.present == 'true'
198233
working-directory: base/${{ inputs.working-directory }}
234+
env:
235+
CDSAPI_TOKEN: ${{ secrets.CDSAPI_TOKEN }}
236+
EARTHDATAHUB_API_KEY: ${{ secrets.EARTHDATAHUB_API_KEY }}
237+
MSK_LICENSE: ${{ secrets.MSK_LICENSE }}
238+
GUROBI_LIC: ${{ secrets.GUROBI_LIC }}
199239
run: |
200240
set -euo pipefail
201241
if [ -e "$BENCHMARK_PATH" ]; then
@@ -209,6 +249,11 @@ jobs:
209249
WHEEL: ${{ steps.wheels.outputs.head }}
210250
run: $RUN uv pip install --reinstall-package "$PACKAGE" "$WHEEL[$EXTRAS]"
211251
- working-directory: head/${{ inputs.working-directory }}
252+
env:
253+
CDSAPI_TOKEN: ${{ secrets.CDSAPI_TOKEN }}
254+
EARTHDATAHUB_API_KEY: ${{ secrets.EARTHDATAHUB_API_KEY }}
255+
MSK_LICENSE: ${{ secrets.MSK_LICENSE }}
256+
GUROBI_LIC: ${{ secrets.GUROBI_LIC }}
212257
run: $RUN pytest "$BENCHMARK_PATH" --benchmark-only --benchmark-json="$RUNNER_TEMP/head.json" $PYTEST_ARGS
213258
- working-directory: head/${{ inputs.working-directory }}
214259
run: |

‎.github/workflows/ci.yml‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,27 @@ jobs:
1616
permissions:
1717
contents: read
1818
steps:
19+
- uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
20+
with:
21+
egress-policy: audit
1922
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
2023
with:
2124
persist-credentials: false
2225
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
23-
- run: uvx --from actionlint-py actionlint
24-
- run: uvx zizmor --offline .github
26+
with:
27+
version: 0.13.0
28+
- run: uvx --from actionlint-py==1.7.12.25 actionlint
29+
- env:
30+
GH_TOKEN: ${{ github.token }}
31+
run: uvx zizmor@1.30.1 .github
2532

2633
matrix-action:
2734
runs-on: ubuntu-latest
2835
permissions: {}
2936
steps:
37+
- uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
38+
with:
39+
egress-policy: audit
3040
- id: lean
3141
uses: $/.github/actions/matrix
3242
with:
@@ -50,7 +60,7 @@ jobs:
5060
uses: ./.github/workflows/test.yml
5161
permissions:
5262
contents: read
53-
secrets: inherit
63+
id-token: write
5464
with:
5565
working-directory: tests/fixture-package
5666
pixi-add: glpk
@@ -60,6 +70,7 @@ jobs:
6070
uses: ./.github/workflows/test.yml
6171
permissions:
6272
contents: read
73+
id-token: write
6374
with:
6475
working-directory: tests/fixture-project
6576
pixi-task: test
@@ -72,7 +83,6 @@ jobs:
7283
contents: read
7384
pull-requests: write
7485
id-token: write
75-
secrets: inherit
7686
with:
7787
mode: compare
7888
benchmark-path: benchmarks
@@ -90,7 +100,6 @@ jobs:
90100
permissions:
91101
contents: write
92102
id-token: write
93-
secrets: inherit
94103
with:
95104
dry-run: true
96105
test: false
@@ -103,7 +112,6 @@ jobs:
103112
permissions:
104113
contents: read
105114
pull-requests: read
106-
secrets: inherit
107115
with:
108116
package: pypsa
109117
repository: PyPSA/PyPSA

‎.github/workflows/link-check.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ jobs:
2828
permissions:
2929
contents: read
3030
steps:
31+
- uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3
32+
with:
33+
egress-policy: audit
3134
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3235
with:
3336
persist-credentials: false

0 commit comments

Comments
 (0)