Skip to content

feat(guard): scan PR/issue/comment bodies, not just the published tree - #33

Open
yakimoto wants to merge 1 commit into
mainfrom
feat/public-repo-guard-body-scan
Open

feat(guard): scan PR/issue/comment bodies, not just the published tree#33
yakimoto wants to merge 1 commit into
mainfrom
feat/public-repo-guard-body-scan

Conversation

@yakimoto

@yakimoto yakimoto commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Brings this repo in line with wave-moq-edge, where this is already merged and running.

public-repo-guard has always scanned the published working tree. It has never scanned a pull request title or body, an issue, or a comment — and those are just as world-readable. The gap was measured, not theorised: a PR was correctly blocked for naming a private repo in a config file, while that same name, carrying more operational detail with it, sat unchallenged in the PR body and merged.

What this adds

A body-guard job on pull_request, issues, and issue_comment — including the edited types, because a body can be made to leak long after it is first opened — plus a vendored body-policy.sh beside the existing content-policy.sh.

The body profile is deliberately not the file profile

content-policy.sh blocks a bare private-repo name outright, which is right for a checked-in file. Applying that rule to bodies would be unusable: an earlier sweep found 134 legitimate cross-repo references in public issue text, of the ordinary "companion to #260" kind. A gate that fires on all of those gets switched off, and then it protects nothing.

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.

Safety properties

  • Untrusted text is read from the event payload file and written to another file, never through argv or an environment variable.
  • pull_request, not pull_request_target — a fork PR gets no write token and no secrets.
  • An unrecognised payload shape fails the job rather than reporting a pass over an unscanned body.
  • Annotations print line numbers only; echoing a hit would republish the leak.
  • The tree scan is skipped on issue events and on body edits, so this adds no redundant runs.

Honest about the limits

On a pull request this prevents the merge. On an issue or comment the text is public the instant it posts, so this is detection. Only a client-side pre-write hook stops that class before publication.

Verification

21 fixtures, run in CI. The negatives are the load-bearing half — bare cross-references, a credential name with no repo nearby, the CI runner path, and text quoting the policy all stay silent. Proven live in wave-moq-edge on a real issue_comment event.

Files are copied verbatim from wave-moq-edge@main; the workflow's actions/checkout pin is preserved as it was in this repo.


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


Note

Medium Risk
Changes merge-blocking CI security policy and how PR/issue text is evaluated; design mitigates fork PR token exposure and re-leak via annotations, but false positives or bypasses on body text could block merges or miss leaks.

Overview
Extends public-repo-guard so world-readable PR/issue/comment text is gated, not only the checked-in tree. A new body-guard job runs on pull_request, issues, and issue_comment (including edited), materializes title/body from the event payload into a temp file, and runs vendored body-policy.sh.

The existing guard tree job is narrowed: it skips issue/comment events and PR edited (body edits do not change the tree). Concurrency is per job—tree scans can cancel in progress; body scans do not, so rapid edits do not leave stale failed checks on the commit.

body-policy.sh mirrors many file-tree rules (credentials, Cloudflare account_id, Tailscale IPs, operator paths, internal markers) but does not block bare private-repo names in prose. It only blocks a configured private repo when it appears within ~140 characters of operational detail (credential names, secret binding language, service bindings, secret counts), with guard:allow and “about the control” allowlists; hit annotations redact matched text.

The guard job also runs body-policy.test.sh fixture self-tests in CI.

Reviewed by Cursor Bugbot for commit 0178ffe. Configure here.


Summary by cubic

