Skip to content

ci: scan issue and comment bodies — this repo has never scanned one - #29

Open
yakimoto wants to merge 12 commits into
mainfrom
ci/1747-public-repo-guard-body-scan
Open

ci: scan issue and comment bodies — this repo has never scanned one#29
yakimoto wants to merge 12 commits into
mainfrom
ci/1747-public-repo-guard-body-scan

Conversation

@yakimoto

@yakimoto yakimoto commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

This repo's public-repo-guard has 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 name Secrets + content policy.

repos triggers jobs
27 pull_request, push, workflow_dispatch guard
1 + issues, issue_comment + body-guard

This 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:

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.

That gap was not theoretical there: 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

Four files — the bundle the workflow's own header names, minus what this repo already has (.gitleaks.toml and content-policy.sh are already vendored, and are checked as prerequisites; a repo missing either is refused rather than half-installed):

.github/workflows/public-repo-guard.yml               replaced (tree gate only)
.github/workflows/public-repo-guard-body.yml          new (the body gate)
scripts/public-repo-guard/body-policy.sh              new, mode 100755
scripts/public-repo-guard/tests/body-policy.test.sh   new, mode 100755

The body gate lives in its OWN workflow file, not as a second job beside the tree gate: a job-level if skip still creates a check run (conclusion skipped) on the PR head SHA, required checks treat skipped as satisfied, and the newest check run of a name wins. If the tree gate's workflow also fired on issue/comment/review events only to skip the guard job, any comment or body edit would launder an earlier genuine Secrets + content policy failure on the same commit into a mergeable PR. Splitting the files means the required check never reports a skipped verdict.

The workflow's header names four files as the install unit but executes a fifthtests/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. Modes are preserved via the git trees API; the contents API creates 100644 regardless, 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 with actions/checkout bumped from v5.0.1 to v7.0.1 (3d3c42e5aac5ba805825da76410c181273ba90b1), the pin already used by claude-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-edge already 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.

Honest about what this can and cannot 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.


View with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is enabled.


Note

Medium Risk
Changes org merge-gate behavior and required-check semantics (skipped vs failure laundering); misconfigured GUARD_PRIVATE_REPOS or missing PCRE2 ripgrep will fail CI until fixed.

Overview
Closes the gap where only the published tree was scanned while PR/issue/comment/review text could leak the same material (e.g. private repo names plus operational detail in a body while wrangler.toml was blocked).

Adds a separate workflow public-repo-guard-body.yml with a Body content policy job on pull_request, issues, issue_comment, pull_request_review, and pull_request_review_comment (including edited). It materializes untrusted title/body via jq into a temp file, sparse-checkouts scripts/public-repo-guard, verifies PCRE2-enabled ripgrep, and runs the new body-policy.sh. Splitting workflows avoids a skipped required Secrets + content policy run replacing a real tree-scan failure on the same commit.

Updates public-repo-guard.yml so the tree guard job stays on tree-changing events only, uses per-job concurrency, bumps actions/checkout to v7.0.1, adds PCRE2 checks for policy scripts, documents a six-file install bundle, and runs tests/body-policy.test.sh in CI.

body-policy.sh applies credential/infra rules plus a proximity rule: private repo names from GUARD_PRIVATE_REPOS only block when paired with operational detail (not bare cross-refs). In CI, empty GUARD_PRIVATE_REPOS fails closed (exit 2). content-policy.sh now parses multiline/newline-separated GUARD_PRIVATE_REPOS the same way as the body gate.

Reviewed by Cursor Bugbot for commit 4a64de0. Configure here.

Review in cubic

Note

