Repository navigation
Conversation
Git tags are mutable: whoever controls the upstream repository (or an attacker who compromises it) can move `v4` or even `v7.0.1` to point at different code, and every workflow referencing the tag will silently run it on the next trigger. This has happened in the wild (e.g. the tj-actions/changed-files compromise, CVE-2025-30066). Pinning to the full 40-character commit SHA makes the reference immutable. The SHAs below are the commits the existing tags resolve to today, so CI behavior is unchanged; the trailing comment records the exact release for readability and for update tooling such as Dependabot. - actions/checkout@v4 -> 11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - actions/checkout@v7.0.1 -> 3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same rationale as the actions/checkout pin: a mutable tag lets the upstream (or anyone who compromises it) change the code CI executes without any change in this repository. install-nix-action is especially sensitive because it runs with the job's token and installs the Nix toolchain that every subsequent build step executes inside. The SHAs are the commits the current tags resolve to, so behavior is unchanged: - cachix/install-nix-action@v27 -> ba0dd844c9180cbf77aa72a116d6fbc515d0e87b # v27 - cachix/install-nix-action@31.11.3 -> 75c75c31ebece974e48ebc36b4ad125bd97bd97b # v31.11.3 (Upstream publishes `v27` only as a major tag with no v27.x.y release tag on that commit, so the comment keeps `v27`. `31.11.3` and `v31.11.3` are both upstream tags on the same commit; the comment uses the v-prefixed form for consistency with the other pins.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same rationale as the other pins. deploy-pages is the most sensitive of the three: its job holds `pages: write` and `id-token: write`, so code substituted behind a moved tag could deface the docs site or mint OIDC tokens for this repository. The SHAs are the commits the current tags resolve to, so behavior is unchanged: - actions/configure-pages@v5 -> 983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0 - actions/upload-pages-artifact@v3 -> 56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 - actions/deploy-pages@v4 -> d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 Note: upload-pages-artifact is a composite action that internally calls actions/upload-artifact by tag, which this repository cannot pin; see the PR description. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This is the highest-value pin in the repository. The release workflow runs with `contents: write` and this action is what publishes the .vsix that users install, so a moved `v2` tag would let a third party replace or tamper with release assets or rewrite release notes. Unlike the actions/* pins it is maintained by an individual, which widens the set of accounts whose compromise reaches this repo. The SHA is the commit the current `v2` tag resolves to (a self-contained node20 action with a bundled dist/index.js and no nested `uses:`), so behavior is unchanged: - softprops/action-gh-release@v2 -> 3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main.yml had no `permissions:` block, so its GITHUB_TOKEN inherits the repository/organization default. For repositories created before February 2023 (and any repo where the setting was never changed) that default is read/write across most scopes, including contents, packages, actions and pull-requests. The job only checks out code, builds, tests and packages; it never writes to the repository or the API. Declaring `contents: read` explicitly makes the workflow least-privilege regardless of the repo setting, so a compromised build dependency (npm packages are installed with lifecycle scripts during `./do test`/`./do package`) cannot use the token to push commits, move tags, or tamper with releases. Fork pull requests already receive a read-only token, so this changes nothing for them; it only narrows `push` runs to what they actually use. This matches the explicit blocks already present in docs.yml and release.yml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
By default actions/checkout writes the job's GITHUB_TOKEN into .git/config so later steps can run authenticated git commands. In the release job that token has `contents: write`, and the steps that follow run `nix develop -c ./do package`, which executes `npm install` (with package lifecycle scripts) and `npx vsce`. Any compromised package in that dependency tree could read the token from .git/config and push commits, move tags, or alter releases. Nothing after checkout needs the persisted credential: - `git fetch --tags --force origin` reads from a public repository and works anonymously. - `./do package` / `./do release-notes` make no authenticated git or API calls. - softprops/action-gh-release receives the token through its own `token` input (default `github.token`), not from git config. Setting `persist-credentials: false` keeps the token available only to the action that actually needs it. Behavior of the release is otherwise unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
actionlint/shellcheck flags SC2086 on the unquoted ${GITHUB_REF_NAME#v}
passed to `./do release-notes`. It is not exploitable today, because
the workflow's tag filter (v[0-9]+.[0-9]+.[0-9]+) means the value can
only contain digits and dots, but quoting it is the correct form, keeps
the workflow lint-clean, and stays safe if the trigger filter is ever
loosened. No behavior change for any tag the workflow currently accepts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
|
Thanks for your contribution. I agree with the idea behind this change, I just need to take a close look at all the commits before moving forward. FYI, Github actions are not currently used for releasing the extension to the marketplace. |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Apply security hygiene to CI configs for known-exploitable attack vectors.
Why
As a user of this extension I'm concerned about supply-chain vulnerability. I looked at the repo's CI especially, because of the recent GitHub Actions-based attacks:
reviewdog/action-setupcompromise (CVE-2025-30154). Downstream CI workflows pinned to a commit SHA were unaffected. (CVE-2025-30066) NVD · StepSecurity analysis · CISA alertaquasecurity/trivy-actionand all 7 tags inaquasecurity/setup-trivy, stealing CI secrets from every workflow that referenced them by tag (CVE-2026-33634). Legit Security writeup · CSA research notepull_request_targetworkflows to exfiltrate secrets and cloud credentials. CSA research noteI found that all workflows in this repo currently reference Actions by mutable tags (
@v4,@v2, …). A tag can be moved by the Action's maintainer — or by anyone who compromises their account — and every workflow using it silently runs the new code on its next trigger. Here that matters most forrelease.yml, which runs withcontents: writeand publishes the.vsixusers install, anddocs.yml's deploy job (pages: write,id-token: write).Changes
@<sha> # v4.4.0).main.yml: addpermissions: contents: read. It previously inherited the repo default token permissions (read/write on older repos), though the job never writes anything.release.yml:persist-credentials: falseon checkout. Otherwise thecontents: writetoken sits in.git/configwhilenpm installruns package lifecycle scripts. Nothing later needs it: the tag fetch is anonymous on a public repo, andaction-gh-releasereceives the token via its own input.release.yml: quote"${GITHUB_REF_NAME#v}"to clear a shellcheck SC2086 noticerelease.yml-- this commit can be dropped if you don't want it.v4/v7.0.111d5960…v4.4.0 /3d3c42e…v7.0.1v27/31.11.3ba0dd84…v27 /75c75c3…v31.11.3v5983d773…v5.0.0v356afc60…v3.0.1v4d6db901…v4.0.5v23bb1273…v2.6.2Keeping pins current (suggestion, not included in this PR)
Pins don't auto-update, so security fixes upstream won't arrive on their own.
Dependabot handles SHA-pinned Actions natively; when upstream moves it submits a reviewable PR that bumps the SHA and the
# vX.Y.Zcomment. A minimal.github/dependabot.yml:Not addressed here
actions/upload-pages-artifactinternally usesactions/upload-artifact@v4by tag (outside this repo's control)../dousesnpm installrather thannpm ci; switching would further reduce build-time supply-chain exposure.v*, since pushing one triggers a write-scoped release.