docs: harden the Actions example in the CLI smoke-testing guide

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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KsKC3HniVF9dCZtDVbYp5B
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-06 14:41:56 -04:00
commit 8dfb2c8c4b

View file

@ -517,8 +517,18 @@ As a GitHub Actions job:
```yaml ```yaml
smoke: smoke:
runs-on: ubuntu-latest 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: steps:
- uses: actions/checkout@v7 - 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 - uses: actions/setup-node@v7
with: with:
node-version: "22.x" node-version: "22.x"
@ -532,6 +542,11 @@ smoke:
MCP_TOKEN: ${{ secrets.MCP_TOKEN }} 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 `"${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 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 place this script runs — macOS still ships bash 3.2. The guarded form expands to