ci: scan issue and comment bodies — this repo has never scanned one - #45
ci: scan issue and comment bodies — this repo has never scanned one#45yakimoto wants to merge 6 commits into
Conversation
…ment body Measured across all 28 public wave-av repos (claude-workstation#1747, #1794): TWO coverage shapes satisfy the one required check name `Secrets + content policy`. 27 repos triggers: pull_request, push, workflow_dispatch jobs: guard 1 repo triggers: + issues, issue_comment jobs: + body-guard This repo is in the 27. All 28 report the same green check. The outlier is wave-moq-edge, and its own comment says why it matters: "`edited` matters as much as `opened`: a body can be made to leak long after the PR is first raised, and until this workflow covered it, nothing ever re-scanned." A PR/issue/comment BODY is exactly as world-readable as the tree, and until now it was scanned by nothing server-side. That gap was not theoretical on wave-moq-edge: a PR was blocked for naming a private repo in wrangler.toml while the very same name, with more operational detail attached, sat unchallenged in its body. WHAT LANDS HERE — the bundle the workflow's own header names, minus what this repo already has (.gitleaks.toml and content-policy.sh are already vendored): .github/workflows/public-repo-guard.yml replaced (73 -> 163 lines) scripts/public-repo-guard/body-policy.sh new, mode 100755 scripts/public-repo-guard/tests/body-policy.test.sh new, mode 100755 Copied from wave-moq-edge, which has run this shape in production. Modes preserved via the git trees API — the contents API would have created both scripts 100644. HONEST ABOUT WHAT IT CAN DO. On a PR this PREVENTS the merge. On an issue or comment the text is already public the moment it posts, so this is DETECTION: it says go redact, fast. Only a client-side pre-write hook stops that class before publication. Also inherited from the reference: concurrency moves from workflow-level to PER JOB, because the two jobs want opposite behaviour. A workflow-level group forced one policy on both, and rapid body edits cancelled the tree job repeatedly — every cancelled check-run stays attached to the commit, so the PR reported UNSTABLE while the live runs were green. The body gate ships with its own fixtures and runs them in CI. Its NEGATIVE cases are the load-bearing half: a leak gate that blocks legitimate cross-repo references gets switched off, and then it protects nothing. Refs wave-av/claude-workstation#1747. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_08e118ad-7abe-434d-8eb3-ea7fccf5399d) |
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 57 minutes Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
Comment |
|
Running ultrareview automatically — This CI security gate now scans untrusted PR/issue/comment bodies and reworks concurrency; a subtle false-positive, fail-open, or injection bug could block every merge or let real secrets go public, so it warrants a deeper review.. I'll post findings when complete. |
ApprovabilityVerdict: Needs human review Unable to check for correctness in 52a7be5. This PR introduces new security scanning capability for issue/comment bodies with significant CI workflow changes. Unresolved review comments identify potential bugs (comment events re-scanning PR bodies and failing main branch) and security gaps (fork PR neutralization, incomplete review-comment coverage) that warrant human attention. You can customize Macroscope's approvability policy. Learn more. |
PR Summary by QodoCI: scan PR/issue/comment bodies in public-repo-guard (add body-guard job)
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1.
|
Qodo FixerNo findings are within the configured fix scope. To change which findings are fixed, adjust the setting on your Qodo configuration page. |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| # Only the gate's own scripts are needed — no reason to pay for the whole | ||
| # tree on every comment. | ||
| sparse-checkout: scripts/public-repo-guard | ||
| sparse-checkout-cone-mode: false |
There was a problem hiding this comment.
🔍 Fork PRs run the body gate from their own branch, so a fork can neutralize it
body-guard triggers on pull_request and checks out the PR's merge ref, then executes scripts/public-repo-guard/body-policy.sh from that checkout. A fork PR can therefore modify the gate script in the same PR and have the modified version judge its own body. The header of .github/workflows/public-repo-guard.yml:9-14 claims the gate "cannot be reprogrammed out-of-band", which is true for wave-av/.github but not for the PR branch itself. The pre-existing guard job has the same property, so this is not new, but the new job is explicitly described as PREVENTING a merge (.github/workflows/public-repo-guard.yml:109-112), which makes the assumption worth verifying. Pinning the script fetch to the base ref (e.g. checking out github.event.pull_request.base.sha for the scripts directory) would close it.
Was this helpful? React with 👍 or 👎 to provide feedback.
…ilter errors Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
There was a problem hiding this comment.
Ultrareview completed in 11m 3s
1 issue found and verified against the latest diff
Confidence score: 4/5
- In
.github/workflows/public-repo-guard.yml, subscribing only toissue_commentleaves PR review comments and review bodies unscanned, so leak-like content in those channels can slip past the guard and remain publicly visible—extend triggers to include PR review comment/review events and scan their payload fields as well.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name=".github/workflows/public-repo-guard.yml">
<violation number="1" location=".github/workflows/public-repo-guard.yml:35">
P2: Comment scanning is still incomplete because the workflow only subscribes to `issue_comment`. Public PR review comments (and review bodies) do not trigger this job, so leak-like content in those comment channels is currently outside the body-policy coverage. Expanding triggers to review comment/review events would close that gap.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| types: [opened, edited, reopened, synchronize] | ||
| issues: | ||
| types: [opened, edited] | ||
| issue_comment: |
There was a problem hiding this comment.
P2: Comment scanning is still incomplete because the workflow only subscribes to issue_comment. Public PR review comments (and review bodies) do not trigger this job, so leak-like content in those comment channels is currently outside the body-policy coverage. Expanding triggers to review comment/review events would close that gap.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/public-repo-guard.yml, line 35:
<comment>Comment scanning is still incomplete because the workflow only subscribes to `issue_comment`. Public PR review comments (and review bodies) do not trigger this job, so leak-like content in those comment channels is currently outside the body-policy coverage. Expanding triggers to review comment/review events would close that gap.</comment>
<file context>
@@ -25,24 +26,44 @@ name: public-repo-guard
+ types: [opened, edited, reopened, synchronize]
+ issues:
+ types: [opened, edited]
+ issue_comment:
+ types: [created, edited]
push:
</file context>
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
…re names Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
| issues: | ||
| types: [opened, edited] | ||
| issue_comment: | ||
| types: [created, edited] |
There was a problem hiding this comment.
🔍 Review-comment bodies are still unscanned
issue_comment covers issue comments and top-level PR conversation comments, but NOT pull_request_review_comment (inline code-review comments) or pull_request_review (review summary bodies). Those bodies are equally world-readable and are exactly where operational detail tends to get pasted during review, so the stated gap ("an issue or comment BODY is scanned by nothing server-side") is only partly closed.
Was this helpful? React with 👍 or 👎 to provide feedback.
…ternal-marker Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
…o WARN for bodies Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
| - name: body policy self-test (fixtures) | ||
| run: bash scripts/public-repo-guard/tests/body-policy.test.sh |
There was a problem hiding this comment.
🟨 The leak gate runs the pull request's own copy of its scripts, so a PR can weaken or disable the check it must pass
The guard job checks out the PR merge ref and then executes scripts/public-repo-guard/tests/body-policy.test.sh (and, in the pre-existing step, content-policy.sh) from that checkout. A pull request — including one from a fork — can modify those vendored scripts in the same PR, so the run that is supposed to gate it executes attacker-controlled code. The self-test step in particular exists to prove the gate works, but a PR that edits the fixtures makes it prove nothing. The same job also passes the org variable GUARD_PRIVATE_REPOS (the list of private WAVE repo names the gate exists to keep out of public view) into a script the PR author controls, so a modified script can print those names into the public Actions log.
Was this helpful? React with 👍 or 👎 to provide feedback.
This repo's
public-repo-guardhas never scanned a single issue or comment body.Measured across all 28 public wave-av repos (
wave-av/claude-workstation#1747,#1794): two coverage shapes satisfy the one required check nameSecrets + content policy.pull_request, push, workflow_dispatchguardissues,issue_commentbody-guardThis repo is in the 27. All 28 report the same green check — because a required check asserts that something named X passed, never what X examined.
The outlier is
wave-moq-edge, and its own comment says why it matters:That gap was not theoretical there: a PR was blocked for naming a private repo in
wrangler.tomlwhile the very same name, with more operational detail attached, sat unchallenged in its body.What lands
Three files — the bundle the workflow's own header names, minus what this repo already has (
.gitleaks.tomlandcontent-policy.share already vendored, and are checked as prerequisites; a repo missing either is refused rather than half-installed):The reference workflow's header named four files as the install unit but executes a fifth —
tests/body-policy.test.sh, in its own self-test step. Omitting it installs a workflow that fails on a step nobody read, so the manifest ships it, and (after review flagged the same trap) the shipped header now lists all five. Modes are preserved via the git trees API; the contents API creates100644regardless, which would silently break running these scripts as executables.Planned by
governance/lib/vendor-bundle.mjs(claude-workstation#1850) against a checked-in manifest, not by ad-hoc shell.One deliberate divergence from the reference, stated rather than silent
The shipped workflow is
wave-moq-edge's withactions/checkoutbumped from v5.0.1 to v7.0.1 (3d3c42e5aac5ba805825da76410c181273ba90b1), the pin already used byclaude-workstation's own gate.Copying verbatim was checked first and rejected on evidence: of the 18 target repos, 17 carry a byte-identical guard, and
wave-realtime-edgealready runs v7.0.0 — so a verbatim copy would have downgraded it, and shipped a stale pin to the other 17. A separate PR brings the reference itself up to the same pin.More divergences, from review findings on this PR
Review (Qodo, Devin) caught real defects in the reference shape, fixed here:
A required check that goes stale on body edits. The reference
guardjob skips PReditedevents entirely, while only the non-requiredBody content policycheck re-runs. If branch protection requires onlySecrets + content policy, a post-approval body edit can introduce a leak behind that check's stale green result. Nowguardruns on every PR action includingedited, the PR title/body scan runs insideguard, andbody-guardnarrows to issues and comments, the detection-only surface.A partial run must not overwrite a full verdict. The first fix skipped the tree-scan steps on
edited, and review caught the consequence: GitHub evaluates a required check from the LATEST check run of that name on the commit, so a green body-only run would have overwritten a red tree verdict, letting a body edit launder a failing secret scan. Everyguardrun is therefore FULL scope, which is also what makes cancel-in-progress safe:An allowlist filter that could fail open. The
|| trueonbody-policy.sh's allowlist pipeline masked real ripgrep errors (exit >= 2) along with the expected no-survivors exit 1, so a broken filter stage could empty the match set and report a pass. Each filter stage now checks its exit code explicitly and exits 2 (fail closed) on a real error.Regex precision holes in
body-policy.sh. A global(?i)made lowercase identifiers likeapi_keycount as credential names (blocking legitimate cross-repo PRs); the credential-name class could not span underscores, missingEXAMPLE_LEASE_ROTATION_SECRET-shaped names in repo-first order; the internal-marker rule missed sentence-initial capitals ("Do not share..."); and the about-the-control allowlist applied beyond its one safe rule; it is now internal-marker-only, because a credential, IP, path, or wiring topology quoted on a line that discusses the gate is re-published all the same. All fixed, each with a regression fixture.The fixtures themselves leaked. The test file pinned three real private repo names (and plausibly real credential names) into a public file inside
scripts/public-repo-guard/, the one directory both scanners deliberately exclude. The fixtures now use obviously fictional names, with a comment explaining why they must stay that way.Honest about what this can and cannot do
On a PR this PREVENTS the merge, and it does so under the one check name branch protection requires. On an issue or comment the text is already public the moment it posts, so this is DETECTION: it says go redact, fast. Only a client-side pre-write hook stops that class before publication.
Also inherited from the reference: concurrency moves from workflow-level to per job, because the two jobs want opposite behaviour. A workflow-level group forced one policy on both, and rapid body edits cancelled the tree job repeatedly — every cancelled check-run stays attached to the commit, so the PR reported UNSTABLE while the live runs were green.
The body gate ships with its own fixtures and runs them in CI. Its negative cases are the load-bearing half: a leak gate that blocks legitimate cross-repo references gets switched off, and then it protects nothing.
Refs
wave-av/claude-workstation#1747.Note
Cursor Bugbot is generating a summary for commit 8f29c21. Configure here.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is enabled.Note
Scan PR titles, bodies, issue bodies, and comment bodies in public-repo-guard CI
guardjob now also scans PR title and body, and a newbody-guardjob handles issue and comment bodies.guardjob on every invocation.guardorbody-guardjob to fail; previously these bodies were never scanned.Macroscope summarized 52a7be5.