Add issue and comment body scanning to the public repo guard CI

  • Adds public-repo-guard-body.yml, a new workflow that triggers on PR, issue, comment, and review events and runs body-policy.sh against the materialized event body.
  • body-policy.sh blocks on credential formats (Stripe, Anthropic, GitHub PATs, AWS keys, PEM keys), infrastructure identifiers, and operational detail near private repo names sourced from GUARD_PRIVATE_REPOS.
  • Updates public-repo-guard.yml to pin ripgrep to a musl build with SHA256 verification, require PCRE2, and run a new body-policy self-test step on every PR.
  • Fixes content-policy.sh to parse newline-separated GUARD_PRIVATE_REPOS values and fail closed in CI when the variable is empty or separator-only.
  • Risk: an empty or misconfigured GUARD_PRIVATE_REPOS org/repo variable now causes the job to exit with code 2 (fail closed) instead of silently skipping the private-repo rule.

Macroscope summarized d2e4139.

…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>
@yakimoto yakimoto added the rr:skip-coderabbit RF.P1 reviewer routing (#1039) label Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are limited based on label configuration.

🚫 Excluded labels (none allowed) (1)
  • rr:skip-coderabbit

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2a7ac8a1-ee22-45bf-88dc-af13f27c8ed6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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_895e2909-3829-40f4-93e0-63e37e037599)

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Running ultrareview automatically — This PR rewires the repo's merge-blocking secret scanner: new issue/comment triggers, per-job concurrency, and a fail-closed body regex engine whose false-positive or false-negative bugs could either stall every PR or let credentials leak in public issue and comment bodies.. I'll post findings when complete.

@macroscopeapp

macroscopeapp Bot commented Aug 6, 2026

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

Unable to check for correctness in d2e4139. This PR introduces security-sensitive CI infrastructure with multiple unresolved review comments identifying potential bypass vulnerabilities and design inconsistencies. The concerns about gate scripts executing from PR checkout and the body scan being advisory-only warrant human review.

You can customize Macroscope's approvability policy. Learn more.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

CI: scan PR/issue/comment bodies in public-repo-guard

✨ Enhancement ⚙️ Configuration changes 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Add body scanning for PRs, issues, and comments (including edited events).
• Split tree-scan vs body-scan jobs with safer concurrency and sparse checkout.
• Introduce body-policy rules plus fixture-based self-tests to prevent false positives.
Diagram

