Skip to content
Closed
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
26 changes: 26 additions & 0 deletions .github/workflows/review-swarm-wrapper-guard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Review swarm wrapper guard

on:
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review]

permissions:
contents: read
pull-requests: read

jobs:
guard:
runs-on: ubuntu-latest
steps:
# pull_request_target always reads this workflow and script from the PR
# base. A candidate therefore cannot relax the guard that evaluates it.
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.sha }}
persist-credentials: false

- name: Reject candidate-owned wrapper changes
env:
GH_TOKEN: ${{ github.token }}
REVIEW_PR_NUMBER: ${{ github.event.pull_request.number }}
run: .github/workflows/scripts/swarm-wrapper-guard.sh "$REVIEW_PR_NUMBER"
25 changes: 1 addition & 24 deletions .github/workflows/review-swarm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -258,30 +258,7 @@ jobs:
done
echo "swarm_status=$status" >> "$GITHUB_OUTPUT"
if [ "$status" != completed ]; then
# The status word alone does not say why the swarm failed, and the
# reason never reaches this log: it sits in the run payload we just

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Diagnostic script path is wrong

High Severity

The wait step calls swarm-status-diagnostic.sh via ../gate-files without working-directory: pr-head, so the path resolves outside the workspace. set +e swallows that miss, and swarm failure reasons never reach the log or step summary.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8a8ece9. Configure here.

# fetched. A quota rejection reads here as a bare "failed", which
# sent one reader inferring for days before querying the run by
# hand. Print what we already have.
reason=$(jq -r '.result.error // .error // empty' <<<"${response:-}" 2>/dev/null)
if [ -n "$reason" ]; then
# The reason is not fully trusted. It can carry agent output,
# which can carry content from the PR under review. Two ways that
# bites: a line starting with `::` is parsed by Actions as a
# workflow command, and a line of three backticks would close a
# fenced block early and render the rest as markup.
# Indenting every line defeats both at once — Actions only parses
# a command at the start of a line, and an indented block is a
# Markdown code block with no fence to break.
safe_reason=$(printf '%s\n' "$reason" | sed 's/^/ /')
echo "swarm failure reason:" >&2
printf '%s\n' "$safe_reason" >&2
{
echo "### Swarm failure reason"
echo
printf '%s\n' "$safe_reason"
} >> "$GITHUB_STEP_SUMMARY"
fi
../gate-files/.github/workflows/scripts/swarm-status-diagnostic.sh "${response:-}"
fi
exit 0

Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/scripts/swarm-status-diagnostic.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail

response=${1:-}
reason=$(jq -r '.result.error // .error // empty' <<<"$response" 2>/dev/null)
if [ -n "$reason" ]; then
safe_reason=$(printf '%s\n' "$reason" | sed 's/^/ /')
echo "swarm failure reason:" >&2
printf '%s\n' "$safe_reason" >&2
{ echo "### Swarm failure reason"; echo; printf '%s\n' "$safe_reason"; } >> "$GITHUB_STEP_SUMMARY"
fi
12 changes: 12 additions & 0 deletions .github/workflows/scripts/swarm-wrapper-guard.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail

pr_number=${1:?usage: swarm-wrapper-guard.sh PR_NUMBER}
changed_files=$(gh api --paginate "repos/${GITHUB_REPOSITORY}/pulls/${pr_number}/files" --jq '.[].filename')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject renames of the protected wrapper

When a PR renames .github/workflows/review-swarm.yml, GitHub reports the destination in filename and the original path in previous_filename; this query discards the latter, so the exact-match check passes. A candidate can therefore rename the wrapper, retain its required check name, and weaken its contents without rejection. Compare both fields against the protected path.

AGENTS.md reference: AGENTS.md:L34-L34

Useful? React with 👍 / 👎.


if grep -Fxq '.github/workflows/review-swarm.yml' <<<"$changed_files"; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Guard misses renamed wrapper file

Medium Severity

The guard only reads each file's filename, so a rename of .github/workflows/review-swarm.yml is not detected. The files API puts the old path in previous_filename, which lets a candidate move the wrapper off the branch without failing this check.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8a8ece9. Configure here.

echo "candidate changes review-swarm.yml; wrapper enforcement is base-owned and immutable" >&2
exit 1
fi

echo "REVIEW_SWARM_WRAPPER_GUARD_OK"
Loading