Skip to content

Bind workflow expressions and drop persisted credentials - #6258

Merged
ChrisJBurns merged 2 commits into
mainfrom
harden-ci-expressions
Aug 10, 2026
Merged

Bind workflow expressions and drop persisted credentials#6258
ChrisJBurns merged 2 commits into
mainfrom
harden-ci-expressions

Conversation

@ChrisJBurns

@ChrisJBurns ChrisJBurns commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • ${{ }} is substituted into a run: block before the shell parses it, so the value lands as command text rather than as an argument. Binding to env: and referencing "$VAR" makes the shell treat it as data whatever it contains. This removes 16 such interpolations.
  • Four of these workflows also persisted the checkout token in .git/config for the whole job. All four declare contents: read, so no job in them can push — the credential is unusable as well as unnecessary. That's 8 checkouts.

Most of this is deleting redundancy rather than adding plumbing:

File Change
image-build-and-publish.yml Four steps already bound VERSION: ${{ steps.version-string.outputs.tag }} in env: and then re-interpolated the same expression in the script. Those now use "$VERSION"; the five steps that lacked the binding get it.
test-e2e-lifecycle.yml ${{ env.YARDSTICK_IMAGE }} and ${{ env.VMCP_IMAGE }} were already shell variables — one from a workflow-level env:, one written to $GITHUB_ENV by an earlier step. The interpolation was redundant; now $YARDSTICK_IMAGE / ${VMCP_IMAGE}.
release-notes.yml steps.tag.outputs.tag and steps.verify.outputs.file bound as TAG / FILE, matching the pattern the "Post notes as a comment" step in the same file already used.
skills-build-and-publish.yml persist-credentials: false.
issue-triage.yml persist-credentials: false.
security-scan.yml security-events: write sat at the workflow level, granting it to govulncheck, which never uploads SARIF. Moved to the two jobs that do.

Expressions in with: blocks are untouched — those aren't shell input and aren't what this is about.

Totals against main: template-injection 45 → 29, artipacked 17 → 9, excessive-permissions 2 → 1.

Rebased onto main now that #6251, #6254 and #6255 have merged.

Part of #6253

Type of change

  • Bug fix
  • New feature
  • Refactoring (no behavior change)
  • Dependency update
  • Documentation
  • Other (describe): CI configuration

Test plan

  • Unit tests (task test)

  • E2E tests (task test-e2e)

  • Linting (task lint-fix)

  • Manual testing (describe below)

  • Confirmed via each workflow's permissions: that all four declare contents: read, so persist-credentials: false cannot break a push — none is possible.

  • All workflows still parse as YAML; actionlint reports 12 findings before and after, all pre-existing.

  • zizmor template-injection 45 → 29, artipacked 17 → 9 and excessive-permissions 2 → 1, and no run: block in any of the four files contains an expression any more.

  • skills-build-and-publish.yml and test-e2e-lifecycle.yml run on pull requests, so this PR exercises them directly.

Does this introduce a user-facing change?

No.

Special notes for reviewers

  • image-build-and-publish.yml and release-notes.yml are not exercised by this PR — the first runs on push to main and on release, the second on workflow_run. Both surface within one merge to main rather than at release time, which is why they're grouped here rather than with the release workflows. Worth a careful read of the VERSION/TAG substitutions in particular.
  • skills-build-and-publish.yml was excluded from Stop persisting git credentials in CI workflow checkouts #6255 as "publish path". That was wrong — it is called from run-on-pr.yml and runs on every PR, so it belongs with the CI batch. Corrected here.
  • Nothing in helm-publish.yml, create-release-tag.yml, create-release-pr.yml or releaser.yml is touched. Those remain deferred per zizmor findings backlog: pins, expressions and checkout credentials #6253 — including the two remaining High-severity template-injection findings, which all live there.

Generated with Claude Code

@ChrisJBurns
ChrisJBurns requested a review from JAORMX as a code owner August 10, 2026 16:47
@github-actions github-actions Bot added the size/S Small PR: 100-299 lines changed label Aug 10, 2026
Base automatically changed from harden-checkout-credentials to main August 10, 2026 16:55
@ChrisJBurns
ChrisJBurns force-pushed the harden-ci-expressions branch from d67336d to 94c7894 Compare August 10, 2026 16:56
@github-actions github-actions Bot added size/XS Extra small PR: < 100 lines changed and removed size/S Small PR: 100-299 lines changed labels Aug 10, 2026
ChrisJBurns and others added 2 commits August 10, 2026 18:06
`${{ }}` is substituted into a run: block before the shell parses it,
so the value becomes part of the command text rather than an argument
to it. Binding to env: and referencing "$VAR" makes the shell treat it
as data regardless of content.

image-build-and-publish already bound VERSION in env: on four of its
steps and then re-interpolated the same expression in the script; those
now use "$VERSION". test-e2e-lifecycle was interpolating two values that
were already shell variables, one from a workflow-level env: and one
written to $GITHUB_ENV, so the expressions were redundant.

Also stops these three workflows persisting the token in .git/config.
All of them declare contents: read, so no job in them can push, which
makes the credential unusable as well as unnecessary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
security-events: write sat at the workflow level in security-scan.yml,
granting it to govulncheck, which never uploads SARIF. It moves to the
two jobs that do. run-on-main.yml still grants the workflow the same
permission when it calls it, so nothing is lost.

issue-triage.yml declares contents: read, so no job in it can push and
the checkout token is unusable as well as unnecessary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.71%. Comparing base (b20cb9f) to head (7b1339b).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6258      +/-   ##
==========================================
+ Coverage   72.70%   72.71%   +0.01%     
==========================================
  Files         742      742              
  Lines       77299    77299              
==========================================
+ Hits        56201    56211      +10     
+ Misses      17132    17126       -6     
+ Partials     3966     3962       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ChrisJBurns
ChrisJBurns force-pushed the harden-ci-expressions branch from 94c7894 to 7b1339b Compare August 10, 2026 17:06
@github-actions github-actions Bot added size/XS Extra small PR: < 100 lines changed and removed size/XS Extra small PR: < 100 lines changed labels Aug 10, 2026
@ChrisJBurns
ChrisJBurns merged commit 4d77048 into main Aug 10, 2026
49 checks passed
@ChrisJBurns
ChrisJBurns deleted the harden-ci-expressions branch August 10, 2026 17:26
@github-actions github-actions Bot mentioned this pull request Aug 14, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XS Extra small PR: < 100 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants