-
Notifications
You must be signed in to change notification settings - Fork 0
ci: scan issue and comment bodies — this repo has never scanned one #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
5b4efc0
4108a81
7c178bf
014db46
0e6c593
3716e82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,10 +13,12 @@ name: public-repo-guard | |
| # wave-av/.github must not be able to alter another repo's secret scanner). The | ||
| # gitleaks binary is version-pinned AND SHA-256-verified before it runs. | ||
| # | ||
| # To install on a new repo, copy all three files together: | ||
| # To install on a new repo, copy all five files together: | ||
| # .github/workflows/public-repo-guard.yml | ||
| # .gitleaks.toml | ||
| # scripts/public-repo-guard/content-policy.sh | ||
| # scripts/public-repo-guard/body-policy.sh | ||
| # scripts/public-repo-guard/tests/body-policy.test.sh | ||
| # | ||
| # Scan scope: the published working TREE (gitleaks --no-git), NOT git history. The | ||
| # goal is "what is public right now is clean", so a shallow checkout is sufficient. | ||
|
|
@@ -25,24 +27,52 @@ name: public-repo-guard | |
| # path glob to a repo-root `.guardignore`, or extend the repo-local `.gitleaks.toml`. | ||
|
|
||
| on: | ||
| # `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. | ||
| pull_request: | ||
| types: [opened, edited, reopened, synchronize] | ||
| issues: | ||
| types: [opened, edited] | ||
| issue_comment: | ||
| types: [created, edited] | ||
| # Reviews are the remaining public text channel: a review BODY and a line-level | ||
| # review comment are just as world-readable as an issue comment, and neither | ||
| # arrives as `issue_comment`. Without these two events a leak pasted into a | ||
| # review would bypass the body gate entirely. | ||
| pull_request_review: | ||
| types: [submitted, edited] | ||
| pull_request_review_comment: | ||
| types: [created, edited] | ||
| push: | ||
| branches: [main, master] | ||
| workflow_dispatch: | ||
|
|
||
| # `pull_request`, deliberately NOT `pull_request_target`: a fork PR must never get | ||
| # a write token or repo secrets just because a gate wanted to read its body. | ||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: public-repo-guard-${{ github.ref }} | ||
| cancel-in-progress: true | ||
| # Concurrency is per JOB, not per workflow: the two jobs want opposite behaviour. | ||
| # A workflow-level group would force one policy on both, and it showed: rapid body | ||
| # edits cancelled the tree job over and over, and every cancelled check-run stays | ||
| # attached to the commit, so the PR reported UNSTABLE while the live runs were green. | ||
|
|
||
| jobs: | ||
| guard: | ||
| name: Secrets + content policy | ||
| # Skips issue/comment events (the tree scan has nothing to say about a comment, | ||
| # and the org should not pay for a gitleaks run every time anyone posts one) and | ||
| # skips `edited` (a title or body edit does not change the tree). | ||
| if: >- | ||
| (github.event_name == 'pull_request' && github.event.action != 'edited') | ||
| || github.event_name == 'push' | ||
| || github.event_name == 'workflow_dispatch' | ||
|
Comment on lines
+66
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 Tree scan is skipped on With Was this helpful? React with 👍 or 👎 to provide feedback. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GitHub treats a skipped check-run as passing for required checks, so the intentional |
||
| concurrency: | ||
| group: public-repo-guard-tree-${{ github.event.pull_request.number || github.ref }} | ||
| cancel-in-progress: true | ||
| 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 | ||
| # itself is MIT-licensed and free. Pin the version AND verify the release | ||
|
|
@@ -64,10 +94,145 @@ jobs: | |
| - name: gitleaks (secret scan — published tree) | ||
| run: gitleaks detect --no-git --source . --config .gitleaks.toml --redact --no-banner --exit-code 1 | ||
|
|
||
| - name: Install ripgrep | ||
| run: command -v rg >/dev/null || (sudo apt-get update -qq && sudo apt-get install -y -qq ripgrep) | ||
| # Both policy scripts run every rule through `rg -P` (PCRE2), and not every | ||
| # rg build has it: Ubuntu 22.04's apt package is built WITHOUT PCRE2, which | ||
| # would make every rule fail closed on perfectly clean input. Accept a | ||
| # preinstalled rg only if it actually has PCRE2; otherwise install the | ||
| # official release binary (which bundles PCRE2), pinned and SHA-256-verified | ||
| # the same way gitleaks is above. | ||
| - name: Install ripgrep (PCRE2-enabled, pinned + checksum-verified) | ||
| env: | ||
| RIPGREP_VERSION: "14.1.1" | ||
| RIPGREP_SHA256: "4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e" | ||
| run: | | ||
| if rg --pcre2-version >/dev/null 2>&1; then exit 0; fi | ||
| curl -fsSL --proto '=https' --tlsv1.2 -o ripgrep.tar.gz \ | ||
| "https://github.com/BurntSushi/ripgrep/releases/download/${RIPGREP_VERSION}/ripgrep-${RIPGREP_VERSION}-x86_64-unknown-linux-musl.tar.gz" | ||
| echo "${RIPGREP_SHA256} ripgrep.tar.gz" | sha256sum -c - | ||
| tar -xzf ripgrep.tar.gz --strip-components=1 "ripgrep-${RIPGREP_VERSION}-x86_64-unknown-linux-musl/rg" | ||
| sudo install -m 0755 rg /usr/local/bin/rg | ||
| rm -f rg ripgrep.tar.gz | ||
| rg --pcre2-version | ||
|
|
||
| - name: content policy (WAVE trade-secret / internal-leak gate) | ||
| env: | ||
| GUARD_PRIVATE_REPOS: ${{ vars.GUARD_PRIVATE_REPOS }} | ||
| run: bash scripts/public-repo-guard/content-policy.sh . | ||
|
|
||
| # The body gate's own fixtures. Its negatives are the load-bearing half — a | ||
| # leak gate that blocks legitimate cross-repo references gets switched off, | ||
| # and then it protects nothing. Runs here so a regression is caught by CI | ||
| # rather than by a leak. | ||
| - name: body policy self-test (fixtures) | ||
| run: bash scripts/public-repo-guard/tests/body-policy.test.sh | ||
|
|
||
| # The other half of a public repo's surface. `guard` above scans the published | ||
| # TREE; a PR/issue/comment/review BODY is just as world-readable and, until this | ||
| # job, was scanned by nothing server-side. That gap was real, not theoretical: 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. | ||
| # | ||
| # Honest about what it can and cannot do. On a PR this PREVENTS the merge. On an | ||
| # issue, comment, or review the text is already public the moment it posts, so | ||
| # this is 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' | ||
| || github.event_name == 'pull_request_review' | ||
| || github.event_name == 'pull_request_review_comment' | ||
| concurrency: | ||
| # Keyed on the specific PR / comment / issue rather than github.ref, because | ||
| # issue events all report the default branch and a ref-keyed group would let | ||
| # two comments cancel each other, leaving one unscanned. | ||
| # | ||
| # The comment/review IDs must come FIRST. Review events carry a | ||
| # `pull_request` object in their payload too, so a PR-number-first chain | ||
| # would coalesce every review and line comment on a PR into ONE group — | ||
| # and GitHub keeps at most one pending run per group, silently dropping | ||
| # the rest of a batch unscanned. Each distinct piece of text gets its own | ||
| # group; the PR number is only the fallback for `pull_request` events, | ||
| # where dropping an intermediate edit is fine because the newest queued | ||
| # run scans the current body. | ||
| # | ||
| # cancel-in-progress is deliberately FALSE. Every version of a body deserves a | ||
| # verdict, the job is seconds long, and a cancelled check-run lingers on the | ||
| # commit and makes an otherwise-green PR look broken. | ||
| group: public-repo-guard-body-${{ github.event.comment.id || github.event.review.id || github.event.pull_request.number || github.event.issue.number || github.ref }} | ||
| cancel-in-progress: false | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| # TRUST BOUNDARY: on `pull_request` the default checkout is the PR's merge | ||
| # ref, which would let a fork PR ship a neutered body-policy.sh and pass its | ||
| # own body — the PR would supply the very script that gates it. This job | ||
| # needs no PR code at all (the text under test comes from the event payload), | ||
| # so it checks out the BASE repo's default branch and always runs the | ||
| # already-reviewed copy of the scanner. A PR that changes body-policy.sh | ||
| # still has its copy exercised — by the self-test step in `guard`, against | ||
| # fixtures on the tree, where the required check reviews it. | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 actions/checkout bumped to an unverified v7.0.1 pin, out of step with the rest of the repo This PR changes the checkout pin from Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| with: | ||
| ref: ${{ github.event.repository.default_branch }} | ||
| # 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[bot] marked this conversation as resolved.
devin-ai-integration[bot] marked this conversation as resolved.
|
||
|
|
||
| - name: Install ripgrep (PCRE2-enabled, pinned + checksum-verified) | ||
| env: | ||
| RIPGREP_VERSION: "14.1.1" | ||
| RIPGREP_SHA256: "4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e" | ||
| run: | | ||
| if rg --pcre2-version >/dev/null 2>&1; then exit 0; fi | ||
| curl -fsSL --proto '=https' --tlsv1.2 -o ripgrep.tar.gz \ | ||
| "https://github.com/BurntSushi/ripgrep/releases/download/${RIPGREP_VERSION}/ripgrep-${RIPGREP_VERSION}-x86_64-unknown-linux-musl.tar.gz" | ||
| echo "${RIPGREP_SHA256} ripgrep.tar.gz" | sha256sum -c - | ||
| tar -xzf ripgrep.tar.gz --strip-components=1 "ripgrep-${RIPGREP_VERSION}-x86_64-unknown-linux-musl/rg" | ||
| sudo install -m 0755 rg /usr/local/bin/rg | ||
| rm -f rg ripgrep.tar.gz | ||
| rg --pcre2-version | ||
|
|
||
| # The body is read straight out of the event payload FILE and written to | ||
| # another file. It is never interpolated into a run: block and never placed | ||
| # in an environment variable, so shell metacharacters in a hostile PR body | ||
| # have nothing to act on. jq is preinstalled on the GitHub-hosted images. | ||
| - name: Materialize the untrusted title/body to a file | ||
| run: | | ||
| set -euo pipefail | ||
| mkdir -p "$RUNNER_TEMP/bodyscan" | ||
| # An UNRECOGNIZED payload shape must fail, never quietly scan nothing and | ||
| # report a pass. If the event schema ever moves, this job must go red | ||
| # rather than become a green rubber stamp over an unscanned body. | ||
| if [ "$(jq -r 'has("pull_request") or has("issue") or has("comment") or has("review")' "$GITHUB_EVENT_PATH")" != "true" ]; then | ||
| echo "::error title=public-repo-guard (body-guard)::Event payload contains no pull_request/issue/comment/review object — refusing to report a pass on an unscanned body." | ||
| exit 1 | ||
| fi | ||
| # `.review.body` covers pull_request_review (the summary text of a | ||
| # review); line-level review comments arrive as `.comment.body` on | ||
| # pull_request_review_comment, already extracted below. | ||
| jq -r '[.pull_request.title, .pull_request.body, | ||
| .issue.title, .issue.body, | ||
| .comment.body, | ||
| .review.body] | ||
| | map(select(. != null)) | join("\n")' \ | ||
| "$GITHUB_EVENT_PATH" > "$RUNNER_TEMP/bodyscan/body.txt" | ||
| echo "scanning $(wc -l < "$RUNNER_TEMP/bodyscan/body.txt") line(s) of body text" | ||
|
|
||
| - name: body policy (PR / issue / comment text) | ||
| env: | ||
| GUARD_PRIVATE_REPOS: ${{ vars.GUARD_PRIVATE_REPOS }} | ||
| run: | | ||
| # Bootstrap window: on the PR that first installs this gate, the default | ||
| # branch does not carry body-policy.sh yet, so the trusted checkout above | ||
| # cannot supply it. Only push access to the default branch can create or | ||
| # clear that state — a fork PR cannot remove the script from main — so a | ||
| # loud skip here is not a bypass. The moment the installing PR merges, | ||
| # this path is dead. | ||
| if [ ! -f scripts/public-repo-guard/body-policy.sh ]; then | ||
| echo "::warning title=public-repo-guard (body-guard)::body-policy.sh is not on the default branch yet (gate still bootstrapping) — the body scan activates once the installing PR merges." | ||
| exit 0 | ||
| fi | ||
| bash scripts/public-repo-guard/body-policy.sh "$RUNNER_TEMP/bodyscan/body.txt" | ||
Uh oh!
There was an error while loading. Please reload this page.