From 8dfb2c8c4b6f81697f10f02cdcf1efa9bf4bf2a6 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sun, 6 Sep 2026 14:41:56 -0400 Subject: [PATCH] docs: harden the Actions example in the CLI smoke-testing guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copilot round 7. The copyable job inherited the repository's default GITHUB_TOKEN scopes and left that token in the runner's git config while `npx` executed a package downloaded from npm. Adds `permissions: contents: read` and `persist-credentials: false`, each with a comment giving its reason. The review cited `.github/workflows/main.yml:11-18` as precedent for both. It supports one: main.yml does set `permissions: contents: read` at workflow level, for exactly the stated reason, and every workflow in this repo carries a block. It does not support the other — `persist-credentials` appears nowhere in .github/workflows. That half is taken on its own merit rather than as convention: the job uses git for nothing after checkout, so the credential is only attack surface while third-party code runs. Both matter more in this example than in a workflow we own, because it is written to be copied into a repository whose defaults the reader cannot see — which is now stated in the guide. The edited block was parsed as YAML to confirm both keys land where intended rather than eyeballing the indentation. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01KsKC3HniVF9dCZtDVbYp5B Signed-off-by: cliffhall --- docs/cli-smoke-testing.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/cli-smoke-testing.md b/docs/cli-smoke-testing.md index 1e8d6422..62039c82 100644 --- a/docs/cli-smoke-testing.md +++ b/docs/cli-smoke-testing.md @@ -517,8 +517,18 @@ As a GitHub Actions job: ```yaml smoke: runs-on: ubuntu-latest + # Least privilege: the job reads the repo and talks to your MCP server. It + # writes nothing back to GitHub, so do not let it inherit the repository's + # default token scopes, which may be far broader. + permissions: + contents: read steps: - uses: actions/checkout@v7 + with: + # Nothing here uses git after checkout, and the next step runs a package + # downloaded from npm. Leaving GITHUB_TOKEN in the runner's git config + # would be handing that package a credential it has no use for. + persist-credentials: false - uses: actions/setup-node@v7 with: node-version: "22.x" @@ -532,6 +542,11 @@ smoke: MCP_TOKEN: ${{ secrets.MCP_TOKEN }} ``` +Both hardening lines matter more here than in a workflow you wrote yourself, +because this one is meant to be **copied into a repository whose defaults you +cannot see**. A `permissions:` block that names what the job needs is the same +discipline this repo applies to its own workflows. + `"${auth[@]+"${auth[@]}"}"` rather than `"${auth[@]}"`: under `set -u` an empty array is an unbound variable in bash before 4.4, and the runner is not the only place this script runs — macOS still ships bash 3.2. The guarded form expands to