Extend public-repo-guard to scan PR titles/bodies, issues, and comments, not just the working tree. Blocks merges on PRs with leaked internal info; detects issue/comment leaks for fast redaction.

  • New Features

    • Added body-guard job for pull_request (opened/edited/reopened/synchronize), issues (opened/edited), and issue_comment (created/edited), using scripts/public-repo-guard/body-policy.sh.
    • Body policy allows bare private-repo mentions; blocks when a private repo from GUARD_PRIVATE_REPOS appears within ~140 chars of operational detail (SECRET/TOKEN names, wrangler secret binding, service binding, secret counts).
    • Also blocks common credentials and infra IDs: Stripe, Anthropic, GitHub, Supabase, AWS AKID, private keys; Cloudflare account_id, Tailscale CGNAT IPs, operator home paths; unquoted “internal-only” markers.
    • Safe handling: reads body from the event payload file, redacts matches in annotations, fails closed on unknown payload shapes; uses pull_request (not pull_request_target).
    • Concurrency: tree job skips issue/comment events and edited; body job is keyed per PR/issue/comment with cancel-in-progress=false. Adds fixture tests via scripts/public-repo-guard/tests/body-policy.test.sh. Installs ripgrep; keeps gitleaks and actions/checkout pins.
  • Migration

    • Set repo/org var GUARD_PRIVATE_REPOS with a comma-separated list of private repo names.
    • When installing elsewhere, copy: .github/workflows/public-repo-guard.yml, .gitleaks.toml, scripts/public-repo-guard/content-policy.sh, scripts/public-repo-guard/body-policy.sh.

Written for commit 0178ffe. Summary will update on new commits.

Review in cubic

Vendored verbatim from wave-moq-edge@main, where this is merged
and proven live. The actions/checkout pin already in this repo is
preserved.

Refs wave-av/claude-workstation#481

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 0178ffe

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@cursor

cursor Bot commented Jul 27, 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_0ac37abf-a142-451b-bff8-4e0be8e7982d)

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request adds a Bash body-policy scanner, fixture tests, and GitHub Actions integration for scanning pull request, issue, and comment text. The workflow also refines event triggers and concurrency handling for the existing tree guard.

Changes

Public repository body guard

Layer / File(s) Summary
Body policy scanner
scripts/public-repo-guard/body-policy.sh
Validates input and rg, scans for credentials, infrastructure identifiers, internal-only markers, and private-repository operational details, then exits with blocking or fail-closed statuses.
Policy fixture validation
scripts/public-repo-guard/tests/body-policy.test.sh
Tests blocking and passing fixtures, output redaction, private-repository matching, and invalid-input handling.
Workflow event integration
.github/workflows/public-repo-guard.yml
Adds event-specific triggers, job-level concurrency, body-policy self-tests, and a body-guard job that materializes event text before scanning it.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHub
  participant body-guard
  participant EventPayloadFile
  participant body-policy.sh
  GitHub->>body-guard: emit PR, issue, or comment event
  body-guard->>EventPayloadFile: validate and write title/body
  body-guard->>body-policy.sh: scan materialized text
  body-policy.sh-->>body-guard: return policy status
Loading
🚥 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: adding body scanning for PRs, issues, and comments.
Description check ✅ Passed The description is directly about the workflow and body-policy changes in this PR.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/public-repo-guard-body-scan
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/public-repo-guard-body-scan

Warning

Review ran into problems

🔥 Problems

These MCP integrations need to be re-authenticated in the Integrations settings: Sentry


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

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

Actionable comments posted: 4

🤖 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/public-repo-guard.yml:
- Around line 128-133: Update the body-guard checkout step using
actions/checkout to set persist-credentials to false in its with configuration,
while preserving the existing sparse-checkout settings.

In `@scripts/public-repo-guard/body-policy.sh`:
- Around line 81-85: Remove the stale shellcheck disable comments immediately
above the cf-account-id and abs-user-path checks, since their regexes and
messages no longer reference shell variables or trigger SC2016. Keep both BLOCK
rules and their existing detection patterns and messages unchanged.
- Around line 38-78: Update check() to accept an optional parameter controlling
whether the guard:allow filter applies, defaulting to the current bypass
behavior for judgment-call rules. Pass the override-disabled value when invoking
the hard credential-format rules stripe-live-key, stripe-account, anthropic-key,
github-pat, supabase-pat, aws-akid, and private-key, so guard:allow cannot
suppress those matches; add or update body-policy.test.sh fixtures to verify
this behavior.
- Around line 87-100: Update the internal-marker regular expression in the BLOCK
check to include left and right smart single quotes in both lookaround
quote-exemption classes, while preserving the existing straight, smart double,
and backtick quote handling.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0b59fb84-9d8c-45a5-806d-d3ea14e3025b

