Skip to content

fix(ci): harden bundle-size PR workflow trust boundaries#6773

Merged
Sheraff merged 4 commits intomainfrom
chore/bundle-size-pr-target
Feb 27, 2026
Merged

fix(ci): harden bundle-size PR workflow trust boundaries#6773
Sheraff merged 4 commits intomainfrom
chore/bundle-size-pr-target

Conversation

@Sheraff
Copy link
Contributor

@Sheraff Sheraff commented Feb 26, 2026

Summary

  • switch the bundle-size PR trigger from pull_request to pull_request_target and split execution into untrusted benchmark + trusted comment jobs
  • restrict default workflow permissions to read-only and scope pull-requests: write only to the comment job
  • pass benchmark outputs through job outputs so PR metadata is handled in the trusted job context

Summary by CodeRabbit

  • Chores
    • Tightened CI workflow permissions and changed triggers to safer event handling.
    • Added capture/restore steps so benchmark current/base results persist reliably across jobs.
    • Introduced a dedicated job to post benchmark results to pull requests and reworked job coordination for stable PR comments.
    • Improved checkout and credential handling to reduce CI flakiness.

@nx-cloud
Copy link

nx-cloud bot commented Feb 26, 2026

View your CI Pipeline Execution ↗ for commit f88be98

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded <1s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 3s View ↗

☁️ Nx Cloud last updated this comment at 2026-02-27 19:04:35 UTC

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0cc5b3e and f88be98.

📒 Files selected for processing (1)
  • .github/workflows/bundle-size.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/bundle-size.yml

📝 Walkthrough

Walkthrough

Trigger changed from pull_request to pull_request_target; top-level permissions set to contents: read; benchmark-pr now captures and exposes current_json_b64, base_sha, pr_number; new capture/comment-pr jobs restore the JSON and post PR comments; benchmark-main gets explicit contents: write permission.

Changes

Cohort / File(s) Summary
Workflow file
.github/workflows/bundle-size.yml
Trigger switched to pull_request_target; top-level permissions reduced to contents: read; benchmark-pr updated to fetch PR merge ref, disable persisting credentials, produce outputs (current_json_b64, base_sha, pr_number) and upload artifact; added capture (downloads artifact, exposes outputs) and comment-pr (restores current.json from base64 and upserts PR comment); benchmark-main granted explicit contents: write permission.

Sequence Diagram(s)

sequenceDiagram
  participant PR as PR/Event
  participant Runner as Actions Runner
  participant Benchmark as benchmark-pr
  participant Capture as capture
  participant Comment as comment-pr
  participant GH as GitHub API

  PR->>Runner: trigger (pull_request_target)
  Runner->>Benchmark: run benchmark-pr (checkout PR merge ref, run benchmarks)
  Benchmark->>Runner: upload artifact (current.json) and set outputs (current_json_b64, base_sha, pr_number)
  Runner->>Capture: run capture (download artifact, emit outputs)
  Capture->>Comment: pass outputs (current_json_b64, base_sha, pr_number)
  Comment->>Runner: restore current.json from base64
  Comment->>GH: upsert/update PR comment using pr_number
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇 I hopped through actions, light and fleet,

I bundled outputs for a tidy treat,
From benchmark burrow I carried the prize,
Restored the JSON under moonlit skies,
A rabbit's comment now greets the PR — neat! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately describes the main security improvement—hardening GitHub Actions workflow trust boundaries by switching to pull_request_target and splitting untrusted and trusted jobs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/bundle-size-pr-target

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

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 26, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@6773

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@6773

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@6773

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@6773

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@6773

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@6773

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@6773

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@6773

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@6773

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@6773

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@6773

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@6773

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@6773

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@6773

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@6773

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@6773

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@6773

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@6773

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@6773

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@6773

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@6773

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@6773

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@6773

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@6773

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@6773

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@6773

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@6773

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@6773

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@6773

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@6773

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@6773

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@6773

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@6773

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@6773

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@6773

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@6773

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@6773

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@6773

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@6773

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@6773

commit: 854883e

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
.github/workflows/bundle-size.yml (1)

20-23: Consider adding a timeout to the untrusted benchmark job.

A job-level timeout helps cap resource abuse and runaway builds when executing PR code.

⏱️ Suggested tweak
   benchmark-pr:
     name: Benchmark PR
     if: github.event_name == 'pull_request_target'
     runs-on: ubuntu-latest
