From 279fd3544e0d1e7f280158d97b717090413fec3a Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Fri, 10 Jul 2026 12:21:29 -0700 Subject: [PATCH 1/2] ci: add Copilot review instructions focused on GHA credential safety Make security review of GitHub Actions a standing, per-PR control rather than something we remember to ask for. Copilot code review reads .github/copilot-instructions.md repo-wide; this directs it to scrutinize workflow changes for the token-exfiltration classes we care about: pull_request_target with PR code/secrets, untrusted ${{ }} interpolated into run:, credentials sharing a job with untrusted content, over-broad permissions, unpinned actions, lifecycle scripts under a live credential, and long-lived tokens where OIDC would do. Plus a short general secrets-in-code note. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/copilot-instructions.md | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..ed526e9 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,49 @@ +# Copilot review instructions + +Repo-wide guidance for automated code review. Prioritize correctness and +security over style. Be specific and actionable; cite the file and line. + +## GitHub Actions — credential & secret safety (review with extra scrutiny) + +Workflows under `.github/workflows/` can hold high-value credentials +(`GITHUB_TOKEN`, `NPM_TOKEN`, OIDC identities, deploy keys). Treat any change to +them as security-sensitive and flag the following: + +- **`pull_request_target` + PR-controlled code or secrets.** Flag any + `pull_request_target` (or `workflow_run`) workflow that checks out the PR head + and then builds/tests it, or exposes secrets to it. A fork PR can then run its + own code with the base repo's token. Prefer `pull_request` (forks get a + read-only token and no secrets). +- **Untrusted `${{ }}` interpolated into `run:`.** Flag any expression carrying + attacker-controllable text — `github.event.pull_request.title`/`.body`, + `.head_commit.message`, `.head_ref`/branch names, issue/comment/review bodies — + substituted directly into a shell `run:` step. That is command injection. + Require it be passed via `env:` and referenced as a quoted `"$VAR"`, or handled + in `actions/github-script` via `process.env`. +- **Credentials exposed alongside untrusted content.** Flag a job that holds a + publish/deploy secret AND processes PR- or contributor-authored content + (changeset text, PR bodies, uploaded artifacts). Prefer splitting into a + credential-free job that handles untrusted content and a separate credentialed + job that only runs reviewed, merged source. +- **Over-broad `permissions:`.** Flag missing top-level `permissions:` (defaults + are broad) and any grant not justified by the job — call out `contents: write`, + `id-token: write`, `packages: write`, `actions: write`. Prefer least privilege, + set per job. +- **Unpinned actions.** In any workflow that touches secrets or OIDC, flag + third-party actions pinned to a tag or branch (`@v4`, `@main`) instead of a + full commit SHA. +- **Lifecycle scripts under a live credential.** Flag dependency installs that + run lifecycle scripts (no `--ignore-scripts`) in a job that holds a publish + token or OIDC identity — a malicious/compromised dependency could exfiltrate + it. Prefer `--ignore-scripts` + an explicit, reviewed build step. +- **Long-lived stored tokens.** Where a registry supports it (e.g. npm trusted + publishing), prefer short-lived OIDC over a stored `NPM_TOKEN`/PAT, and flag + new long-lived secrets that could be replaced by OIDC. + +## Secrets & credentials (all code) + +- Flag hardcoded secrets, tokens, private keys, or credentials in source, + tests, fixtures, or committed config. Never read or echo `.env` / `.dev.vars` + / `.secrets`. +- Flag logging or error messages that could print a token, Authorization header, + cookie, or other secret. From b3487c0422398fb189cada1cb6906785dfb8371d Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Fri, 10 Jul 2026 12:39:11 -0700 Subject: [PATCH 2/2] docs(copilot): don't assume default token permissions are broad Copilot review on #58: GitHub's default GITHUB_TOKEN scope varies by repo/org settings and is often read-only, so encoding "defaults are broad" as repo-wide review guidance bakes in a potentially wrong assumption. Reframe to the accurate, intent-preserving guidance: prefer an explicit least-privilege permissions block over relying on the implicit default (which may grant more than the job needs). Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/copilot-instructions.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ed526e9..c16e1d1 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -25,10 +25,11 @@ them as security-sensitive and flag the following: (changeset text, PR bodies, uploaded artifacts). Prefer splitting into a credential-free job that handles untrusted content and a separate credentialed job that only runs reviewed, merged source. -- **Over-broad `permissions:`.** Flag missing top-level `permissions:` (defaults - are broad) and any grant not justified by the job — call out `contents: write`, - `id-token: write`, `packages: write`, `actions: write`. Prefer least privilege, - set per job. +- **Over-broad or implicit `permissions:`.** Prefer an explicit least-privilege + `permissions:` block over relying on the repo/org default token scope, which + varies by settings and may grant more than the job needs. Flag any grant not + justified by the job — call out `contents: write`, `id-token: write`, + `packages: write`, `actions: write` — and prefer setting permissions per job. - **Unpinned actions.** In any workflow that touches secrets or OIDC, flag third-party actions pinned to a tag or branch (`@v4`, `@main`) instead of a full commit SHA.