📥 Commits

Reviewing files that changed from the base of the PR and between de34314 and 0178ffe.

📒 Files selected for processing (3)
  • .github/workflows/public-repo-guard.yml
  • scripts/public-repo-guard/body-policy.sh
  • scripts/public-repo-guard/tests/body-policy.test.sh
📜 Review details
🧰 Additional context used
🪛 Shellcheck (0.11.0)
scripts/public-repo-guard/tests/body-policy.test.sh

[info] 83-83: Expressions don't expand in single quotes, use double quotes for that.

(SC2016)

🪛 zizmor (1.26.1)
.github/workflows/public-repo-guard.yml

[warning] 128-133: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🔍 Remote MCP Linear

Additional context for PR review

  • I found no exact Linear matches for public-repo-guard, body-policy.sh, or body-guard in issues or documents.
  • The closest related tracked item is WAVE-3524 — “Phase 7: GitHub Actions modernization”; it is a canceled backlog issue created 2026-01-02 and canceled 2026-07-03, with no attachments, documents, or relations listed.
  • Other nearby workflow/security-hardening issues are:
    • WAVE-3194 — “Create smart-enforcement.sh workflow” (canceled backlog)
    • WAVE-3212 — “Add missing GitHub secrets from .env.local” (canceled backlog)
    • WAVE-3523 — “Phase 5.2: Pin third-party actions to SHA” (canceled backlog)
  • These records suggest the PR fits into a broader GitHub Actions / workflow-security effort, but I did not find any Linear item with details specific to this exact PR.
🔇 Additional comments (3)
scripts/public-repo-guard/body-policy.sh (1)

1-36: LGTM!

Also applies to: 102-140

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

1-109: LGTM!

.github/workflows/public-repo-guard.yml (1)

16-20: LGTM!

Also applies to: 29-63, 95-127, 135-163

Comment on lines +128 to +133
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Security Misconfiguration (CWE-522): Insufficiently Protected Credentials

Reachability: Internal

Set persist-credentials: false on the body-guard checkout.

This checkout doesn't need to push anything, but by default actions/checkout persists the job's token in .git/config, making it readable by any later step (or a compromised dependency) in this job. Since permissions: contents: read already limits scope, this is defense-in-depth, but it's a one-line, zero-downside hardening on a job that scans untrusted text.

🔒 Suggested fix
       - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd  # v5.0.1
         with:
+          persist-credentials: false
           sparse-checkout: scripts/public-repo-guard
           sparse-checkout-cone-mode: false
📝 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
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.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
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false
# 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
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 128-133: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 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/public-repo-guard.yml around lines 128 - 133, Update the
body-guard checkout step using actions/checkout to set persist-credentials to
false in its with configuration, while preserving the existing sparse-checkout
settings.

Source: Linters/SAST tools

Comment on lines +38 to +78
check() {
local sev="$1" name="$2" re="$3" why="$4"
[[ -z "$re" ]] && { echo "::error::body-policy: internal bug — empty regex for rule '$name'"; exit 2; }
# rg exit: 0=match, 1=no match, >=2=real error → FAIL CLOSED. A gate that passes
# because its scanner broke is worse than no gate: it reports success.
local raw rc
raw="$(rg -nP --no-filename -- "$re" "$FILE" 2>/dev/null)"; rc=$?
if (( rc >= 2 )); then
echo "::error title=public-repo-guard ($name)::ripgrep failed (exit $rc) scanning rule '$name' — failing closed."
exit 2
fi
# Filter with rg, not grep: BSD/macOS grep has no -P, so a `grep -P` allowlist
# silently errors out locally while working on GNU/CI — the gate would then
# disagree with itself depending on where it ran. rg is already required above.
local matches
matches="$(printf '%s' "$raw" \
| rg -vN -- 'guard:allow[[:space:]]+[^[:space:]]' \
| rg -vNiP -- "$ABOUT_THE_CONTROL" || true)"
[[ -z "$matches" ]] && return 0
local count; count="$(printf '%s\n' "$matches" | grep -c '')"
# Print the LINE NUMBER only — never the matched text. This annotation is itself
# world-readable, so echoing the hit would re-publish the very thing we caught.
echo "::group::[$sev] $name — $why"
printf '%s\n' "$matches" | sed -E 's/^([0-9]+):.*/ line \1: «match redacted — view the body to see it»/'
echo "::endgroup::"
if [[ "$sev" == "BLOCK" ]]; then
echo "::error title=public-repo-guard ($name)::$why — $count occurrence(s) in the title/body. Edit the body to remove it, then re-run."
VIOLATIONS=$((VIOLATIONS+1))
else
echo "::warning title=public-repo-guard ($name)::$why — $count occurrence(s) (non-blocking; review)."
fi
}

