Skip to content

Harden CI: bind desktop-artifacts' version into env, not into the script - #5913

Merged
vivekchand merged 1 commit into
mainfrom
harden/desktop-artifacts-env-binding
Sep 12, 2026
Merged

Harden CI: bind desktop-artifacts' version into env, not into the script#5913
vivekchand merged 1 commit into
mainfrom
harden/desktop-artifacts-env-binding

Conversation

@vivekchand

Copy link
Copy Markdown
Owner

What

The four packaging steps in desktop-artifacts.yml each read the resolved version by expanding ${{ steps.ver.outputs.version }} directly inside a run: block. That substitution is textual — the runner splices the value into the script body before pwsh or bash ever parses it, so the value is evaluated as code rather than read as data.

Each of those sites is one zizmor template-injection finding, and this workflow held nine of them, the largest single concentration in the repository (24 across all workflows; release-on-merge.yml has the other 10 and is left for its own PR).

The fix

Each step now receives the value as a step-level env: var and reads it back as a variable — $Env:APP_VERSION in pwsh, "$APP_VERSION" in bash. This is the same env-binding fix already applied to auto-deploy-cloud (#5673), verify-published-wheel (#5744) and the smoke-test matrix (#5363).

The interpolation disappears from the script text. The packaging logic, the artifact names and the fixed-name stable-URL copies (ClawMetry-windows.zip, ClawMetry-windows-setup.exe, clawmetry-linux.tar.gz) are unchanged.

Steps touched, all four in the Windows and Linux build jobs:

Step Shell Sites
Zip distribution folder pwsh 1
Build NSIS installer pwsh 5
Tarball distribution folder bash 1
Build AppImage bash 1

In the NSIS step the value is bound once into $version and the three later uses read that, collapsing five separate expansions into one binding. Where a literal . follows the reference (${version}.0, ClawMetry-Setup-${version}.exe) it is written braced, so there is no PowerShell member-access ambiguity for a reader to have to reason about.

Permissions are untouched

This workflow is one of the six that legitimately need write scopes: its release job elevates to contents: write so action-gh-release can attach installers to the tag's Release. That elevation, and the least-privilege contents: read top level it sits under, are exactly as they were — this PR adds no permissions: line and removes none. git diff over the file contains no permissions change.

Verification

  • Every workflow file parses: python3 -c "import yaml,glob; [yaml.safe_load(open(f)) for f in glob.glob('.github/workflows/*.yml')]"
  • Repository workflow guards pass: tests/test_workflow_yaml_valid.py + tests/test_ci_workflow_invocations_are_real.py594 passed, 369 skipped
  • No ${{ }} expansion remains inside any run: block in this file (re-scanned after the edit; count went 9 → 0)

Scope

One concern, one workflow file, 25 insertions / 7 deletions. Under .github/, so exempt from the product-record gate.

No-PRD: CI-only change under .github/, no product code touched.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGkmcn95GPNqMaK6dAnVNw


Generated by Claude Code

The four packaging steps in desktop-artifacts.yml each read the resolved
version by expanding `${{ steps.ver.outputs.version }}` directly inside a
`run:` block. That substitution is textual: the runner splices the value
into the script body before pwsh or bash ever parses it, so the value is
evaluated as code rather than read as data. Every such site is one zizmor
`template-injection` finding, and this workflow held nine of them -- the
largest single concentration in the repository.

Each step now receives the value as a step-level `env:` var and reads it
back as a variable ($Env:APP_VERSION in pwsh, "$APP_VERSION" in bash),
which is the same env-binding fix already applied to auto-deploy-cloud
(#5673), verify-published-wheel (#5744) and the smoke-test matrix (#5363).
The interpolation disappears from the script text; the packaging logic,
the artifact names and the fixed-name stable-URL copies are unchanged.

In the NSIS step the value is bound once into `$version` and the three
later uses read that, replacing five separate expansions. PowerShell
member-access ambiguity is avoided by writing `${version}` where a literal
`.` follows the reference.

Scope note: `permissions:` is untouched. This workflow is one of the six
that need write scopes -- its `release` job elevates to `contents: write`
to attach installers to the tag's Release -- and that elevation, plus the
least-privilege `contents: read` top level, is exactly as it was.

Verified: every workflow file still parses, and the repository's workflow
guards pass (tests/test_workflow_yaml_valid.py +
tests/test_ci_workflow_invocations_are_real.py: 594 passed, 369 skipped).
No `${{ }}` expansion remains inside any `run:` block in this file.

No-PRD: CI-only change under .github/, no product code touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGkmcn95GPNqMaK6dAnVNw
@8090-software-factory

Copy link
Copy Markdown

✅ Drift Bot (ClawMetry): no drift detected

Drift Bot analyzed the changed files against this project's blueprints and requirements and found no drift.

@vivekchand
vivekchand merged commit eba6c06 into main Sep 12, 2026
39 checks passed
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.

2 participants