Skip to content

Validate fork PR plugins via pull_request_target#100

Draft
andrewmumblebee wants to merge 5 commits into
mainfrom
work/ah/fork-pr-runs
Draft

Validate fork PR plugins via pull_request_target#100
andrewmumblebee wants to merge 5 commits into
mainfrom
work/ah/fork-pr-runs

Conversation

@andrewmumblebee

@andrewmumblebee andrewmumblebee commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

📋 Summary

The plugin PR workflow logged in with --apiKey before validating, but GitHub Actions withholds repository secrets from pull_request runs triggered by a fork PR — so SQUAREDUP_API_KEY resolved to an empty string.

In practice this meant validate never ran for external contributors.

This switches the trigger to pull_request_target, which runs with the base repo's secrets regardless of where the PR originates, and pins checkout to the PR's head commit. Because the workflow file itself is always read from the base branch under pull_request_target, a fork PR editing pr-run.yaml has no effect on what actually runs — but the
checked-out plugin files are still attacker-controlled data, so this also:

  • Routes plugin_paths through an env: var instead of splicing it directly into shell scripts via ${{ }} - a script-injection hole that was harmless without secrets in scope, but wouldn't have been once they were.
  • Moves the CLI install outside the checkout ($RUNNER_TEMP), since npm reads a project .npmrc from the working directory even for global installs, and a fork PR could otherwise redirect the install to a package it controls in the same step the API key is exposed.
  • Gates deploy to same-repo PRs only - validation always determines that a plugin is deployable, we only need to deploy if we want to test ourselves. Happy to change as needed.
  • Logs the installed CLI version, to make future CI failures easier to diagnose.

Transitional: pull_request is still here too

validate-and-deploy is a required status check, and this PR can't satisfy that check against itself through pull_request_target alone — that trigger always resolves the workflow file from main, which doesn't have it until this merges. pull_request is kept temporarily so this PR's own check can actually run; it comes back out in an immediate follow-up PR once this lands, since every PR after that is covered by pull_request_target on main.

While both triggers are active, a same-repo PR touching plugin files would otherwise run deploy twice per push (once per trigger), racing on the same --suffix. deploy is now also restricted to github.event_name == 'pull_request_target' specifically, and a concurrency group (keyed on PR number) serializes runs so a rapid follow-up push can't independently overlap and land out of order either.


🔍 Scope of change

  • Documentation only
  • Repository metadata or configuration
  • CI / automation
  • Other (please describe):

📚 Checklist

Summary by CodeRabbit

  • CI/CD
    • Updated pull request workflow triggers to support safer pull_request_target handling while retaining PR-scoped concurrency.
    • Improved code checkout to use the PR head commit SHA when available.
    • Refined plugin detection/validation flow to use detected plugin paths via an environment variable.
    • Hardened deployment to require modified plugins and to block deployment of fork-submitted plugin code unless the event matches pull_request_target.
    • Kept support for manually triggered runs without posting pull request comments.

The CI workflow logged in with --apiKey before validating, but
GitHub Actions withholds repository secrets from pull_request runs
triggered by a fork PR. The API key resolved to an empty string,
and the CLI silently fell through to the OAuth device flow, hanging
for its full 5-minute timeout before failing instead of erroring
immediately.

Switched the trigger to pull_request_target, which runs with the
base repo's secrets regardless of where the PR originates, and
pinned checkout to the PR's head commit so a later push can't swap
in different code after review. The workflow file itself is always
read from the base branch under pull_request_target, so a fork
editing pr-run.yaml has no effect — but the checked-out plugin
files are still attacker-controlled data. Three spots spliced
${{ steps.detect.outputs.plugin_paths }} directly into shell
scripts, a script-injection hole that was harmless without secrets
but not with them; that data now flows through an env var instead.
The CLI install also moved outside the checkout, since npm reads a
project .npmrc from the working directory even for global installs,
and a fork PR could otherwise redirect the install to a package it
controls in the same step the API key is in scope.

Deploy still only runs for same-repo PRs — validating fork-submitted
plugin code is one thing, but auto-deploying it to the live tenant
needs a maintainer to decide that deliberately. Also logs the
installed CLI version, since not knowing which version had actually
run was part of what made this issue hard to diagnose.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request workflow adds pull_request_target, checks out the PR head commit, passes detected plugin paths through PLUGIN_PATHS, separates CLI setup from validation, and restricts deployment to same-repository pull_request_target runs.