# --- Credential formats — never legitimate in prose --------------------------
check BLOCK stripe-live-key '(sk|rk)_live_[A-Za-z0-9]{16,}' 'Live Stripe secret/restricted key'
check BLOCK stripe-account 'acct_[A-Za-z0-9]{16,}' 'Live Stripe account ID — financial infra, never publish'
check BLOCK anthropic-key 'sk-ant-(api|admin)[0-9]{2}-[A-Za-z0-9_-]{20,}' 'Real Anthropic API/admin key'
check BLOCK github-pat 'github_pat_[A-Za-z0-9_]{30,}' 'GitHub fine-grained PAT'
check BLOCK supabase-pat 'sbp_[a-f0-9]{40}' 'Supabase personal access token'
check BLOCK aws-akid 'AKIA[0-9A-Z]{16}' 'AWS access key ID'
check BLOCK private-key '-----BEGIN [A-Z ]*PRIVATE KEY-----' 'Embedded private key material'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Other (CWE-807)

Reachability: External

guard:allow bypasses hard credential-format BLOCKs too — not just the judgment-call rules.

The guard:allow <reason> filter at Line 54 runs unconditionally inside check(), before any rule-specific logic, so it exempts a whole line from every rule that scans it — including the format-specific credential checks at Lines 72-78 (stripe-live-key, stripe-account, anthropic-key, github-pat, supabase-pat, aws-akid, private-key). A line like sk_live_XXXXXXXXXXXXXXXX guard:allow debug fully defeats the live-Stripe-key block.

The header (Lines 20-22) and the internal-marker section (Lines 97-99) explicitly justify this tradeoff for context-dependent judgment calls ("a deliberate one is visible in a public diff"), but that rationale doesn't hold for hard credential formats: there is no legitimate reason a real live key/PAT/private-key match should ever be allowed through with a comment, and unlike the internal-marker case, exposure already happens the instant the text is posted regardless of whether the check blocks the PR. This scenario also isn't covered by any fixture in body-policy.test.sh.

