blog.git

blog

~ Personal research notes. blog.sqzer.com
git clone https://github.com/debuman/blog.git
commit 591166a63d287a4e6f262bed9aef61fd29bb3d05
parent eeb8e14abbc2c2e9b50b9b191d9db8e9bf2f9908
Author: sqzer <110329244+sqzer-x@users.noreply.github.com>
Date: Fri, 25 Sep 2026 03:42:44 +0900

ci: pin actions to SHAs, scope permissions per job, refuse lockfile drift

- checkout, withastro/action and deploy-pages are pinned to the commit
SHAs of v7.0.1, v6.1.3 and v5.0.1. A moved tag can no longer change
what runs without a change here. Dependabot proposes the bumps.
- Workflow-level permissions are empty. The build job, which runs every
npm dependency, mermaid and Chrome, gets contents: read. Only the
deploy job holds pages: write and id-token: write, the minimum
deploy-pages documents.
- checkout no longer leaves the token in .git/config.
- withastro/action installs with npm install, which quietly resolves new
versions when package.json and the lockfile disagree. A dry-run npm ci
before it fails the job instead, without installing or running any
package script. Checked on npm 10 and 11.

Diffstat:
A.github/dependabot.yml | 11+++++++++++
M.github/workflows/deploy.yml | 31++++++++++++++++++++++++-------
2 files changed, 35 insertion(+), 7 deletion(-)

diff --git a/.github/dependabot.yml b/.github/dependabot.yml
@@ -0,0 +1,11 @@
+# Keeps the SHA-pinned actions in .github/workflows current. A pin that is never bumped
+# stops receiving the action's fixes, so each release arrives as a pull request instead.
+version: 2
+updates:
+  - package-ecosystem: github-actions
+    directory: /
+    schedule:
+      interval: weekly
+    groups:
+      actions:
+        patterns: ['*']
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
@@ -5,22 +5,35 @@ on:
     branches: [main]
   workflow_dispatch:
 
-permissions:
-  contents: read
-  pages: write
-  id-token: write
+# Nothing granted at workflow level; each job names what it needs. The build job runs
+# every npm dependency, mermaid and Chrome, so it can read the repository and nothing
+# more. Only the deploy job, which runs one pinned action, can write to Pages or mint an
+# OIDC token.
+permissions: {}
 
 # Serialise deploys without cancelling one that is already running.
 concurrency:
   group: pages
   cancel-in-progress: false
 
+# Actions are pinned to commit SHAs rather than tags: a tag can be moved to other code with
+# no change in this repository, a SHA cannot. The comment after each pin names the release.
+# Dependabot proposes the bumps (.github/dependabot.yml).
 jobs:
   build:
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
     steps:
-      - uses: actions/checkout@v7
-      - uses: withastro/action@v6
+      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+        with:
+          # Nothing in this job pushes, so the token is not left behind in .git/config.
+          persist-credentials: false
+      # withastro/action installs with `npm install`, which quietly resolves new versions
+      # when package.json and package-lock.json disagree. `npm ci` refuses instead; as a
+      # dry run it does so without installing anything or running any package's scripts.
+      - run: npm ci --dry-run --ignore-scripts
+      - uses: withastro/action@3eafd002e65cc31b4f0eae0bb05450d521562247 # v6.1.3
         with:
           node-version: 24
           # Must go through npm run build so that prebuild — the content gate — runs.
@@ -39,9 +52,13 @@ jobs:
   deploy:
     needs: build
     runs-on: ubuntu-latest
+    # The minimum actions/deploy-pages documents for the job that deploys.
+    permissions:
+      pages: write
+      id-token: write
     environment:
       name: github-pages
       url: ${{ steps.deployment.outputs.page_url }}
     steps:
       - id: deployment
-        uses: actions/deploy-pages@v5
+        uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1