Changes

Plugin workflow

Layer / File(s) Summary
PR trigger and head checkout
.github/workflows/pr-run.yaml
The workflow adds pull_request_target, preserves PR-number concurrency, and checks out the pull request head SHA.
Plugin validation and deployment
.github/workflows/pr-run.yaml
SquaredUp CLI setup is gated on modified plugins, validation and summary steps consume PLUGIN_PATHS, and deployment requires modified plugins from the repository’s own pull request target event.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: validating fork PR plugins with pull_request_target.
Description check ✅ Passed The description is detailed and covers the workflow change, security rationale, scope, and checklist.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@andrewmumblebee
andrewmumblebee marked this pull request as ready for review July 22, 2026 13:38
@andrewmumblebee
andrewmumblebee requested review from a team, clarkd and shaswot77 July 22, 2026 13:38

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/pr-run.yaml:
- Around line 41-44: Update the SquaredUp CLI installation step around the
squaredup version check to install an approved exact `@squaredup/cli` version
instead of resolving the moving latest version, and verify the installed
package’s integrity before proceeding. Keep the existing version output while
ensuring the workflow uses the pinned version consistently.
- Line 4: Add concurrency configuration to the pull_request_target workflow so
runs sharing the same pull request are serialized, using a group keyed by the PR
number and cancelling or queuing previous runs according to the required
deployment behavior. Ensure the existing deployment job reuses this
workflow-level or job-level concurrency setting.
- Line 4: Replace the privileged pull_request_target trigger and checkout flow
with an unprivileged pull_request validation workflow, ensuring the PR head code
cannot access repository secrets. Pin the `@squaredup/cli` version before
retaining or introducing any separate secret-backed deployment path, and keep
secret-based authentication out of the PR checkout job.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9dce5589-b86d-4fb8-b187-513e695eae57

📥 Commits

Reviewing files that changed from the base of the PR and between 2c93cd8 and 7d56da8.

📒 Files selected for processing (1)
  • .github/workflows/pr-run.yaml

Comment thread .github/workflows/pr-run.yaml
Comment thread .github/workflows/pr-run.yaml
@clarkd

clarkd commented Jul 22, 2026

Copy link
Copy Markdown
Member

I'll leave this one to @shaswot77 - I don't have good visibility of whether this would expose secrets in any logs etc.

pull_request_target locks execution to the workflow file on the PR's
base ref, which is exactly what makes it safe against a fork editing
pr-run.yaml — but it also means a PR can never be used to test changes
to this file itself; the old version keeps running until the change
lands on main.

Adding workflow_dispatch closes that gap: it lets anyone with write
access run this workflow directly against a branch (gh workflow run
"Validate & Deploy Plugins" --ref <branch>), executing that branch's
own copy of the file. Two steps assumed a pull_request payload that
doesn't exist for a manual run — checkout's ref now falls back to the
dispatched commit, and the PR-comment step (which has nothing to
comment on) is skipped outside pull_request_target.

workflow_dispatch has a different safety model than pull_request_target
though: it runs whatever copy of the workflow lives on the ref you
select, not always the default branch's. A fork can't trigger it at all
(dispatching requires write access to this repo), but a maintainer
manually dispatching against a PR/fork ref rather than a real branch
would run that PR's own version of the workflow with the real secret in
scope. Left an explicit warning against doing that next to the trigger.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/pr-run.yaml (1)

47-57: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Split the CLI install from the secret-bearing login step.
SQUAREDUP_API_KEY is inherited by npm install, so any lifecycle script in @squaredup/cli or its dependencies can read it before squaredup login runs. Install the CLI in a secret-free step, then pass the API key only to the login step.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr-run.yaml around lines 47 - 57, Split the “Install &
Configure SquaredUp CLI” workflow step into separate install and login steps.
Keep the npm installation and version check in a step without SQUAREDUP_API_KEY
in its environment, then place squaredup login in a subsequent conditional step
that exposes the secret only through its env configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.github/workflows/pr-run.yaml:
- Around line 47-57: Split the “Install & Configure SquaredUp CLI” workflow step
into separate install and login steps. Keep the npm installation and version
check in a step without SQUAREDUP_API_KEY in its environment, then place
squaredup login in a subsequent conditional step that exposes the secret only
through its env configuration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: b2ebf0c4-b6dc-47fc-a5c9-7f60489150bf