graph TD
  A["GitHub events\n(PR / issues / comments)"] --> B["public-repo-guard workflow"] --> C["guard job\n(tree scan)"] --> D["content-policy.sh"] --> E["repo working tree"]
  B --> F["body-guard job\n(body scan)"] --> G["body-policy.sh"] --> H["event title/body text file"]
  C --> I["body-policy fixture tests"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Separate workflow file for body scanning
  • ➕ Clearer separation of concerns and permissions per workflow
  • ➕ Independent required checks (tree vs body) if desired
  • ➖ More files to vendor/manage across repos
  • ➖ Harder to keep pins and behavior in sync without additional tooling
2. Use GitHub Advanced Security secret scanning + push protection
  • ➕ Native platform support; less custom maintenance
  • ➕ Broad detection coverage for many credential types
  • ➖ May not cover organization-specific policy (private repo + ops detail proximity)
  • ➖ Availability/cost constraints depending on org/repo settings
3. Comment/issue scanning via a reusable workflow in a central repo
  • ➕ Single source of truth; easier global updates
  • ➕ Consistent behavior and pins across all public repos
  • ➖ Requires careful design to avoid token escalation and fork PR pitfalls
  • ➖ Still needs each repo wired to call the reusable workflow correctly

Recommendation: The PR’s approach (a dedicated body-guard job inside the existing workflow, using pull_request not pull_request_target, failing closed on unknown payload shapes, and using sparse checkout) is the best near-term tradeoff: it adds the missing coverage without increasing fork-token risk and keeps rollout simple (single workflow to vendor). Consider splitting into a reusable workflow later if cross-repo maintenance becomes burdensome.

Files changed (3) +342 / -5

Enhancement (1) +139 / -0
body-policy.shIntroduce body content-policy gate for PR/issue/comment text +139/-0

Introduce body content-policy gate for PR/issue/comment text

• Implements a ripgrep-based policy scanner for materialized title/body text, with fail-closed behavior on scanner errors or invalid input. Adds allowlisting ('guard:allow ...' and an "about the control" regex), redacts matched content in annotations, and enforces targeted blocking rules including credential formats, internal markers, and private-repo proximity to operational detail via GUARD_PRIVATE_REPOS.

scripts/public-repo-guard/body-policy.sh

Tests (1) +108 / -0
body-policy.test.shAdd fixture tests to validate body-policy precision and redaction +108/-0

Add fixture tests to validate body-policy precision and redaction

• Adds hermetic fixture tests that assert both blocking cases (leak-like strings) and non-blocking cases (legitimate cross-repo references and policy discussion). Verifies fail-closed exit codes for missing inputs and ensures annotations never echo matched body text.

scripts/public-repo-guard/tests/body-policy.test.sh

Other (1) +95 / -5
public-repo-guard.ymlAdd body-guard job and expand triggers to issues/comments +95/-5

Add body-guard job and expand triggers to issues/comments

• Adds issues and issue_comment triggers (including edited) and expands pull_request types to rescan edited bodies. Introduces a separate body-guard job with its own concurrency policy, sparse checkout, and safe event-payload materialization before running the new body-policy script; also bumps actions/checkout to v7.0.1 and moves concurrency to per-job semantics.

.github/workflows/public-repo-guard.yml

@qodo-code-review

qodo-code-review Bot commented Aug 6, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. PR bypasses body scan ⊘ Outdated 🐞 Bug ⛨ Security
Description
The body-guard job checks out repository content and then executes
scripts/public-repo-guard/body-policy.sh from that checkout, so a pull request can modify the policy
script to always exit 0 and make “Body content policy” report success without actually enforcing the
intended policy.
Code

.github/workflows/public-repo-guard.yml[R160-163]

+      - name: body policy (PR / issue / comment text)
+        env:
+          GUARD_PRIVATE_REPOS: ${{ vars.GUARD_PRIVATE_REPOS }}
+        run: bash scripts/public-repo-guard/body-policy.sh "$RUNNER_TEMP/bodyscan/body.txt"
Evidence
The workflow’s body-guard job runs on pull_request events and checks out
scripts/public-repo-guard, then runs bash scripts/public-repo-guard/body-policy.sh ... from the
checked-out workspace, meaning the scanner implementation is taken from the checked-out revision
rather than an immutable trusted source.

.github/workflows/public-repo-guard.yml[113-163]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`body-guard` executes `scripts/public-repo-guard/body-policy.sh` from the checked-out workspace. On `pull_request` events, that workspace content comes from the PR revision, so the PR author can modify `body-policy.sh` (or its helpers) to always succeed, effectively disabling the body gate while still producing a green check.

### Issue Context
This job is intended to act as a policy gate for PR bodies (preventing merge) and as detection for issues/comments. The workflow correctly avoids `pull_request_target`, but that does not make scripts inside the PR checkout trusted.

### Fix Focus Areas
- .github/workflows/public-repo-guard.yml[113-163]

### Suggested fix
For `pull_request` events, ensure the policy script is sourced from a trusted ref (base branch) rather than from the PR checkout. Options:
1. **Checkout base ref for scripts** in `body-guard`:
  - Set `ref: ${{ github.event.pull_request.base.sha }}` (or `${{ github.base_ref }}` with appropriate usage) on the `actions/checkout` step.
  - Keep sparse-checkout to only `scripts/public-repo-guard`.
2. **Alternative**: inline the policy script in the workflow or package it as an immutable artifact, so PRs cannot alter what executes.

After change, verify that `body-guard` still reads the untrusted body from `$GITHUB_EVENT_PATH` and only treats the scanner code as trusted.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Allowlist hides credential hits ✓ Resolved 🐞 Bug ≡ Correctness
Description
body-policy.sh applies the ABOUT_THE_CONTROL allowlist as a global negative filter for every rule,
so a real credential-pattern match can be suppressed if it appears on the same line as an allowlist
term like “public-repo-guard” or “SECURITY.md”.
Code

scripts/public-repo-guard/body-policy.sh[R53-56]

+  matches="$(printf '%s' "$raw" \
+    | rg -vN -- 'guard:allow[[:space:]]+[^[:space:]]' \
+    | rg -vNiP -- "$ABOUT_THE_CONTROL" || true)"
+  [[ -z "$matches" ]] && return 0
Evidence
The script defines ABOUT_THE_CONTROL and then pipes every rule’s raw matches through an `rg -vNiP --
"$ABOUT_THE_CONTROL"` filter, so any matching line containing those terms is excluded before
violations are counted.

scripts/public-repo-guard/body-policy.sh[31-56]
scripts/public-repo-guard/body-policy.sh[71-86]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`ABOUT_THE_CONTROL` is applied as a blanket exclusion to *all* rule matches. This creates a false-negative/bypass: any matched line that also contains an allowlisted term (e.g., `public-repo-guard`, `SECURITY.md`) is dropped, even if it contains a real secret-shaped value.

### Issue Context
The allowlist is documented as intentional to avoid blocking discussions about the control. However, exempting entire lines for all rule types also exempts credential-format rules, which are exactly the ones you generally want to remain blocking even in “discussion” contexts (and can be allowlisted explicitly with `guard:allow` when needed).

### Fix Focus Areas
- scripts/public-repo-guard/body-policy.sh[31-69]

### Suggested fix
Change the filter model so ABOUT_THE_CONTROL does **not** suppress credential-format rules:
- Option A (recommended): Add a per-rule flag to `check()` to control whether ABOUT_THE_CONTROL filtering applies. Apply it only to the prose-oriented rules (e.g., internal-marker) and/or the private-repo-ops rule, but **not** to credential-format rules.
- Option B: Keep ABOUT_THE_CONTROL filtering, but if a line matches a credential rule, require an explicit `guard:allow <reason>` to suppress it (do not suppress solely due to ABOUT_THE_CONTROL).

Update/add fixture coverage to demonstrate that a credential match remains blocking even when the same line mentions `public-repo-guard`/`SECURITY.md` unless `guard:allow` is present.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
✅ Compliance rules (platform): 2 rules
Review mode: ⚖️ Balanced: Downgraded extended -> standard: change is below the extended eligibility bar (hunks 5/18, lines 347/200; both must reach the floor). Router rationale: This security-sensitive CI change adds substantial workflow, shell-scanning, event-handling, regex, and test logic across multiple independent edit sites, making redundant review materially useful for subtle bypasses or regressions.

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

qodo-code-review[bot]

This comment was marked as resolved.

@qodo-code-review

Copy link
Copy Markdown

Qodo Fixer

✅ Merged (0) · ☑ Fixed (0)

Process

  • No fixes were applied (no_fixes_applied)

devin-ai-integration[bot]

This comment was marked as resolved.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ultrareview completed in 9m 52s

2 issues found and verified against the latest diff

Confidence score: 2/5

  • In .github/workflows/public-repo-guard.yml, the PR body scanner executes scripts/public-repo-guard/body-policy.sh from the PR’s own checkout, so an attacker can modify the script in the same PR to suppress findings and bypass the control; this weakens a security boundary for public-repo leak prevention — run the policy script from a trusted ref (default branch/pinned commit) instead of PR code.
  • In .github/workflows/public-repo-guard.yml, leak detection appears fail-open because Body content policy is separate from the required merge-protection context, so merges can proceed even when the scanner fails or is bypassed; this creates a direct path for sensitive content to slip through — make the scanning check itself required (or block on its result in the required context).
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:66">
P1: The new body leak gate can be bypassed on pull requests because it executes `scripts/public-repo-guard/body-policy.sh` from the PR checkout itself. That means the same PR can change the scanner logic and force a green result even when the PR body should be blocked. Using the trusted base ref for PR events keeps enforcement code immutable during evaluation.</violation>

<violation number="2" location=".github/workflows/public-repo-guard.yml:114">
P1: Custom agent: **Flag Security Vulnerabilities**

PR body leak detection is currently fail-open for merge protection. The new scanner runs in a separate `Body content policy` check context, while the required context called out in this repo is still `Secrets + content policy`; that means a leaking PR body can fail `body-guard` without failing the required gate. This is especially visible on `pull_request` `edited`, where the required `guard` job is skipped and only the non-required body job runs. Consider routing body-scan failure into the required check context (or updating required checks) so PR body leaks actually block merges.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

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: The new body leak gate can be bypassed on pull requests because it executes scripts/public-repo-guard/body-policy.sh from the PR checkout itself. That means the same PR can change the scanner logic and force a green result even when the PR body should be blocked. Using the trusted base ref for PR events keeps enforcement code immutable during evaluation.

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 66:

<comment>The new body leak gate can be bypassed on pull requests because it executes `scripts/public-repo-guard/body-policy.sh` from the PR checkout itself. That means the same PR can change the scanner logic and force a green result even when the PR body should be blocked. Using the trusted base ref for PR events keeps enforcement code immutable during evaluation.</comment>

<file context>
@@ -25,24 +26,44 @@ name: public-repo-guard
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd  # v5.0.1
+      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1  # v7.0.1
 
       # gitleaks' GitHub Action requires a paid license for organizations; the CLI
</file context>
Fix with cubic

Comment thread .github/workflows/public-repo-guard.yml Outdated
# detection — it tells us to go redact, fast. Only the client-side pre-write hook
# can stop that class before publication.
body-guard:
name: Body content policy

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

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: Custom agent: Flag Security Vulnerabilities

PR body leak detection is currently fail-open for merge protection. The new scanner runs in a separate Body content policy check context, while the required context called out in this repo is still Secrets + content policy; that means a leaking PR body can fail body-guard without failing the required gate. This is especially visible on pull_request edited, where the required guard job is skipped and only the non-required body job runs. Consider routing body-scan failure into the required check context (or updating required checks) so PR body leaks actually block merges.

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 114:

<comment>PR body leak detection is currently fail-open for merge protection. The new scanner runs in a separate `Body content policy` check context, while the required context called out in this repo is still `Secrets + content policy`; that means a leaking PR body can fail `body-guard` without failing the required gate. This is especially visible on `pull_request` `edited`, where the required `guard` job is skipped and only the non-required body job runs. Consider routing body-scan failure into the required check context (or updating required checks) so PR body leaks actually block merges.</comment>

<file context>
@@ -71,3 +92,72 @@ jobs:
+  # detection — it tells us to go redact, fast. Only the client-side pre-write hook
+  # can stop that class before publication.
+  body-guard:
+    name: Body content policy
+    if: github.event_name == 'pull_request' || github.event_name == 'issues' || github.event_name == 'issue_comment'
+    concurrency:
</file context>
Fix with cubic

Comment thread .github/workflows/public-repo-guard.yml Outdated
yakimoto and others added 2 commits August 6, 2026 17:33
…E2 at install

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
…public surface

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 5 new potential issues.

Open in Devin Review

Comment thread scripts/public-repo-guard/body-policy.sh
Comment thread scripts/public-repo-guard/tests/body-policy.test.sh
Comment on lines +91 to +96
# --- Infrastructure identifiers ----------------------------------------------
# shellcheck disable=SC2016 # $CLOUDFLARE_ACCOUNT_ID is literal guidance text
check BLOCK cf-account-id 'account_id\s*[:=]\s*["'"'"']?[0-9a-f]{32}' 'Hardcoded Cloudflare account_id — reference the env var instead'
check BLOCK internal-ip '100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\.[0-9]{1,3}\.[0-9]{1,3}' 'Internal Tailscale-CGNAT IP (100.64.0.0/10) — internal fleet address'
# shellcheck disable=SC2016 # $HOME is literal guidance text
check BLOCK abs-user-path '/(Users|home)/(?!runner/)[a-z][a-z0-9._-]+/' 'Operator absolute home path — leaks identity and local layout'

@devin-ai-integration devin-ai-integration Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Infrastructure rules will block bodies that merely discuss the gate's own patterns

internal-ip, cf-account-id and abs-user-path deliberately do not accept the ABOUT-THE-CONTROL allowlist. That means a PR/issue body describing this gate — e.g. quoting 100.64.0.0/10 or pasting /home/someone/repo from a repro — blocks the merge, and the only escape is an explicit guard:allow <reason> in the body. The internal-ip regex matches 100.64.0.0 inside the CIDR notation itself, so the design doc / release notes for this very feature would trip it. Worth confirming this friction is acceptable, since the file's own comments argue that a gate people fight gets switched off.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This friction is intentional and documented in the file: credential and infrastructure rules deliberately ignore the ABOUT-THE-CONTROL allowlist because an infra identifier is a leak even on a line discussing the gate, and the explicit, publicly visible guard:allow is the designed escape for docs and design discussions that must quote the patterns.

Comment thread .github/workflows/public-repo-guard.yml Outdated
Comment on lines +147 to +152
- 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

@devin-ai-integration devin-ai-integration Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Gate scripts are executed from the pull request's own checkout

Both jobs check out the PR merge ref and then run bash scripts/public-repo-guard/body-policy.sh from that checkout, so a pull request that edits (or empties) the policy script is scanned by its own modified version and can report a pass. This is pre-existing behaviour for the tree job, but the new body job inherits it, and the body job is the one whose whole premise is that server-side scanning cannot be bypassed client-side. If the intent is tamper-resistance, the gate scripts would need to come from the base ref (e.g. a second checkout of github.event.pull_request.base.sha into a separate path, or a git checkout base -- scripts/public-repo-guard).

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

On pull_request events GitHub executes the workflow definition itself from the PR merge ref, so a fork PR can already edit the YAML to bypass the gate; pinning the script checkout to a fixed ref gains nothing. The pull_request-over-pull_request_target trade (no write token or secrets for forks) is deliberate and documented, and true tamper-proofing must come from a source outside the repo checkout entirely.

Comment thread .github/workflows/public-repo-guard.yml Outdated
…credential names

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 3 new potential issues.

Open in Devin Review

Comment thread .github/workflows/public-repo-guard.yml Outdated
Comment on lines +121 to +129
# So a bare mention stays silent. What fires is a private repo name within ~140
# characters of INTERNAL OPERATIONAL DETAIL — a SCREAMING_CASE credential NAME, a
# secret-binding verb, a service binding, or a secret COUNT. That is the topology
# of what is wired to what, and it is the shape that actually leaked.
#
# Names are NOT hardcoded (this file is public); CI injects them via the
# GUARD_PRIVATE_REPOS variable. Unset locally → this check is skipped.
if [[ -n "${GUARD_PRIVATE_REPOS:-}" ]]; then
OPS_DETAIL='(?:[A-Z][A-Z0-9]*_(?:SECRET|TOKEN|KEY|PASSWORD)|wrangler\s+secret|secret\s+(?:is\s+)?(?:bound|binding|list)|(?:is\s+)?bound\s+on|service\s+binding|\d{2,}\s+secrets)'

@devin-ai-integration devin-ai-integration Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Proximity matching is line-scoped, so multi-line bodies split repo name and ops detail

The proximity pattern uses [^\n]{0,140}? between the repo name and OPS_DETAIL, and rg matches per line, so a real PR body that names a private repo on one line and its bound secret on the next line will not fire. That is a deliberate precision trade-off (bare mentions must stay silent), but it means the common markdown shape — a bullet list where each fact is its own line — is the exact shape that evades the rule. Worth confirming this matches the intended threat model.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread .github/workflows/public-repo-guard.yml Outdated
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…e newline-separated lists

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 5 new potential issues.

Open in Devin Review

Comment thread .github/workflows/public-repo-guard.yml Outdated
Comment on lines +134 to +142
_no_private_repo_config() {
if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then
echo "::error title=public-repo-guard (private-repo-ops)::GUARD_PRIVATE_REPOS is empty (the org variable is unset, renamed, or not visible to this run). Refusing to report a pass with the private-repo rule silently disabled."
exit 2
fi
echo "body-policy: GUARD_PRIVATE_REPOS unset; private-repo-ops rule skipped (local run only)"
}
if [[ -z "${GUARD_PRIVATE_REPOS:-}" ]]; then
_no_private_repo_config

@devin-ai-integration devin-ai-integration Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Fail-closed on empty GUARD_PRIVATE_REPOS may hard-fail every fork PR

The new fail-closed branch turns an empty GUARD_PRIVATE_REPOS into exit 2 whenever GITHUB_ACTIONS=true, and the same semantics were added to scripts/public-repo-guard/body-policy.sh:140-148. Both workflows inject the value from ${{ vars.GUARD_PRIVATE_REPOS }} (.github/workflows/public-repo-guard.yml:118, .github/workflows/public-repo-guard-body.yml:133). Worth confirming before rollout: whether org/repo configuration variables are exposed to pull_request runs originating from a fork, and whether the org variable's visibility list includes this repo. If either is not the case, Secrets + content policy — a required check — will hard-fail (exit 2, indistinguishable to a contributor from a real leak) on every external contribution to what is an examples repo whose main contribution path is forking. If that is a real risk, consider keeping the fail-closed behaviour only for push/workflow_dispatch/same-repo PRs, or asserting the variable's presence once in the workflow with a clearer error.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread .github/workflows/public-repo-guard.yml Outdated
Comment thread .github/workflows/public-repo-guard.yml Outdated
Comment thread scripts/public-repo-guard/body-policy.sh
…e retarget

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…t body-guard merge-gate claim

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…r reports skipped

A job-level 'if' skip still creates a check run (conclusion 'skipped') on
the PR head SHA, required checks treat 'skipped' as satisfied, and the
newest check run of a name wins: with 'edited' (and review events) in the
tree gate's trigger list, editing a PR description or posting a review
re-reported 'Secrets + content policy' as skipped, laundering an earlier
genuine failure on the same commit into a mergeable PR.

The tree workflow now triggers only on events it really scans, running a
full scan on 'edited' (preserving base-retarget coverage), and body-guard
moves to public-repo-guard-body.yml with the issue/comment/review triggers.

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot 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_2443c9a7-d0b0-4ed4-b30f-22f4cdcdf321)

