diff --git a/.github/workflows/actions-audit.yml b/.github/workflows/actions-audit.yml new file mode 100644 index 000000000..4a4cb996a --- /dev/null +++ b/.github/workflows/actions-audit.yml @@ -0,0 +1,27 @@ +name: Audit GitHub Actions Workflows + +# Kept separate from quality.yml so it is only wired into ci.yml (the PR merge +# gate), not into publish-release.yml. zizmor audits the CI/CD workflow files +# themselves rather than the code being released, so a new finding here +# shouldn't be able to block a release the way a ruff/mypy/test failure can. +on: + workflow_call: {} + +permissions: {} + +jobs: + zizmor: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Audit GitHub Actions workflows with zizmor + uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 + with: + advanced-security: false + config: .github/zizmor.yml + env: + GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d357bdf0..df5a8f1ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,3 +23,8 @@ jobs: secrets: HF_TOKEN_READ_PUBLIC_ONLY: ${{ secrets.HF_TOKEN_READ_PUBLIC_ONLY }} + actions-audit: + permissions: + contents: read + uses: ./.github/workflows/actions-audit.yml + diff --git a/.github/workflows/cut-release-branch.yml b/.github/workflows/cut-release-branch.yml index c4ab88e8e..96861162e 100644 --- a/.github/workflows/cut-release-branch.yml +++ b/.github/workflows/cut-release-branch.yml @@ -37,7 +37,7 @@ jobs: with: app-id: ${{ vars.CI_APP_ID }} private-key: ${{ secrets.CI_PRIVATE_KEY }} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: main token: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/docs-publish.yml b/.github/workflows/docs-publish.yml index ff5bc61e8..1dbb53e4f 100644 --- a/.github/workflows/docs-publish.yml +++ b/.github/workflows/docs-publish.yml @@ -66,7 +66,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false @@ -263,7 +263,7 @@ jobs: if: >- steps.latest_check.conclusion == 'skipped' || steps.latest_check.outputs.is_latest_final != 'false' - uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 + uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_branch: gh-pages diff --git a/.github/workflows/pr-update.yml b/.github/workflows/pr-update.yml index ea48da246..a0bdda9ec 100644 --- a/.github/workflows/pr-update.yml +++ b/.github/workflows/pr-update.yml @@ -26,7 +26,7 @@ jobs: contents: read steps: - name: Checkout code # Checks out the base branch, not PR branch. - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false diff --git a/.github/workflows/publish-dev-from-main.yml b/.github/workflows/publish-dev-from-main.yml index 8bfc88e99..ba1b34eaf 100644 --- a/.github/workflows/publish-dev-from-main.yml +++ b/.github/workflows/publish-dev-from-main.yml @@ -46,7 +46,7 @@ jobs: with: app-id: ${{ vars.CI_APP_ID }} private-key: ${{ secrets.CI_PRIVATE_KEY }} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: main token: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index f1e94e8d6..b8b19ea86 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -70,7 +70,7 @@ jobs: outputs: TARGET_TAG_V: ${{ steps.version_check.outputs.TRGT_VERSION }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false @@ -113,7 +113,7 @@ jobs: with: app-id: ${{ vars.CI_APP_ID }} private-key: ${{ secrets.CI_PRIVATE_KEY }} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.ref_name }} token: ${{ steps.app-token.outputs.token }} @@ -165,7 +165,7 @@ jobs: with: app-id: ${{ vars.CI_APP_ID }} private-key: ${{ secrets.CI_PRIVATE_KEY }} - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: main token: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 97b3a3a2c..1055fa78e 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -27,7 +27,7 @@ jobs: permissions: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Decide whether to publish @@ -62,6 +62,6 @@ jobs: run: uv build - name: Publish distribution 📦 to PyPI if: steps.gate.outputs.skip != 'true' - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 with: attestations: true diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 894fecc9a..24d79886f 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -21,7 +21,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Lint GitHub Actions workflows @@ -32,7 +32,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Check SPDX license headers @@ -51,7 +51,7 @@ jobs: # specific steps that need it, to limit exposure to unrelated steps like the # Ollama installer. Rotate via repo Settings -> Secrets and variables. steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Free disk space @@ -63,7 +63,7 @@ jobs: enable-cache: true - name: pre-commit cache key run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> "$GITHUB_ENV" - - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ~/.cache/pre-commit key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml', 'uv.lock') }} diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 000000000..62d3a2f64 --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,79 @@ +# zizmor configuration — ignore rules for pre-existing findings +# +# Each entry here suppresses a known, pre-existing finding that predates the +# introduction of zizmor CI enforcement. New occurrences of the same rule in +# different files or on different lines are NOT suppressed and will fail CI. +# +# Each group of ignores has a corresponding open issue that tracks the +# underlying fix. When a fix lands, remove the relevant ignore entries here. +# +# Rule reference: https://docs.zizmor.sh/audits/ + +rules: + + # --- unpinned-uses ---------------------------------------------------------- + # actions/create-github-app-token is referenced by tag (@v1) rather than a + # commit SHA across all release workflows. These workflows are + # workflow_dispatch-only and require maintainer write access to trigger, so + # the external attacker surface is low, but SHA-pinning is the right fix. + # Fix tracked in: https://github.com/generative-computing/mellea/issues/1531 + unpinned-uses: + ignore: + - cut-release-branch.yml:35 + - dispatch-to-contribs.yml:44 + - publish-dev-from-main.yml:44 + - publish-release.yml:111 + - publish-release.yml:163 + + # --- github-app ------------------------------------------------------------- + # GitHub App tokens are created without explicit permission scoping in the + # release workflows. The tokens are used for legitimate release automation + # (push commits/tags, open PRs, dispatch workflows), but the permission set + # should be narrowed to the minimum required. + # Fix tracked in: https://github.com/generative-computing/mellea/issues/1531 + github-app: + ignore: + - cut-release-branch.yml:35 + - dispatch-to-contribs.yml:44 + - publish-dev-from-main.yml:44 + - publish-release.yml:111 + - publish-release.yml:163 + + # --- template-injection ----------------------------------------------------- + # workflow_dispatch inputs used directly in run: blocks in + # dispatch-to-contribs.yml. The workflow is dispatch-only (requires + # maintainer write access); no external attacker trigger path exists. + # The fix is to route inputs through env vars per AGENTS.md convention. + # Fix tracked in: https://github.com/generative-computing/mellea/issues/1532 + template-injection: + ignore: + - dispatch-to-contribs.yml:33 + - dispatch-to-contribs.yml:34 + - dispatch-to-contribs.yml:59 + + # --- cache-poisoning -------------------------------------------------------- + # astral-sh/setup-uv and actions/setup-node use caching in the + # docs-publish build-and-validate job, which is triggered on push to main. + # The cache-poisoning risk requires write access to main (branch-protected), + # so no external attacker trigger path exists. + # Fix tracked in: https://github.com/generative-computing/mellea/issues/1534 + cache-poisoning: + ignore: + - docs-publish.yml:75 + - docs-publish.yml:81 + + # --- excessive-permissions -------------------------------------------------- + # dispatch-to-contribs.yml has no permissions: block, so it inherits the + # repo default. The workflow is release-triggered and dispatch-only. + # Fix tracked in: https://github.com/generative-computing/mellea/issues/1534 + excessive-permissions: + ignore: + - dispatch-to-contribs.yml:20 + + # --- artipacked ------------------------------------------------------------- + # publish-release.yml snapshot-docs job uses persist-credentials implicitly + # (no persist-credentials: false) because it pushes commits to main. + # Fix tracked in: https://github.com/generative-computing/mellea/issues/1534 + artipacked: + ignore: + - publish-release.yml:168