From bcb9daae2cbae60fb183635271359b98fd718969 Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Tue, 1 Sep 2026 14:01:36 +0200 Subject: [PATCH 1/5] Bump the workflow action pins on a schedule Nothing bumped the seven workflows' action pins, so they only moved when someone noticed. Dependabot opens a weekly PR per pin, and the two required checks stand between it and master before auto-merge takes it. Only github-actions is enabled. Bumping the engine pin or a runtime Gradle dependency needs a device run, and no CI leg does one, so those stay manual. r0adkll/upload-google-play is ignored for the same reason: no PR runs play-publish.yml, so no check would validate the bump. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Xavier Roche --- .github/dependabot.yml | 12 +++++++++ .github/workflows/dependabot-automerge.yml | 31 ++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/dependabot-automerge.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..baedd8b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +# gradle and gitsubmodule stay out: bumping the engine pin or a runtime dep +# needs a device run, and no CI leg does one. +updates: + # Keep the workflow action pins current (they only rot manually otherwise). + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + ignore: + # play-publish.yml never runs on a PR, so no check validates this bump. + - dependency-name: r0adkll/upload-google-play diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml new file mode 100644 index 0000000..ee23598 --- /dev/null +++ b/.github/workflows/dependabot-automerge.yml @@ -0,0 +1,31 @@ +# CI already validates every pin dependabot bumps here, so auto-merge only saves the manual click. +name: Dependabot auto-merge + +# pull_request_target, because a dependabot pull_request gets a read-only token. +# Nothing from the PR is checked out or run, so the writable token stays safe. +on: pull_request_target + +concurrency: + group: dependabot-automerge-${{ github.ref }} + cancel-in-progress: true + +permissions: + # Both writes are what enabling auto-merge costs: the API call needs the pull + # request scope, and the merge it queues needs the contents one. + contents: write + pull-requests: write + +jobs: + automerge: + name: Arm auto-merge + if: github.repository == 'xroche/httrack-android' && github.actor == 'dependabot[bot]' + runs-on: ubuntu-24.04 + steps: + # Dependabot's body is a release-notes dump, so give the squash commit a body of its own. + - name: Enable auto-merge + run: gh pr merge --auto --squash --subject "$PR_TITLE (#$PR_NUMBER)" --body "" "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + PR_TITLE: ${{ github.event.pull_request.title }} + PR_NUMBER: ${{ github.event.pull_request.number }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 51e92a37fbe0bb3f70999d577e923c1a2e99857d Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Tue, 1 Sep 2026 14:07:04 +0200 Subject: [PATCH 2/5] Warn off a job-level if: on the two required checks GitHub counts a skipped check run as success for a required context, and neither required job can skip today. Once auto-merge arms unattended, adding a job-level if: to either would satisfy both contexts without building. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Xavier Roche --- .github/workflows/android.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index d63deaa..8f46a03 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -23,6 +23,8 @@ env: NDK_VERSION: r27d OPENSSL_VERSION: "3.0.15" +# native and assemble are the two required checks. Never give either a job-level `if:`, +# because a skipped check run counts as success and auto-merge would land on a build that never ran. jobs: # Native-only: proves recursive submodules (incl. nested coucal), libiconv # vendoring, OpenSSL 3.x, and the 64-bit ndk-build. x86_64 needs the coffeecatch From c03f0d7bea02e645e98b16b7fb0e8b674feaec57 Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Tue, 1 Sep 2026 14:13:25 +0200 Subject: [PATCH 3/5] Say what auto-merge actually gates on The header came verbatim from xroche/httrack, where every pinned action also appears in a workflow that runs on a pull request under a required context. Here docker/build-push-action, docker/login-action and actions/download-artifact appear only in workflows no PR runs, so the inherited claim was false. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Xavier Roche --- .github/workflows/dependabot-automerge.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index ee23598..2e7036d 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -1,4 +1,5 @@ -# CI already validates every pin dependabot bumps here, so auto-merge only saves the manual click. +# Auto-merge saves the manual click, but only the two required jobs gate it. A pin those jobs +# never run, such as the release and docker workflows' actions, merges unexercised. name: Dependabot auto-merge # pull_request_target, because a dependabot pull_request gets a read-only token. From 2f7861928edafb633743f31adde1f881dd97a0a2 Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Tue, 1 Sep 2026 14:21:49 +0200 Subject: [PATCH 4/5] Name a pin the required jobs really never run The example named the release and docker workflows, but those also use actions/checkout and actions/upload-artifact, which native and assemble do run. actions/download-artifact is only in play-publish.yml, so it is a true example. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Xavier Roche --- .github/workflows/dependabot-automerge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index 2e7036d..cd08d52 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -1,5 +1,5 @@ # Auto-merge saves the manual click, but only the two required jobs gate it. A pin those jobs -# never run, such as the release and docker workflows' actions, merges unexercised. +# never run, such as actions/download-artifact, merges unexercised. name: Dependabot auto-merge # pull_request_target, because a dependabot pull_request gets a read-only token. From 0eaaad2cab743e68fa6c14fd5ef9de6b10456f6b Mon Sep 17 00:00:00 2001 From: Xavier Roche Date: Tue, 1 Sep 2026 15:05:27 +0200 Subject: [PATCH 5/5] Guard on the PR author and the head repo, not the pusher github.actor is the event sender. On a synchronize that is whoever pushed, so a fork-side Dependabot push onto an attacker's PR satisfies the old guard, and GitHub's own examples use pull_request.user.login for this reason. The head-repo clause is what cannot be forged, since Dependabot pushes branches into the repo itself and never opens a PR from a fork. pull_request_target also reports the base branch as github.ref, so the old concurrency key put every PR in one group, where cancel-in-progress let any later PR event cancel an in-flight arming run. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Xavier Roche --- .github/workflows/dependabot-automerge.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index cd08d52..db82d14 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -7,7 +7,9 @@ name: Dependabot auto-merge on: pull_request_target concurrency: - group: dependabot-automerge-${{ github.ref }} + # On pull_request_target github.ref is the base branch, so keying on it puts every PR in one + # group. Any later PR event, human ones included, then cancels an in-flight arming run. + group: dependabot-automerge-${{ github.event.pull_request.number }} cancel-in-progress: true permissions: @@ -19,7 +21,12 @@ permissions: jobs: automerge: name: Arm auto-merge - if: github.repository == 'xroche/httrack-android' && github.actor == 'dependabot[bot]' + # github.actor is the pusher, not the author, so a dependabot push onto a fork PR can + # satisfy it. The head-repo clause is the one that cannot be forged. + if: >- + github.repository == 'xroche/httrack-android' + && github.event.pull_request.user.login == 'dependabot[bot]' + && github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-24.04 steps: # Dependabot's body is a release-notes dump, so give the squash commit a body of its own.