+    timeout-minutes: 20
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/bundle-size.yml around lines 20 - 23, Add a job-level
timeout to the benchmark-pr GitHub Actions job to prevent runaway or abusive PR
builds: update the benchmark-pr job definition (identifier "benchmark-pr") to
include a timeout-minutes field with an appropriate value (e.g., 30 or 60) so
the workflow runner will cancel the job after that duration.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/bundle-size.yml:
- Around line 47-49: The delimiter echo can be concatenated to the JSON if the
file lacks a trailing newline; change the three-line block that uses DELIM so
the delimiter is always on its own line by printing it with an explicit newline
(e.g. use printf '%s\n' "$DELIM" or echo -e "\n$DELIM" where appropriate) and
ensure you emit a newline after cat e2e/bundle-size/results/current.json before
printing the delimiter; update the statements referencing DELIM (the echo
"current_json<<$DELIM", the cat of current.json, and the final echo "$DELIM")
accordingly.

---

Nitpick comments:
In @.github/workflows/bundle-size.yml:
- Around line 20-23: Add a job-level timeout to the benchmark-pr GitHub Actions
job to prevent runaway or abusive PR builds: update the benchmark-pr job
definition (identifier "benchmark-pr") to include a timeout-minutes field with
an appropriate value (e.g., 30 or 60) so the workflow runner will cancel the job
after that duration.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4027b6 and 21e8125.

📒 Files selected for processing (1)
  • .github/workflows/bundle-size.yml

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/bundle-size.yml:
- Around line 24-27: The workflow is exposing PR metadata (outputs
current_json_b64, base_sha, pr_number) from an untrusted path (benchmark-pr)
which creates a confused-deputy risk; ensure these values are produced and
consumed only by a trusted job (e.g., capture) and not passed through
benchmark-pr. Modify the job wiring so that capture declares and sets outputs
current_json_b64, base_sha, pr_number, and have comment-pr consume
capture.outputs.* directly (and depend on capture), removing any assignments or
passthrough of those keys from benchmark-pr; also audit other occurrences (lines
referenced around 47-49, 91-92, 99-100) to replace untrusted job outputs with
the trusted capture job outputs.
- Around line 66-72: The restore step currently writes an empty
e2e/bundle-size/results/current.json when process.env.CURRENT_JSON_B64 is
missing; change the inline Node script that uses process.env.CURRENT_JSON_B64 to
check for a non-empty value (CURRENT_JSON_B64) and, if absent, print a clear
error and exit non‑zero (e.g., throw or call process.exit(1)) so the workflow
fails fast instead of creating an empty file; update the node -e invocation that
writes current.json to perform this guard and error out when the payload is
missing.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21e8125 and 7df7372.

📒 Files selected for processing (1)
  • .github/workflows/bundle-size.yml

Comment on lines +66 to +72
- name: Restore Benchmark Outputs
env:
CURRENT_JSON_B64: ${{ needs.benchmark-pr.outputs.current_json_b64 }}
run: |
mkdir -p e2e/bundle-size/results
node -e "const fs=require('node:fs'); fs.writeFileSync('e2e/bundle-size/results/current.json', Buffer.from(process.env.CURRENT_JSON_B64 || '', 'base64'))"

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fail fast when benchmark payload is missing.

Current restore logic silently writes an empty current.json when output propagation fails, which makes later failures harder to diagnose.

🧪 Suggested guardrail
       - name: Restore Benchmark Outputs
         env:
           CURRENT_JSON_B64: ${{ needs.benchmark-pr.outputs.current_json_b64 }}
         run: |
           mkdir -p e2e/bundle-size/results
-          node -e "const fs=require('node:fs'); fs.writeFileSync('e2e/bundle-size/results/current.json', Buffer.from(process.env.CURRENT_JSON_B64 || '', 'base64'))"
+          if [ -z "${CURRENT_JSON_B64:-}" ]; then
+            echo "Missing benchmark payload from benchmark-pr outputs." >&2
+            exit 1
+          fi
+          printf '%s' "$CURRENT_JSON_B64" | base64 -d > e2e/bundle-size/results/current.json
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Restore Benchmark Outputs
env:
CURRENT_JSON_B64: ${{ needs.benchmark-pr.outputs.current_json_b64 }}
run: |
mkdir -p e2e/bundle-size/results
node -e "const fs=require('node:fs'); fs.writeFileSync('e2e/bundle-size/results/current.json', Buffer.from(process.env.CURRENT_JSON_B64 || '', 'base64'))"
- name: Restore Benchmark Outputs
env:
CURRENT_JSON_B64: ${{ needs.benchmark-pr.outputs.current_json_b64 }}
run: |
mkdir -p e2e/bundle-size/results
if [ -z "${CURRENT_JSON_B64:-}" ]; then
echo "Missing benchmark payload from benchmark-pr outputs." >&2
exit 1
fi
printf '%s' "$CURRENT_JSON_B64" | base64 -d > e2e/bundle-size/results/current.json
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/bundle-size.yml around lines 66 - 72, The restore step
currently writes an empty e2e/bundle-size/results/current.json when
process.env.CURRENT_JSON_B64 is missing; change the inline Node script that uses
process.env.CURRENT_JSON_B64 to check for a non-empty value (CURRENT_JSON_B64)
and, if absent, print a clear error and exit non‑zero (e.g., throw or call
process.exit(1)) so the workflow fails fast instead of creating an empty file;
update the node -e invocation that writes current.json to perform this guard and
error out when the payload is missing.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
.github/workflows/bundle-size.yml (1)

