Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/auto-label.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
- 'jest.config.*'

- name: Auto assign and label
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { owner, repo } = context.repo;
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/bundle-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,13 @@
echo "status=:white_check_mark:" >> $GITHUB_OUTPUT
else
echo "status=:heavy_check_mark:" >> $GITHUB_OUTPUT
fi

- name: Comment on PR
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { owner, repo } = context.repo;

Check notice on line 124 in .github/workflows/bundle-analysis.yml

View check run for this annotation

Claude / Claude Code Review

Script injection via backtick template literal in bundle-analysis.yml

A pre-existing script injection vulnerability exists in bundle-analysis.yml: the bundleBreakdown variable is constructed using a backtick template literal with a direct GitHub Actions expression (const bundleBreakdown = backtick-dollar-brace steps.compare.outputs.bundle_breakdown end-brace-backtick), so GitHub Actions interpolates the raw value into JS source before parsing — a malicious PR could craft dist/ filenames with backticks (SyntaxError) or dollar-brace sequences (arbitrary JS execution
Comment on lines 118 to 124
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟣 A pre-existing script injection vulnerability exists in bundle-analysis.yml: the bundleBreakdown variable is constructed using a backtick template literal with a direct GitHub Actions expression (const bundleBreakdown = backtick-dollar-brace steps.compare.outputs.bundle_breakdown end-brace-backtick), so GitHub Actions interpolates the raw value into JS source before parsing — a malicious PR could craft dist/ filenames with backticks (SyntaxError) or dollar-brace sequences (arbitrary JS execution). The fix is to pass the value via an env: block and read it as process.env.BUNDLE_BREAKDOWN.

Extended reasoning...

What the bug is and how it manifests

In .github/workflows/bundle-analysis.yml (around line 130), the bundleBreakdown variable is assigned via a backtick template literal with a raw GitHub Actions expression:

const bundleBreakdown = ${{ steps.compare.outputs.bundle_breakdown }};

GitHub Actions evaluates ${{ ... }} expressions BEFORE the script engine parses the JavaScript. The raw string value is substituted directly into the JS source code — whatever bundle_breakdown contains becomes literal characters in the JavaScript source.

The specific code path that triggers it

The value of bundle_breakdown is set in the Compare bundle sizes step using du -sh dist/*. The dist/ directory is built directly from the PR's own source code via pnpm build. On Linux, filenames can contain any character except null and forward-slash, including backticks and dollar-brace sequences.

Why existing code does not prevent it

There is no sanitization of filenames before they are captured by du -sh dist/*, no escaping when writing to GITHUB_OUTPUT, and no sanitization when reading the output into the JavaScript context. The template literal interpolation happens at the GitHub Actions layer, before the script engine ever sees the code.

What the impact would be

The workflow runs on pull_request events and has pull-requests:write permission. A malicious contributor could craft a PR that generates files in dist/ with specially crafted names. A filename containing a backtick terminates the template literal and causes a SyntaxError (denial of service). A filename containing a dollar-brace expression causes that expression to be evaluated as JavaScript, potentially exfiltrating secrets or posting arbitrary content to the PR.

How to fix it

Pass the value via an env: block and access it as an environment variable. Add BUNDLE_BREAKDOWN under env: pointing to the steps output, then read process.env.BUNDLE_BREAKDOWN in the script instead of using a template literal. This keeps the value out of the JS source code entirely.

Step-by-step proof

  1. Attacker submits a PR with a build script that creates a file in dist/ whose name contains an injected JS expression (e.g. "chunk${process.env.GITHUB_TOKEN}.js").
  2. The Install and build PR step runs pnpm build, creating that file in dist/.
  3. The Compare bundle sizes step runs du -sh dist/*, capturing the malicious filename into BUNDLE_BREAKDOWN.
  4. The bundle_breakdown output is set to include that filename.
  5. In the Comment on PR step, GitHub Actions substitutes the expression, embedding the filename literally into the JS source inside a template literal.
  6. The JS engine evaluates the injected expression, executing attacker-controlled code with access to the GITHUB_TOKEN and pull-requests:write permissions.

This bug is pre-existing and was not introduced by the v8-to-v9 upgrade in this PR, but the PR touches this exact step, making it an appropriate opportunity to flag and fix.

const prSize = '${{ steps.compare.outputs.pr_size }}';
const baseSize = '${{ steps.compare.outputs.base_size }}';
const diff = '${{ steps.compare.outputs.diff }}';
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy docs/build --project-name=ralph-starter-docs --branch=${{ github.head_ref }}

# Comment preview URL on the PR

Check failure on line 70 in .github/workflows/deploy-docs.yml

View check run for this annotation

Claude / Claude Code Review

deploy-docs.yml uses mutable version tag instead of SHA pin

deploy-docs.yml pins actions/github-script to the mutable tag @v9.0.0 instead of the full commit SHA used in every other workflow updated by this PR. A mutable tag can be force-pushed by the upstream maintainer to point to arbitrary code, creating a supply chain attack vector. Fix by replacing @v9.0.0 with @3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 deploy-docs.yml pins actions/github-script to the mutable tag @v9.0.0 instead of the full commit SHA used in every other workflow updated by this PR. A mutable tag can be force-pushed by the upstream maintainer to point to arbitrary code, creating a supply chain attack vector. Fix by replacing @v9.0.0 with @3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0.

Extended reasoning...

What the bug is: In .github/workflows/deploy-docs.yml (line 70), this PR updates actions/github-script from @v8 to @v9.0.0 — still a mutable tag reference. All five other workflow files modified in this same PR (auto-label.yml, bundle-analysis.yml, pr-issue-check.yml, prepare-release.yml, release.yml) were correctly updated to pin the full commit SHA: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0.

Why it matters: Version tags like v9.0.0 are mutable Git references. The upstream maintainer (or an attacker who compromises the upstream repo) can force-push the tag to point to a completely different commit at any time. When deploy-docs.yml runs, GitHub Actions resolves the tag at execution time, meaning the workflow could silently run different — potentially malicious — code without any change to this repository. This is a classic supply chain attack vector that SHA pinning is designed to prevent.

Why existing code does not prevent it: GitHub Actions has no built-in enforcement of SHA pinning. The @v9.0.0 reference is accepted without warning. The inconsistency was not caught during this PR's preparation because Dependabot updated five files correctly but missed this one, and the diff looks superficially similar (v8v9.0.0 still represents a version upgrade).

Step-by-step proof:

  1. Before this PR: deploy-docs.yml uses actions/github-script@v8 (mutable tag).
  2. This PR: five other workflows are updated to @3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (immutable SHA).
  3. This PR: deploy-docs.yml is updated only to @v9.0.0 — still a mutable tag, still exploitable.
  4. An attacker who gains write access to actions/github-script upstream (or the maintainer accidentally) force-pushes the v9.0.0 tag to a commit that exfiltrates secrets.CLOUDFLARE_API_TOKEN and secrets.CLOUDFLARE_ACCOUNT_ID (both are passed to this step's context).
  5. The next pull_request event triggers the deploy-docs workflow, and the malicious code runs with full access to those secrets.

How to fix: Replace line 70 in deploy-docs.yml:

# Before (mutable — vulnerable)
uses: actions/github-script@v9.0.0

# After (immutable — safe)
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0

This matches the pattern already used in all other updated workflows in this PR.

- name: Comment Preview URL
if: github.event_name == 'pull_request'
uses: actions/github-script@v8
uses: actions/github-script@v9.0.0
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.

P2 Mutable tag instead of SHA pin

deploy-docs.yml uses @v9.0.0 (a mutable tag) while all five other updated files consistently use a SHA-pinned reference (@3a2844b7e9c422d3c10d287c895573f7108da1b3). A maintainer or attacker who force-pushes the v9.0.0 tag could cause this step to execute arbitrary code; SHA pinning eliminates that risk.

Suggested change
uses: actions/github-script@v9.0.0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/deploy-docs.yml
Line: 73

Comment:
**Mutable tag instead of SHA pin**

`deploy-docs.yml` uses `@v9.0.0` (a mutable tag) while all five other updated files consistently use a SHA-pinned reference (`@3a2844b7e9c422d3c10d287c895573f7108da1b3`). A maintainer or attacker who force-pushes the `v9.0.0` tag could cause this step to execute arbitrary code; SHA pinning eliminates that risk.

```suggestion
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
```

How can I resolve this? If you propose a fix, please make it concise.

with:
script: |
const branch = context.payload.pull_request.head.ref
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-issue-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check PR body for issue references
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const prBody = context.payload.pull_request.body || '';
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ jobs:

- name: Comment on source PR
if: steps.collect.outputs.skip != 'true'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
NEW_VERSION: ${{ steps.version.outputs.new }}
EXISTING_FOUND: ${{ steps.check.outputs.found }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ jobs:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Comment on PR
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { owner, repo } = context.repo;
Expand Down
Loading