diff --git a/.github/workflows/workflow-lint.yml b/.github/workflows/workflow-lint.yml index f5c4efe..28388a7 100644 --- a/.github/workflows/workflow-lint.yml +++ b/.github/workflows/workflow-lint.yml @@ -26,6 +26,10 @@ on: description: sha256 of that release's linux.x86_64 tarball; travels with shellcheck-version, so overriding one without the other fails the install type: string default: "8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198" + tooling-isolation: + description: Fail when a job that holds a secret also installs third-party tooling + type: boolean + default: true permissions: contents: read @@ -83,3 +87,127 @@ jobs: env: SHELLCHECK_BIN: ${{ env.SHELLCHECK_BIN }} run: actionlint -color "-shellcheck=${SHELLCHECK_BIN}" + + # A job that installs third-party tooling is running code we did not + # write: `cargo install` (and the other installers) executes build + # scripts over the whole dependency tree, so the secrets a job holds + # are reachable from code outside this organisation. Keeping the + # install out of any job that holds a secret bounds that reach to + # nothing. `pip install --require-hashes` is the exception because a + # hash-pinned install resolves nothing — the artifacts are fixed by + # the lockfile, so no surprise tree appears. + - name: A job holding a secret must not install third-party tooling + if: ${{ inputs.tooling-isolation }} + run: | + python3 - <<'PY' + import glob + import re + import sys + + import yaml + + # `secrets.` is how every GitHub Actions expression reaches a + # secret. `secrets: inherit` and `secrets:` declarations on + # reusable calls don't match — those are declaration blocks, not + # references, and `secrets:` does not contain a `.`. + SECRET_RE = re.compile(r"(?