📥 Commits

Reviewing files that changed from the base of the PR and between 7d56da8 and aa2e335.

📒 Files selected for processing (1)
  • .github/workflows/pr-run.yaml

@andrewmumblebee
andrewmumblebee marked this pull request as draft July 23, 2026 09:52
andrewmumblebee and others added 2 commits July 23, 2026 11:13
validate-and-deploy is a required status check on main, and this PR
can't satisfy it against itself: its own branch removed pull_request
(the only trigger that ever evaluates a PR's own workflow content),
and pull_request_target always resolves from main, which doesn't have
that trigger yet. Neither one fires, so the required check never
reports anything and the merge stays blocked — confirmed via `gh run
list`, which shows no pull_request/pull_request_target run has ever
fired on this branch, and the repo's ruleset has no bypass actors, so
there's no way around it either.

Restoring pull_request here just long enough for this PR to report
its own required check. Once merged, main has pull_request_target and
every subsequent PR is covered by it, so pull_request comes back out
in an immediate follow-up. Both the checkout ref fallback and the
PR-comment gate were already written in terms of what data is present
(a pull_request payload, or not workflow_dispatch) rather than a
specific event name, so they keep working through this transition
without further changes, including once pull_request is removed again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The prior wording didn't say who could actually act on it, which read
as if a fork PR author had some way to leak the secret directly. They
don't — dispatching requires write access to this repo, so only a
maintainer can trigger it at all. The real risk is a maintainer being
asked (or absent-mindedly choosing) to dispatch against a PR/fork ref
rather than a branch, which is a social-engineering risk against a
trusted human, not a bypass available to the fork author.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/pr-run.yaml:
- Around line 4-15: Restrict the deploy step around the visible workflow job or
step near lines 105–111 to run only when github.event_name is
pull_request_target. Preserve the existing same-repository check, while ensuring
pull_request executions remain validation-only and cannot deploy or race with
the target-triggered run.
- Around line 16-26: Remove the workflow_dispatch trigger and its associated
manual-dispatch guidance from the secret-bearing workflow, while preserving the
pull_request_target behavior and SQUAREDUP_API_KEY usage. If manual testing must
remain available, move it to a separate workflow that does not receive secrets.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6baa0edd-782c-4af6-b3de-457a563ac933

📥 Commits

Reviewing files that changed from the base of the PR and between aa2e335 and 80f646e.

📒 Files selected for processing (1)
  • .github/workflows/pr-run.yaml

Comment thread .github/workflows/pr-run.yaml
Comment thread .github/workflows/pr-run.yaml Outdated
Two real issues from CodeRabbit's review, both against the current
transitional dual-trigger setup: with pull_request and
pull_request_target both active, a same-repo PR touching plugin files
would run deploy twice per push (once per trigger), racing on the same
--suffix. Restricted deploy to pull_request_target specifically to
fix that at the root, and added a concurrency group per PR number so a
rapid follow-up push can't independently overlap and finish out of
order either — both were live gaps, not just theoretical.

Also dropping workflow_dispatch. It was added so future changes to
this file could be tested pre-merge, but it also puts the real
SQUAREDUP_API_KEY in scope for a maintainer-triggered run, and we
don't have an actual need for it yet — easier to add back once we do
than to carry the extra secret-bearing trigger on spec. Checkout's ref
and the PR-comment step's condition were only written the way they
were to account for workflow_dispatch's missing pull_request payload,
so both simplify back now that it's gone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🧩 Plugin PR Summary

ℹ️ No plugins were modified in this PR.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.github/workflows/pr-run.yaml (2)

54-64: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Keep SQUAREDUP_API_KEY out of the install step.
npm install -g @squaredup/cli`` runs with the secret in the step environment, so any npm lifecycle script can access it. Install the CLI in a separate step with no secrets, then pass SQUAREDUP_API_KEY only to `squaredup login`.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr-run.yaml around lines 54 - 64, Split the “Install &
Configure SquaredUp CLI” workflow step into separate install and login steps.
Keep npm installation and version reporting in a step without SQUAREDUP_API_KEY,
then provide the secret only in the login step that runs squaredup login.

Source: MCP tools


175-177: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Gate the comment step to pull_request_target (.github/workflows/pr-run.yaml:175-177)
GITHUB_TOKEN is read-only on fork-originated pull_request runs, so issues.deleteComment / createComment can 403 and turn a passing validation into a failed required check. Gate this step to pull_request_target or skip forks here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr-run.yaml around lines 175 - 177, Update the “Post PR
comment” step to run only for pull_request_target events, or explicitly exclude
fork-originated pull_request runs while preserving execution for trusted pull
request contexts. Keep the existing always() behavior within the permitted event
scope.

Source: MCP tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/pr-run.yaml:
- Around line 17-22: Update the workflow concurrency configuration around the
pr-run concurrency group to separate validation and deployment lanes by event,
or disable cancellation for pull_request_target deploy runs. Ensure a newer
pull_request validation cannot cancel an in-flight pull_request_target
deployment, while preserving serialization for runs that reuse the same PR
suffix.

---

Outside diff comments:
In @.github/workflows/pr-run.yaml:
- Around line 54-64: Split the “Install & Configure SquaredUp CLI” workflow step
into separate install and login steps. Keep npm installation and version
reporting in a step without SQUAREDUP_API_KEY, then provide the secret only in
the login step that runs squaredup login.
- Around line 175-177: Update the “Post PR comment” step to run only for
pull_request_target events, or explicitly exclude fork-originated pull_request
runs while preserving execution for trusted pull request contexts. Keep the
existing always() behavior within the permitted event scope.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1d90fa6e-10ee-4281-9cb4-3cd1733a49db

📥 Commits

Reviewing files that changed from the base of the PR and between 80f646e and e6c1489.

📒 Files selected for processing (1)
  • .github/workflows/pr-run.yaml

Comment on lines +17 to +22
# Serialize runs per PR so an older run (from a rapid follow-up push, or the transitional
# pull_request/pull_request_target overlap above) can't finish last and overwrite a newer
# deploy that reuses the same --suffix.
concurrency:
group: pr-run-${{ github.event.pull_request.number }}
cancel-in-progress: true

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the workflow file structure first, then inspect the relevant slices.
ast-grep outline .github/workflows/pr-run.yaml --view expanded || true

printf '\n--- workflow excerpt ---\n'
cat -n .github/workflows/pr-run.yaml | sed -n '1,260p'

Repository: squaredup/plugins

Length of output: 10060


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where the workflow uses pull_request / pull_request_target and where deploy happens.
rg -n "pull_request_target|pull_request|concurrency|cancel-in-progress|squaredup deploy|PLUGIN_PATHS|comments|API_KEY|token" .github/workflows/pr-run.yaml .github/workflows -g '*.yaml' -g '*.yml'

Repository: squaredup/plugins

Length of output: 5382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- pr-cleanup workflow ---\n'
if [ -f .github/workflows/pr-cleanup.yaml ]; then
  cat -n .github/workflows/pr-cleanup.yaml
else
  echo "missing"
fi

printf '\n--- deploy-related references ---\n'
rg -n "squaredup deploy|cleanup|suffix|cancel-in-progress|concurrency" .github/workflows -g '*.yml' -g '*.yaml'

Repository: squaredup/plugins

Length of output: 1990


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect any docs or code that describe squaredup deploy behavior or suffix cleanup.
rg -n "squaredup deploy|--suffix|pr-cleanup|cleanup old|remove.*deployment|undeploy|delete.*deployment|tenant" . -g '*.md' -g '*.yaml' -g '*.yml' -g '*.js' -g '*.ts' -g '*.sh'

Repository: squaredup/plugins

Length of output: 541


Separate validation from deployment concurrency
.github/workflows/pr-run.yaml:20-22 uses one PR-wide group for both events, so a newer pull_request validation can cancel an in-flight pull_request_target deploy. Since deploy writes to the live tenant and reuses the same --suffix, that can leave partial PR state. Split the group by event, or disable cancellation for the deploy lane.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr-run.yaml around lines 17 - 22, Update the workflow
concurrency configuration around the pr-run concurrency group to separate
validation and deployment lanes by event, or disable cancellation for
pull_request_target deploy runs. Ensure a newer pull_request validation cannot
cancel an in-flight pull_request_target deployment, while preserving
serialization for runs that reuse the same PR suffix.

Source: MCP tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants