-
Notifications
You must be signed in to change notification settings - Fork 0
ci: scan issue and comment bodies — this repo has never scanned one #45
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 2 commits
8f29c21
3dac59f
d9a46a2
852f887
d271bc1
52a7be5
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,11 @@ 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 four files together: | ||
| # .github/workflows/public-repo-guard.yml | ||
| # .gitleaks.toml | ||
| # scripts/public-repo-guard/content-policy.sh | ||
| # scripts/public-repo-guard/body-policy.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,30 +26,59 @@ 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: | ||
|
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. P2: Comment scanning is still incomplete because the workflow only subscribes to Prompt for AI agents |
||
| types: [created, edited] | ||
|
Comment on lines
+36
to
+39
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. 🔍 Review-comment bodies are still unscanned
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| 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). | ||
| # PR `edited` events DO run this job: this is the one check name branch | ||
| # protection requires, so the body scan must produce a fresh verdict under it, | ||
| # or an edit could introduce a leak behind a stale green check. The tree-scan | ||
| # steps below skip `edited` individually (an edit does not change the tree). | ||
| if: >- | ||
| github.event_name == 'pull_request' | ||
| || github.event_name == 'push' | ||
| || github.event_name == 'workflow_dispatch' | ||
|
qodo-code-review[bot] marked this conversation as resolved.
devin-ai-integration[bot] marked this conversation as resolved.
|
||
| concurrency: | ||
| # `edited` runs get their own group and are never cancelled: each body | ||
| # version deserves a verdict, the run is seconds long, and a body-only run | ||
| # must never cancel (or be cancelled by) an in-flight tree scan, since a | ||
| # cancelled tree scan would leave a commit's tree unscanned. Tree runs keep | ||
| # cancel-in-progress: a superseded scan of an outdated commit is pure waste. | ||
| group: public-repo-guard-tree-${{ github.event.pull_request.number || github.ref }}-${{ github.event.action == 'edited' && 'body' || 'tree' }} | ||
| cancel-in-progress: ${{ github.event.action != 'edited' }} | ||
| 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 | ||
| # tarball's SHA-256 before extracting, so a tampered or MITM'd download can | ||
| # never execute inside the security gate. | ||
| - name: Install gitleaks (pinned + checksum-verified) | ||
| if: github.event.action != 'edited' | ||
| env: | ||
| GITLEAKS_VERSION: "8.30.1" | ||
| GITLEAKS_SHA256: "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb" | ||
|
|
@@ -62,12 +92,111 @@ jobs: | |
| gitleaks version | ||
|
|
||
| - name: gitleaks (secret scan — published tree) | ||
| if: github.event.action != 'edited' | ||
| 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) | ||
|
|
||
| - name: content policy (WAVE trade-secret / internal-leak gate) | ||
| if: github.event.action != 'edited' | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
|
||
| 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 | ||
|
Comment on lines
+112
to
+113
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. 🟨 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 Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
| # A PR body is merge-blocking, so it is scanned HERE, inside the one check | ||
| # name branch protection requires. Scanning it only in a differently named | ||
| # job would let a body edit hide behind this check's stale green result. | ||
| # Same handling discipline as body-guard below: the untrusted text goes | ||
| # payload file to scratch file, never through a run: block or an env var. | ||
| - name: Materialize the untrusted PR title/body to a file | ||
| if: github.event_name == 'pull_request' | ||
| run: | | ||
| set -euo pipefail | ||
| mkdir -p "$RUNNER_TEMP/bodyscan" | ||
| # An UNRECOGNIZED payload shape must fail, never quietly scan nothing | ||
| # and report a pass. | ||
| if [ "$(jq -r 'has("pull_request")' "$GITHUB_EVENT_PATH")" != "true" ]; then | ||
| echo "::error title=public-repo-guard (guard)::Event payload contains no pull_request object; refusing to report a pass on an unscanned body." | ||
| exit 1 | ||
| fi | ||
| jq -r '[.pull_request.title, .pull_request.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 title/body, merge-blocking) | ||
| if: github.event_name == 'pull_request' | ||
| env: | ||
| GUARD_PRIVATE_REPOS: ${{ vars.GUARD_PRIVATE_REPOS }} | ||
| run: bash scripts/public-repo-guard/body-policy.sh "$RUNNER_TEMP/bodyscan/body.txt" | ||
|
|
||
| # The other half of a public repo's surface. `guard` above scans the published | ||
| # TREE and (on PRs) the merge-blocking title/body; an issue or comment 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. Issue and comment 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. PR bodies are NOT scanned here: they are merge-blocking, so | ||
| # they belong to `guard`, the check name branch protection actually requires. | ||
| body-guard: | ||
| name: Body content policy | ||
| if: github.event_name == 'issues' || github.event_name == 'issue_comment' | ||
| concurrency: | ||
| # Keyed on the specific 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. | ||
| # | ||
| # 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.issue.number || github.ref }} | ||
| cancel-in-progress: false | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - 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 | ||
|
Comment on lines
+169
to
+174
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. 🔍 Fork PRs run the body gate from their own branch, so a fork can neutralize it
Was this helpful? React with 👍 or 👎 to provide feedback.
devin-ai-integration[bot] marked this conversation as resolved.
|
||
|
|
||
| - name: Install ripgrep | ||
| run: command -v rg >/dev/null || (sudo apt-get update -qq && sudo apt-get install -y -qq ripgrep) | ||
|
|
||
| # 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 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("issue") or has("comment")' "$GITHUB_EVENT_PATH")" != "true" ]; then | ||
| echo "::error title=public-repo-guard (body-guard)::Event payload contains no issue/comment object; refusing to report a pass on an unscanned body." | ||
| exit 1 | ||
| fi | ||
| jq -r '[.issue.title, .issue.body, | ||
| .comment.body] | ||
| | map(select(. != null)) | join("\n")' \ | ||
| "$GITHUB_EVENT_PATH" > "$RUNNER_TEMP/bodyscan/body.txt" | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
|
||
| echo "scanning $(wc -l < "$RUNNER_TEMP/bodyscan/body.txt") line(s) of body text" | ||
|
|
||
| - name: body policy (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" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| #!/usr/bin/env bash | ||
| # WAVE public-repo BODY policy — the internal-leak gate for PR/issue/comment text. | ||
| # | ||
| # Companion to content-policy.sh. That script scans the published working TREE; | ||
| # this one scans the other half of a public repo's surface: pull-request titles | ||
| # and bodies, issue bodies, and comment bodies. Those are equally world-readable | ||
| # and, until this script existed, were scanned by NOTHING server-side. That gap | ||
| # was not theoretical — a PR was merged whose wrangler.toml was correctly BLOCKED | ||
| # for naming a private repo while the PR body named the same repo, with more | ||
| # operational detail attached, and sailed through. | ||
| # | ||
| # Usage: scripts/public-repo-guard/body-policy.sh <file> | ||
| # <file> holds the untrusted text, already materialized to disk. It is passed as | ||
| # a PATH and only ever read — the body is never interpolated into a command line | ||
| # or an environment variable, so no amount of shell metacharacters in a PR body | ||
| # can influence what runs here. | ||
| # | ||
| # Exit: 0 clean · 1 blocking violation · 2 scanner error (fail closed). | ||
| # | ||
| # Allowlisting: a line carrying `guard:allow <reason>` is exempt (an accidental | ||
| # leak never carries the marker; a deliberate one is visible in a public diff), as | ||
| # is any line matching the ABOUT-THE-CONTROL allowlist below. | ||
| set -uo pipefail | ||
|
|
||
| FILE="${1:-}" | ||
| [[ -n "$FILE" && -f "$FILE" ]] || { echo "::error::body-policy: usage: body-policy.sh <file>"; exit 2; } | ||
| command -v rg >/dev/null 2>&1 || { echo "::error::body-policy: ripgrep (rg) required"; exit 2; } | ||
|
|
||
| VIOLATIONS=0 | ||
|
|
||
| # Lines that TALK ABOUT the control rather than leaking through it. Without this, | ||
| # the gate blocks its own pull requests and every security discussion — the | ||
| # self-referential trap that gets a gate switched off. Ported verbatim in intent | ||
| # from the client-side gate's allowlist, which was built for exactly this. | ||
| ABOUT_THE_CONTROL='(public-repo-guard|body-policy|content-policy|public-github-write-gate|\bNDA\s+(gate|guard|policy|denylist|sweep|scan|hook)\b|\bno\s+NDA\b|responsib\w*\s+disclos|SECURITY\.md)' | ||
|
|
||
| # check <BLOCK|WARN> <name> <regex> <why> | ||
| 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 | ||
| (( rc == 1 )) && return 0 | ||
| # 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. | ||
| # | ||
| # Each stage's exit code is checked EXPLICITLY: 1 (nothing survived the filter) | ||
| # is a normal clean result, but >=2 is a broken filter and must fail closed, | ||
| # exactly like the scan above. A bare `|| true` here would turn a filter crash | ||
| # into an empty match set: a false pass from the one stage meant to narrow, | ||
| # never erase, the raw hits. | ||
| local filtered matches frc | ||
| filtered="$(printf '%s' "$raw" | rg -vN -- 'guard:allow[[:space:]]+[^[:space:]]')"; frc=$? | ||
| if (( frc >= 2 )); then | ||
| echo "::error title=public-repo-guard ($name)::ripgrep failed (exit $frc) applying the guard:allow filter for rule '$name'; failing closed." | ||
| exit 2 | ||
| fi | ||
| matches="$(printf '%s' "$filtered" | rg -vNiP -- "$ABOUT_THE_CONTROL")"; frc=$? | ||
| if (( frc >= 2 )); then | ||
| echo "::error title=public-repo-guard ($name)::ripgrep failed (exit $frc) applying the about-the-control filter for rule '$name'; failing closed." | ||
| exit 2 | ||
| fi | ||
| [[ -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' | ||
|
|
||
| # --- 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[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| # --- 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' | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| # --- Private repo + operational detail (PROXIMITY, not bare name) ------------ | ||
| # The BODY profile deliberately DIVERGES from the FILE profile here, and the | ||
| # divergence is the whole design. content-policy.sh blocks a bare private-repo | ||
| # name outright, which is right for a checked-in file. Applying that to bodies | ||
| # would be unusable: a sweep of public issues found 134 LEGITIMATE cross-repo | ||
| # references ("companion to <private-repo>#260"). 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. 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[bot] marked this conversation as resolved.
Outdated
|
||
| _ALT='' | ||
| IFS=', ' read -r -a _PRIV <<< "$GUARD_PRIVATE_REPOS" | ||
| for _name in "${_PRIV[@]}"; do | ||
| [[ -z "$_name" ]] && continue | ||
| # Regex-escape so metacharacters in a name match literally. | ||
| _esc="$(printf '%s' "$_name" | sed -E 's/[][(){}.^$*+?|\\]/\\&/g')" | ||
| _ALT="${_ALT:+$_ALT|}${_esc}" | ||
| done | ||
| if [[ -n "$_ALT" ]]; then | ||
| # Both orders: name-then-detail and detail-then-name. | ||
| check BLOCK private-repo-ops \ | ||
| "(?i)\\b(?:${_ALT})\\b[^\\n]{0,140}?\\b${OPS_DETAIL}|${OPS_DETAIL}[^\\n]{0,140}?\\b(?:${_ALT})\\b" \ | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
|
||
| 'A private WAVE repo named alongside internal operational detail (credential name, secret binding, or secret count) — the wiring topology is not public' | ||
| fi | ||
| fi | ||
|
|
||
| if (( VIOLATIONS > 0 )); then | ||
| echo "::error::public-repo-guard: $VIOLATIONS blocking body-policy violation(s) — see annotations above." | ||
| exit 1 | ||
| fi | ||
| echo "public-repo-guard: body policy OK" | ||
Uh oh!
There was an error while loading. Please reload this page.