From f396968326d9253c52dd08c66aaa6bdcebb54567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tao=20Bojl=C3=A9n?= Date: Mon, 15 Jun 2026 09:23:43 +0100 Subject: [PATCH] ci: use zizmor to lint for CI security issues (#721) * Harden GitHub Actions workflows per zizmor audit Fix zizmor findings across the workflows: - Move `github.base_ref`/`inputs.ref`/step-output expressions out of `run:` blocks into `env:` vars to avoid template injection. - Scope `checks: write` down from the workflow level to only the `check_api_server` job in apply_pr_checks.yaml. - Add `persist-credentials: false` to checkout steps. - Add a zizmor CI workflow to run the audit on push and PRs. Co-Authored-By: Claude Opus 4.8 (1M context) * mdbook: drop broken cache step, install binary unconditionally zizmor's cache-poisoning auto-fix added `lookup-only: true` to the mdbook cache step. Because the install step was gated on `cache-hit != 'true'`, a cache hit then skipped both the download and the install, leaving mdbook-bin/ empty and breaking the build. The original win (PR #14 / issue #3) was switching from compiling mdbook from source to downloading the prebuilt release; the cache was only a marginal optimization on top. Removing it keeps that win, fixes the break, and resolves the cache-poisoning finding cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) * address PR comments * ignore dependabot cooldowns in zizmor * add dependency cooldown to dependabot --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .github/dependabot.yml | 14 ++++++++ .github/workflows/apply_pr_checks.yaml | 20 +++++++++--- .github/workflows/checklist.yaml | 2 ++ .github/workflows/mdbook.yml | 16 ++++----- .github/workflows/publish-db-migrator.yaml | 2 ++ .github/workflows/publish-docker.yaml | 8 +++++ .github/workflows/publish-types.yaml | 2 ++ .github/workflows/zizmor.yml | 38 ++++++++++++++++++++++ 8 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/zizmor.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d0c156c..cc35f42 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -19,6 +19,8 @@ updates: - '/nodejs-instrumentation' schedule: interval: 'weekly' + cooldown: + default-days: 7 labels: - 'dependencies' - 'dependabot' @@ -31,6 +33,8 @@ updates: directory: '/' schedule: interval: 'weekly' + cooldown: + default-days: 7 labels: - 'dependencies' - 'dependabot' @@ -58,6 +62,8 @@ updates: directory: '/db' schedule: interval: 'weekly' + cooldown: + default-days: 7 labels: - 'dependencies' - 'dependabot' @@ -85,6 +91,8 @@ updates: directory: '/migrator' schedule: interval: 'weekly' + cooldown: + default-days: 7 labels: - 'dependencies' - 'dependabot' @@ -112,6 +120,8 @@ updates: directory: '/server' schedule: interval: 'weekly' + cooldown: + default-days: 7 labels: - 'dependencies' - 'dependabot' @@ -139,6 +149,8 @@ updates: directory: '/client' schedule: interval: 'weekly' + cooldown: + default-days: 7 labels: - 'dependencies' - 'dependabot' @@ -166,6 +178,8 @@ updates: directory: '/nodejs-instrumentation' schedule: interval: 'weekly' + cooldown: + default-days: 7 labels: - 'dependencies' - 'dependabot' diff --git a/.github/workflows/apply_pr_checks.yaml b/.github/workflows/apply_pr_checks.yaml index 0b30d76..a081826 100644 --- a/.github/workflows/apply_pr_checks.yaml +++ b/.github/workflows/apply_pr_checks.yaml @@ -5,8 +5,6 @@ on: permissions: contents: read - actions: read - checks: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -38,6 +36,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - name: Check generated GraphQL is up to date run: docker compose run --rm --quiet-pull codegen-check @@ -50,11 +50,13 @@ jobs: ref: ${{ github.base_ref || 'main' }} fetch-depth: 1 sparse-checkout: 'db/src/scripts' + persist-credentials: false - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: # this is needed to fetch the main branch fetch-depth: 1 sparse-checkout: 'db/src/scripts' + persist-credentials: false - name: Check changed migration files id: check-changed-migration-files @@ -92,7 +94,7 @@ jobs: # Checkout main so that we're correctly identifying the existing migrations # in the loop below. - git checkout ${{ github.base_ref || 'main' }} + git checkout "$BASE_REF" # Loop through each sub-directory under the scripts directory for subdir in ${SCRIPTS_DIR}*/ ; do @@ -100,7 +102,7 @@ jobs: IFS=$'\n' sorted_existing_migrations=($(sort <<<"${existing_migrations[*]}")) most_recent_migration_datestamp=$(datestamp ${sorted_existing_migrations[-1]}) - readarray -t CHANGED_FILES < <(git diff --name-only $GITHUB_SHA ${{ github.base_ref || 'main' }}) + readarray -t CHANGED_FILES < <(git diff --name-only "$GITHUB_SHA" "$BASE_REF") # Identify and validate changed migration files in the current sub-directory printf "%s\n" "${existing_migrations[@]}" @@ -118,6 +120,8 @@ jobs: done exit 0 + env: + BASE_REF: ${{ github.base_ref || 'main' }} # jobs have to be defined in the root workflow file in order to be marked as # 'Required Checks' in GitHub CI @@ -126,9 +130,15 @@ jobs: if: needs.changes.outputs.server == 'true' runs-on: ubuntu-latest timeout-minutes: 15 + permissions: + contents: read + actions: read + checks: write # for dorny/test-reporter to create a check run steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - name: Lint run: docker compose run --rm --quiet-pull backend npm run lint @@ -165,6 +175,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - name: Lint client run: docker compose run --rm --quiet-pull client npm run lint diff --git a/.github/workflows/checklist.yaml b/.github/workflows/checklist.yaml index 7004149..fb7c3f5 100644 --- a/.github/workflows/checklist.yaml +++ b/.github/workflows/checklist.yaml @@ -11,6 +11,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - name: Checklist uses: ethanresnick/pr-checklists@4fcf5dc226d3b7eddb209e58fdbd8226f4d179d3 # v1 (no patch tags published) env: diff --git a/.github/workflows/mdbook.yml b/.github/workflows/mdbook.yml index 2b15405..2b2cf6c 100644 --- a/.github/workflows/mdbook.yml +++ b/.github/workflows/mdbook.yml @@ -48,12 +48,13 @@ jobs: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: ref: ${{ inputs.ref }} + persist-credentials: false - name: Determine deployment path id: deploy-path run: | # For manual backfill, use the input ref; otherwise derive from the triggering ref - DEPLOY_REF="${{ inputs.ref }}" + DEPLOY_REF="${INPUTS_REF}" if [[ -z "$DEPLOY_REF" ]]; then if [[ "${{ github.event_name }}" == "workflow_dispatch" && "$GITHUB_REF_NAME" != "main" ]]; then echo "Dispatching from a non-main branch requires a ref input (e.g. a tag name)" >&2 @@ -70,16 +71,10 @@ jobs: else echo "path=latest" >> "$GITHUB_OUTPUT" fi - - - name: Cache mdBook binary - id: cache-mdbook - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: mdbook-bin - key: mdbook-${{ env.MDBOOK_VERSION }}-${{ runner.os }}-${{ runner.arch }} + env: + INPUTS_REF: ${{ inputs.ref }} - name: Install mdBook - if: steps.cache-mdbook.outputs.cache-hit != 'true' run: | ARCH="x86_64-unknown-linux-gnu" ARCHIVE="mdbook-v${MDBOOK_VERSION}-${ARCH}.tar.gz" @@ -95,8 +90,9 @@ jobs: - name: Deploy to GitHub Pages env: GITHUB_TOKEN: ${{ github.token }} + STEPS_DEPLOY_PATH_OUTPUTS_PATH: ${{ steps.deploy-path.outputs.path }} run: | - DEPLOY_PATH="${{ steps.deploy-path.outputs.path }}" + DEPLOY_PATH="${STEPS_DEPLOY_PATH_OUTPUTS_PATH}" # Clone the gh-pages branch, or initialize it if this is the first deployment if git ls-remote --exit-code --heads origin gh-pages > /dev/null 2>&1; then diff --git a/.github/workflows/publish-db-migrator.yaml b/.github/workflows/publish-db-migrator.yaml index a4bf06a..d6fa6a9 100644 --- a/.github/workflows/publish-db-migrator.yaml +++ b/.github/workflows/publish-db-migrator.yaml @@ -17,6 +17,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - name: Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 diff --git a/.github/workflows/publish-docker.yaml b/.github/workflows/publish-docker.yaml index 3daaec0..c1b0be8 100644 --- a/.github/workflows/publish-docker.yaml +++ b/.github/workflows/publish-docker.yaml @@ -62,6 +62,8 @@ jobs: fi - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 id: filter @@ -95,6 +97,8 @@ jobs: image: coop-worker steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 @@ -139,6 +143,8 @@ jobs: packages: write steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 @@ -183,6 +189,8 @@ jobs: packages: write steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 diff --git a/.github/workflows/publish-types.yaml b/.github/workflows/publish-types.yaml index a8ff4c3..4f1d969 100644 --- a/.github/workflows/publish-types.yaml +++ b/.github/workflows/publish-types.yaml @@ -17,6 +17,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false - name: Setup Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 0000000..80dfa44 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,38 @@ +name: GitHub Actions security analysis + +on: + push: + branches: ['main'] + pull_request: + branches: ['**'] + +concurrency: + group: zizmor-${{ github.ref }} + cancel-in-progress: true + +permissions: {} + +jobs: + zizmor: + name: Run zizmor + runs-on: ubuntu-latest + permissions: + security-events: write # to upload SARIF results to code scanning + contents: read + actions: read + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + persist-credentials: false + + - name: Run zizmor + uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 + with: + version: "1.25.2" + # only upload to github advanced security on `main`. + # on PRs from forks (which do not have permission to push SARIF results) + # we surface findings as PR annotations instead. + advanced-security: ${{ github.event_name == 'push' }} + annotations: ${{ github.event_name == 'pull_request' }} + -- 2.51.2