Skip to content

Commit 43a0d76

Browse files
ci: audit the workflows every generated project runs
These five workflows execute in every project this account generates, with contents: write, packages: write and the release app private key. Nothing checked them: the scaffold toolbox asserts they are sha-pinned and disable credential persistence, but skips both tests whenever a dot-github checkout is not beside it — which is every CI run there. The first run found one: the app token added yesterday inherited every permission the app was installed with, in every repository it can reach. Release Please needs contents: write and pull-requests: write, and now says so. zizmor and actionlint, plus the three invariants a generated project depends on, kept here rather than in a repository that may not be checked out.
1 parent c7af847 commit 43a0d76

4 files changed

Lines changed: 86 additions & 0 deletions

File tree

‎.github/workflows/app-release.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ jobs:
4545
with:
4646
app-id: ${{ secrets.RELEASE_APP_ID }}
4747
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
48+
# Without these the token carries every permission the app was
49+
# installed with, in every repository the app can reach. Release
50+
# Please needs exactly two: the release commit and tag, and the
51+
# pull request it opens.
52+
permission-contents: write
53+
permission-pull-requests: write
4854
- id: release
4955
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
5056
with:

‎.github/workflows/ci.yml‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: CI
2+
on:
3+
pull_request:
4+
push:
5+
branches: [main]
6+
concurrency:
7+
group: ${{ github.workflow }}-${{ github.ref }}
8+
cancel-in-progress: true
9+
permissions: {}
10+
11+
# The workflows in this repository run inside every project the scaffold
12+
# toolbox generates, with contents: write, packages: write and the release
13+
# app's private key. Until this file existed they were checked only from that
14+
# toolbox's own test suite, which skips those tests whenever a dot-github
15+
# checkout is not sitting beside it — so on its CI they never ran at all.
16+
jobs:
17+
lint:
18+
runs-on: ubuntu-latest
19+
permissions:
20+
contents: read
21+
timeout-minutes: 5
22+
steps:
23+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24+
with:
25+
persist-credentials: false
26+
- uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0
27+
- run: mise run ci

‎mise.lock‎

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎mise.toml‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
[tools]
2+
zizmor = "1.29.0"
3+
"aqua:rhysd/actionlint" = "1.7.12"
4+
5+
[settings]
6+
pin = true
7+
lockfile = true
8+
9+
[tasks.lint]
10+
description = "audit the reusable workflows every generated project runs"
11+
run = ["zizmor .github/workflows/", "actionlint"]
12+
13+
# These run in every project this account generates, with contents: write,
14+
# packages: write and the release app's private key. Until this repository had
15+
# CI of its own they were only ever checked from the scaffold toolbox's test
16+
# suite, which skips them whenever a dot-github checkout is not beside it —
17+
# that is, on every CI run there.
18+
[tasks.check]
19+
description = "the invariants a generated project depends on"
20+
run = [
21+
"""
22+
unpinned="$(grep -rhoE 'uses: [^ ]+@[^ ]+' .github/workflows/ | grep -vE '@[0-9a-f]{40}$' || true)"
23+
[ -z "$unpinned" ] || { printf 'actions not pinned to a sha:\\n%s\\n' "$unpinned" >&2; exit 1; }
24+
""",
25+
"""
26+
checkouts="$(grep -c 'actions/checkout@' .github/workflows/*.yml | awk -F: '{s+=$2} END {print s}')"
27+
persist="$(grep -c 'persist-credentials: false' .github/workflows/*.yml | awk -F: '{s+=$2} END {print s}')"
28+
[ "$checkouts" = "$persist" ] || {
29+
printf '%s checkouts but %s persist-credentials: false\\n' "$checkouts" "$persist" >&2; exit 1; }
30+
""",
31+
"""
32+
for wf in .github/workflows/*.yml; do
33+
grep -q '^permissions: {}' "$wf" \\
34+
|| { printf '%s does not start from a closed permission set\\n' "$wf" >&2; exit 1; }
35+
done
36+
""",
37+
]
38+
39+
[tasks.ci]
40+
run = [{ task = "lint" }, { task = "check" }]

0 commit comments

Comments
 (0)