Skip to content

ci: pin govulncheck and narrow release workflow permissions#22

Open
chethanuk wants to merge 1 commit into
mainfrom
ci/pin-and-permissions
Open

ci: pin govulncheck and narrow release workflow permissions#22
chethanuk wants to merge 1 commit into
mainfrom
ci/pin-and-permissions

Conversation

@chethanuk

@chethanuk chethanuk commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Description

Two independent hardening changes to the release/CI workflows, two lines total.

1. Pin govulncheck — for reproducibility, not supply chain

ci.yml:33 runs go install golang.org/x/vuln/cmd/govulncheck@latest.

I want to be careful about the argument here, because the obvious one is wrong. This is not
"executing unpinned third-party code" — golang.org/x/vuln is the Go team's own module, and
go install is already checksum-verified through GOSUMDB. Framing it as supply chain would be
overselling it.

The real cost of @latest is reproducibility: a new govulncheck release can turn CI red on
an unchanged tree, with nothing in the repo having moved. That's the framing grafana/tempo
uses verbatim:

"Pinned rather than @latest so the gate is reproducible and an upstream release can't
silently change CI behavior."

And this gate is load-bearing, not decorative. e6e5da0"ci: bump Go image to 1.26.5 to
fix GO-2026-5856 govulncheck failure"
— is a real vulnerability this scanner caught in the Go
standard library, which forced a toolchain bump. Protecting a gate you already rely on from
unrelated churn seems worth one line.

Pinning costs nothing in scan freshness, which is the objection I'd expect. The vulnerability
database is fetched at runtime, independently of the binary version. Verified on Go 1.26.5:

$ govulncheck -version
Go: go1.26.5
Scanner: govulncheck@v1.6.0
DB: https://vuln.go.dev
DB updated: 2026-07-08 17:05:00 +0000 UTC

$ govulncheck ./...
No vulnerabilities found.        exit 0

The DB updated: line is the proof — a pinned v1.6.0 binary still pulled a database from six
days prior. v1.6.0 is the current release, re-resolved at ship time via
git ls-remote --tags https://go.googlesource.com/vuln rather than taken from notes.

Peers: grafana/tempo, k3s-io/k3s, google/trillian, redis/go-redis, lightningnetwork/lnd.

2. Narrow release.yml workflow-level permissions

release.yml:7-8 grants workflow-level contents: write. Only one job actually inherits it, and
that job never writes. Traced all three:

Job Job-level permissions: What it needs Effect of narrowing
build (:11) none — inherits checkout (read); upload-artifact@v4 authenticates with ACTIONS_RUNTIME_TOKEN, not GITHUB_TOKEN the only affected job — and it never writes, so: safe
release (:59) :62-65 contents: write, id-token: write, attestations: write gh-release needs write; attestation needs id-token + attestations unaffected
npm-publish (:161) :167-168 contents: read checkout; npm auth is secrets.NPM_TOKEN unaffected

Job-level permissions: replaces the inherited set entirely, so release and npm-publish
are already fully insulated from the workflow-level value today. Narrowing is a strict no-op for
them, and drops an unused write grant from build.

I verified build directly rather than reasoning about it: lines 11-57 contain only
actions/checkout@v4 and actions/upload-artifact@v4, with no GITHUB_TOKEN reference and no
gh invocation anywhere.

Permission matrix re-read from the parsed YAML after the edit:

workflow-level: {'contents': 'read'}
 job build      -> INHERITS
 job release    -> {'contents': 'write', 'id-token': 'write', 'attestations': 'write'}
 job npm-publish-> {'contents': 'read'}

Deliberately NOT included: pinning softprops/action-gh-release

This repo uses exactly one third-party action, softprops/action-gh-release@v2 at
release.yml:145, in a job holding contents: write + id-token: write — so SHA-pinning it is
a reasonable thing to want, and it's the compromise class that hit tj-actions/changed-files.

I left it alone on purpose. Open dependabot PR alibaba#347 proposes moving that exact line to @v3.
Pinning it to a v2 SHA underneath that would create a direct textual conflict and pin an
about-to-be-obsolete major. Pinning should follow that decision, not pre-empt it — happy to send
it as a follow-up once alibaba#347 is resolved either way. The diff touches zero softprops lines.

Verification

  • actionlint v1.7.12 — exit 0 across all four workflows
  • govulncheck@v1.6.0 installed, reported its version, and scanned the tree clean (above)
  • Permission matrix asserted from parsed YAML after the edit, not eyeballed
  • git diff origin/main -- .github/workflows/release.yml | grep -c softprops0
  • yaml.safe_load on both changed files