87-93: ⚠️ Potential issue | 🟡 Minor

Add a guard to fail fast when benchmark payload is missing.

The current implementation silently writes an empty current.json when CURRENT_JSON_B64 is missing or empty, making downstream failures harder to diagnose.

,

🛡️ Suggested guardrail
       - name: Restore Benchmark Outputs
         env:
           CURRENT_JSON_B64: ${{ needs.capture.outputs.current_json_b64 }}
         run: |
           mkdir -p e2e/bundle-size/results
-          node -e "const fs=require('node:fs'); fs.writeFileSync('e2e/bundle-size/results/current.json', Buffer.from(process.env.CURRENT_JSON_B64 || '', 'base64'))"
+          if [ -z "${CURRENT_JSON_B64:-}" ]; then
+            echo "::error::Missing benchmark payload from capture outputs."
+            exit 1
+          fi
+          printf '%s' "$CURRENT_JSON_B64" | base64 -d > e2e/bundle-size/results/current.json
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/bundle-size.yml around lines 87 - 93, The workflow
currently decodes process.env.CURRENT_JSON_B64 and writes an empty
e2e/bundle-size/results/current.json when the env var is missing; update the run
step's node -e script to check process.env.CURRENT_JSON_B64 first and if it's
undefined or empty, print a clear error (including the variable name
CURRENT_JSON_B64) to stderr and exit with a non-zero status so the job fails
fast instead of writing an empty file; otherwise decode and write the file as
before.
🧹 Nitpick comments (1)
.github/workflows/bundle-size.yml (1)

96-96: Redundant directory creation.

Line 91 already creates e2e/bundle-size/results, so this mkdir -p is unnecessary.

♻️ Suggested removal
       - name: Read Historical Data (if available)
         run: |
-          mkdir -p e2e/bundle-size/results
           if git fetch --depth=1 origin gh-pages; then
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/bundle-size.yml at line 96, Remove the redundant directory
creation command "mkdir -p e2e/bundle-size/results" from the workflow; since the
same directory is already created earlier in the bundle-size job (the earlier
"mkdir -p e2e/bundle-size/results" invocation), delete the duplicate line in
.github/workflows/bundle-size.yml to avoid unnecessary repetition.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/bundle-size.yml:
- Line 27: The workflow uses an outdated checkout action version string
"actions/checkout@v6.0.1"; update that reference to the latest patch by
replacing it with "actions/checkout@v6.0.2" (or change to the major pin
"actions/checkout@v6" if you prefer major-version pinning) so the bundle-size
GitHub Action uses the recommended latest patch release.

---

Duplicate comments:
In @.github/workflows/bundle-size.yml:
- Around line 87-93: The workflow currently decodes process.env.CURRENT_JSON_B64
and writes an empty e2e/bundle-size/results/current.json when the env var is
missing; update the run step's node -e script to check
process.env.CURRENT_JSON_B64 first and if it's undefined or empty, print a clear
error (including the variable name CURRENT_JSON_B64) to stderr and exit with a
non-zero status so the job fails fast instead of writing an empty file;
otherwise decode and write the file as before.

---

Nitpick comments:
In @.github/workflows/bundle-size.yml:
- Line 96: Remove the redundant directory creation command "mkdir -p
e2e/bundle-size/results" from the workflow; since the same directory is already
created earlier in the bundle-size job (the earlier "mkdir -p
e2e/bundle-size/results" invocation), delete the duplicate line in
.github/workflows/bundle-size.yml to avoid unnecessary repetition.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7df7372 and 0cc5b3e.

📒 Files selected for processing (1)
  • .github/workflows/bundle-size.yml

@Sheraff Sheraff merged commit d958ef1 into main Feb 27, 2026
5 checks passed
@Sheraff Sheraff deleted the chore/bundle-size-pr-target branch February 27, 2026 19:12
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