🔒 Suggested fix: opt specific rules out of the guard:allow filter
-check() {
-  local sev="$1" name="$2" re="$3" why="$4"
+check() {
+  local sev="$1" name="$2" re="$3" why="$4" allow_override="${5:-yes}"
   ...
   local matches
-  matches="$(printf '%s' "$raw" \
-    | rg -vN -- 'guard:allow[[:space:]]+[^[:space:]]' \
-    | rg -vNiP -- "$ABOUT_THE_CONTROL" || true)"
+  matches="$raw"
+  if [[ "$allow_override" == "yes" ]]; then
+    matches="$(printf '%s' "$matches" | rg -vN -- 'guard:allow[[:space:]]+[^[:space:]]' || true)"
+  fi
+  matches="$(printf '%s' "$matches" | rg -vNiP -- "$ABOUT_THE_CONTROL" || true)"

Then call the hard-format rules with the override disabled, e.g. check BLOCK stripe-live-key '...' '...' no.

🤖 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 `@scripts/public-repo-guard/body-policy.sh` around lines 38 - 78, Update
check() to accept an optional parameter controlling whether the guard:allow
filter applies, defaulting to the current bypass behavior for judgment-call
rules. Pass the override-disabled value when invoking the hard credential-format
rules stripe-live-key, stripe-account, anthropic-key, github-pat, supabase-pat,
aws-akid, and private-key, so guard:allow cannot suppress those matches; add or
update body-policy.test.sh fixtures to verify this behavior.

Comment on lines +81 to +85
# 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'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale shellcheck disable=SC2016 comments don't match the code they annotate.

Both disables claim a literal $CLOUDFLARE_ACCOUNT_ID/$HOME reference justifies suppressing SC2016, but neither the regex nor the "why" text on Lines 82/85 actually contains those variable names — nothing here would trigger SC2016 in the first place. This looks like the "why" messages once told the operator which env var to use and that guidance was dropped, leaving a dangling disable comment and a less actionable annotation ("reference the env var instead" without naming it).

✏️ Suggested fix
-# 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'
-# 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'

Or, if the intent was to actually name the env var in the message, restore that instead of removing the disable.

📝 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
# 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'
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'
check BLOCK abs-user-path '/(Users|home)/(?!runner/)[a-z][a-z0-9._-]+/' 'Operator absolute home path — leaks identity and local layout'
🤖 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 `@scripts/public-repo-guard/body-policy.sh` around lines 81 - 85, Remove the
stale shellcheck disable comments immediately above the cf-account-id and
abs-user-path checks, since their regexes and messages no longer reference shell
variables or trigger SC2016. Keep both BLOCK rules and their existing detection
patterns and messages unchanged.

Comment on lines +87 to +100
# --- Self-identified internal material ---------------------------------------
# USE vs MENTION. A body that SAYS "internal-only" is leaking; a body that QUOTES
# the phrase is describing a policy — including this one. The lookarounds exempt a
# marker wrapped in straight, smart, or backtick quotes.
#
# Not hypothetical: the first run of this job failed on its own pull request,
# because a review bot had edited the PR body to summarize the change and its
# summary quoted the phrase verbatim. The line-level allowlist could not help —
# that line named no gate. Only use-vs-mention separates the two.
#
# A quoted marker is also a trivial bypass, and that is an accepted trade. The
# threat here is the ACCIDENTAL paste; a deliberate evader has easier routes, and
# `guard:allow <reason>` already exists as the honest, visible one.
check BLOCK internal-marker '(?<![“"'"'"'`])\b(internal[- ]only|do\s+not\s+(share|publish|distribute)|for\s+internal\s+use)\b(?![”"'"'"'`])' 'Text self-identifies as not-for-public'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Extract only the internal-marker check line and replay it through a stub
# check() to see the literal regex bash produces (no repo file is executed).
LN=$(grep -n "check BLOCK internal-marker" scripts/public-repo-guard/body-policy.sh | cut -d: -f1)
LINE=$(sed -n "${LN}p" scripts/public-repo-guard/body-policy.sh)
echo "extracted line:"; echo "$LINE"
check() { printf 'REGEX=%s\n' "$3" > /tmp/marker_regex.txt; }
eval "$LINE"
echo "---"; cat /tmp/marker_regex.txt

if command -v rg >/dev/null && rg --pcre2-version >/dev/null 2>&1; then
  RE=$(sed -n 's/^REGEX=//p' /tmp/marker_regex.txt)
  for s in "This is 'internal-only' text." "This is ‘internal-only’ text." "This is internal-only text."; do
    printf '%s\n' "$s" > /tmp/t.txt
    echo -n "[$s] -> "
    rg -nP -- "$RE" /tmp/t.txt || echo "no match (exempt)"
  done
fi

Repository: wave-av/wave-profiles

Length of output: 654


Add / to the quote-exemption class. The pattern already exempts straight single quotes, but ‘internal-only’ still matches. If smart single quotes should count as quoted markers too, include them in the lookaround class.

🤖 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 `@scripts/public-repo-guard/body-policy.sh` around lines 87 - 100, Update the
internal-marker regular expression in the BLOCK check to include left and right
smart single quotes in both lookaround quote-exemption classes, while preserving
the existing straight, smart double, and backtick quote handling.

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