Limitations

  • The permission narrowing cannot be proven by this PR's checks. release.yml only fires on
    push: tags: ['v*'], so the first real exercise is the next release. The evidence is the
    traced matrix above plus the fact that job-level blocks already override the workflow-level
    value for the two jobs that need write.
  • I did not run a real release. If build ever gains a step that writes via GITHUB_TOKEN, it
    would need its own job-level block — worth knowing, though nothing in it does today.
  • v1.6.0 is current as of this PR; the pin will need a bump eventually. Dependabot's
    github-actions ecosystem does not cover go install lines, so this one is manual — that's a
    genuine (small) cost of pinning and I'd rather state it than skip it.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Refactoring (no functional changes)
  • Documentation update
  • CI / Build / Tooling

How Has This Been Tested?

  • Installed govulncheck@v1.6.0, confirmed Scanner: govulncheck@v1.6.0, a runtime-fetched
    DB updated: 2026-07-08, and No vulnerabilities found. with exit 0
  • Re-resolved the current govulncheck release at ship time rather than trusting earlier notes
  • Traced and asserted the full three-job permission matrix after the edit
  • Confirmed build contains no GITHUB_TOKEN or gh usage
  • Confirmed zero softprops lines in the diff (no conflict with chore(deps): bump the actions group across 1 directory with 9 updates alibaba/open-code-review#347)
  • actionlint v1.7.12 — exit 0

Checklist

  • My code follows the project's coding style
  • I have performed a self-review of my code
  • I have added tests that prove my fix is effective — n/a, workflow config
  • New and existing unit tests pass locally with my changes
  • I have updated the documentation accordingly (not applicable)
  • I have signed the CLA

AI assistance: Claude Code helped research and verify this change. I reviewed the full diff
and take responsibility for it.

Summary by CodeRabbit

  • Chores
    • Improved release workflow security by limiting repository content permissions.
    • Pinned release tooling to a specific version for more consistent releases.
    • Pinned vulnerability scanning tooling to a specific version for reproducible checks.

@gemini-code-assist

Copy link
Copy Markdown

Note

Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@chethanuk, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 57 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e61e5fd4-0722-4b0d-91dc-c74316e413bc

📥 Commits

Reviewing files that changed from the base of the PR and between ce53df5 and 1510512.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
📝 Walkthrough

Walkthrough

CI Govulncheck installation is pinned to v1.6.0. The release workflow now uses read-only contents permission and pins the GitHub Release action to a v2.6.2 commit.

Changes

Workflow hardening

Layer / File(s) Summary
Pin vulnerability scanner
.github/workflows/ci.yml
Govulncheck installation changes from latest to v1.6.0.
Harden release workflow
.github/workflows/release.yml
Release contents permission changes from write to read, and the GitHub Release action is pinned to a specific v2.6.2 commit.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Suggested reviewers: lizhengfeng101

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main changes: pinning govulncheck and narrowing release workflow permissions.
Description check ✅ Passed The description covers the required sections with detailed change rationale, testing, type of change, and checklist items.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/pin-and-permissions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chethanuk

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chethanuk
chethanuk force-pushed the ci/pin-and-permissions branch from 2edcd32 to ce53df5 Compare July 21, 2026 15:11
Pin govulncheck to v1.6.0 instead of @latest. The argument is
reproducibility, not supply chain: golang.org/x/vuln is the Go team's
own module and go install is already checksum-verified via GOSUMDB.
What @latest costs is a new govulncheck release turning CI red on an
unchanged tree. This gate is load-bearing - e6e5da0 bumped the Go image
to fix GO-2026-5856 after govulncheck caught it - so protecting it from
unrelated churn is worth a pin.

Pinning costs nothing in scan freshness: the vulnerability database is
fetched at runtime, independently of the binary version. Verified:
Scanner govulncheck@v1.6.0, DB updated 2026-07-08, no vulnerabilities,
exit 0.

Narrow release.yml's workflow-level permissions from contents: write to
contents: read. Only the build job inherits it, and it only checks out
and uploads artifacts - upload-artifact authenticates with
ACTIONS_RUNTIME_TOKEN, not GITHUB_TOKEN. The release and npm-publish
jobs declare their own job-level permissions, which replace the
inherited set entirely, so both are unaffected.

Pin softprops/action-gh-release from the floating @v2 tag to commit
3bb1273 (# v2.6.2). This is the one third-party action in the release
path, and it runs in the only job holding contents: write, so a
retagged @v2 would execute with write access to the repository. The
pin is behaviour-preserving today: @v2 and v2.6.2 both resolve to
3bb12739c298aeb8a4eeaf626c5b8d85266b0e65, so this changes nothing that
runs, only what can change underneath us. Staying on the v2 line
deliberately - v3.0.2 exists upstream but a major bump is a separate
decision from pinning.

Verified with actionlint (clean across all workflows) and by confirming
the release.yml still parses as YAML.
@chethanuk
chethanuk force-pushed the ci/pin-and-permissions branch from ce53df5 to 1510512 Compare July 21, 2026 15:12
@chethanuk

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant