From ea90ae9349c22673219e3579f4e7ed33d12585f1 Mon Sep 17 00:00:00 2001 From: Guillaume Sainthillier Date: Fri, 2 Oct 2026 09:57:46 +0200 Subject: [PATCH 1/3] ci: restrict the default GITHUB_TOKEN to read-only Declare `permissions: contents: read` at the workflow level so jobs that don't need more get a read-only token. Jobs that push, comment or publish keep their explicit job-level grants. The repository default is now read-only too, with Actions no longer allowed to approve pull requests. --- .github/workflows/continuous-integration.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 60fb7ac..d31fe67 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -8,6 +8,9 @@ on: schedule: - cron: '0 0 * * 0' +permissions: + contents: read + jobs: lint: name: Lint From 6a6f6dd8180528aeb492ddd0fec7acd09f503774 Mon Sep 17 00:00:00 2001 From: Guillaume Sainthillier Date: Fri, 2 Oct 2026 09:58:07 +0200 Subject: [PATCH 2/3] ci: detect changed image paths with git diff instead of tj-actions tj-actions/changed-files was compromised in March 2025 (CVE-2025-30066) and runs on same-repo Renovate PRs, where the Docker Hub token is available. A plain `git diff` against the merge commit's base parent does the same job without a third-party action. --- .github/workflows/continuous-integration.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index d31fe67..881f4c0 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -84,12 +84,21 @@ jobs: steps: - name: Checkout uses: actions/checkout@v7 + with: + # HEAD is the PR merge commit and HEAD^1 the base branch tip, so + # `git diff HEAD^1 HEAD` is exactly what the PR changes. + fetch-depth: 2 - name: Check if path changed if: github.event_name == 'pull_request' id: changed - uses: tj-actions/changed-files@v47 - with: - files: ${{ matrix.path }}/** + env: + MATRIX_PATH: ${{ matrix.path }} + run: | + if git diff --quiet HEAD^1 HEAD -- "$MATRIX_PATH/"; then + echo "any_changed=false" >> "$GITHUB_OUTPUT" + else + echo "any_changed=true" >> "$GITHUB_OUTPUT" + fi - name: Set up QEMU if: github.event_name != 'pull_request' || steps.changed.outputs.any_changed == 'true' uses: docker/setup-qemu-action@v4 From 65a69692a40fbb7a28b9788989ae35a6afb8f4f7 Mon Sep 17 00:00:00 2001 From: Guillaume Sainthillier Date: Fri, 2 Oct 2026 10:13:49 +0200 Subject: [PATCH 3/3] ci: bound job run times and cancel superseded pull request runs Every job now has a timeout-minutes sized from observed run times (CI jobs 5-15 min, image builds 30-90 min, deploys 20 min) instead of the 6-hour default. Pull request workflows cancel the previous run of the same PR when a new commit is pushed; runs on push are never cancelled. --- .github/workflows/continuous-integration.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 881f4c0..d6af697 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -11,10 +11,16 @@ on: permissions: contents: read +# Supersede outdated runs of a pull request; push runs (head_ref empty) are never cancelled. +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: lint: name: Lint runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@v7 @@ -29,6 +35,7 @@ jobs: name: Build (${{ matrix.path }}) needs: [lint] runs-on: ubuntu-latest + timeout-minutes: 90 strategy: fail-fast: false matrix: @@ -127,6 +134,7 @@ jobs: needs: [lint, build] if: always() runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Check that all required jobs succeeded if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')