devin-ai-integration[bot]

This comment was marked as resolved.

…d events cancelling tree scans

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
devin-ai-integration[bot]

This comment was marked as resolved.

… cased internal markers

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines +79 to +87
- 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
# The gate only reads the tree; no later step may find the workflow token
# in .git/config — matches every other workflow in this repo.
persist-credentials: false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟨 Body-leak gate executes the pull request's own copy of the scanner, so a PR can weaken the check that scans it

On pull_request events actions/checkout defaults to the PR merge ref, so scripts/public-repo-guard/body-policy.sh executed at .github/workflows/public-repo-guard-body.yml:134 is the version proposed by the PR, not the trusted version from the default branch. A PR that both leaks internal detail in its body and modifies (or deletes) the policy script in the same change would be scanned by its own, potentially neutered, scanner. The token is read-only (persist-credentials: false, contents: read), so this is gate integrity rather than code-execution privilege escalation; the tree gate has the same property inherently because it must scan the PR tree, but the body gate has no such need — it only needs the trusted scanner.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

On pull_request / pull_request_review / pull_request_review_comment events GitHub executes the workflow YAML itself from the PR merge ref, so a PR that can neuter body-policy.sh can equally rewrite the job to emit a green check with no scan; pinning the script checkout to the default branch adds no integrity boundary. On issues/issue_comment events both the workflow and the checkout already come from the default branch, so the scanner is already trusted there.

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

Labels

rr:skip-coderabbit RF.P1 reviewer routing (#1039)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant