feat(workflows): add reusable secret-leak check for per-PR scanning#24
Conversation
Adds `reusable-secret-leak-check.yml` to the org-wide reusable
workflow library. Pairs with the weekly org-wide audit in
geolonia-operations (`secret-leak-audit.yml`): the audit catches what
is already in main history; this reusable prevents new secret commits
from getting in.
## Behavior
Scans only the commits added by the PR (the diff against the base
ref), via `betterleaks git . --log-opts="<base>..<head>"`. Runs the
upstream betterleaks container pinned by immutable digest. Emits:
- Inline workflow annotations on the PR's "Files changed" view
- An idempotent PR comment (updated across re-pushes via a hidden
marker line)
- A workflow artifact with the redacted JSON report (30-day retention)
- Optional job failure when findings exist (the gate that blocks merge)
## Inputs
| Name | Default | Purpose |
|---|---|---|
| `betterleaks-image` | digest of v1.2.0 | Override only for testing newer versions. |
| `fail-on-findings` | `true` | Block-by-default. Set to `false` for warn-only mode during cleanup of legacy false positives. |
| `config-path` | `''` | Optional path to a `.betterleaks.toml` config in the caller repo. |
| `base-ref` | `''` | Defaults to the PR base; override only for non-pull_request triggers. |
| `validate-secrets` | `false` | Active-secret HTTP validation. Off by default for PR triggers. |
## Permissions
`contents: read` + `pull-requests: write` (for the comment). No org-
level token needed; `GITHUB_TOKEN` is sufficient since the scan is
scoped to the caller repo.
## Consumer pattern
```yaml
name: Secret Leak Check
on:
pull_request:
branches: [main]
jobs:
scan:
uses: geolonia/.github/.github/workflows/reusable-secret-leak-check.yml@v1
```
Override defaults per repo if needed:
```yaml
with:
fail-on-findings: false
config-path: .betterleaks.toml
```
## Failure semantics
- Schema-validates the JSON output is `null` (clean) or array
(findings). Anything else triggers `scan_failed=true` and an
explicit job failure.
- `--redact` on by default: detected values never appear in logs,
annotations, or PR comments.
- 60,000-char cap on the inline comment body; full report in the
artifact.
## Related
- Tracking issue: geolonia/geolonia-operations#60
- Pair workflow: `secret-leak-audit.yml` in geolonia-operations
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughThis pull request adds a reusable GitHub Actions workflow that scans PR-introduced commits for secret leaks using betterleaks, validates and counts JSON findings, emits inline annotations, posts or updates an idempotent PR comment, uploads findings as an artifact, and optionally fails the job; it also adds workflow-template metadata for the new workflow. ChangesSecret Leak Detection Workflow
Sequence DiagramsequenceDiagram
participant Workflow
participant Git
participant Container as betterleaks
participant GitHubAPI
participant Artifacts
Workflow->>Git: Checkout repo (full history)
Git-->>Workflow: Repository ready
Workflow->>Workflow: Resolve base & head SHAs
Workflow->>Container: Pull & verify image
Container-->>Workflow: Image ready
Workflow->>Container: Run scan (PR commits only)
Container-->>Workflow: findings.json + exit code
Workflow->>Workflow: Validate findings.json
alt Scan succeeded & findings found
Workflow->>GitHubAPI: Emit inline annotations
GitHubAPI-->>Workflow: Annotations posted
end
Workflow->>Workflow: Build idempotent PR comment (hidden marker)
Workflow->>GitHubAPI: Post/update PR comment
GitHubAPI-->>Workflow: Comment posted
Workflow->>Artifacts: Upload findings.json
Artifacts-->>Workflow: Artifact stored
alt Findings non-zero & fail-on-findings enabled
Workflow->>Workflow: ❌ Fail job
else Scan failed before JSON
Workflow->>Workflow: ❌ Fail job (scan error)
else Success
Workflow->>Workflow: ✓ Job passes
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Makes the reusable secret-leak workflow discoverable via GitHub's "New workflow" UI. When someone clicks "New workflow" in any geolonia-org repo, they will see a "Secret Leak Check (per PR)" tile under Security; selecting it copies the wrapper into their repo. Files: - `workflow-templates/secret-leak-check.yml` - thin wrapper that calls the reusable `reusable-secret-leak-check.yml@v1` (added in the same PR). Per-repo overrides exposed as commented-out lines the contributor can uncomment. - `workflow-templates/secret-leak-check.properties.json` - GitHub UI metadata (name, description, category=Security, iconName=shield). This mirrors the existing template pattern (see `publish-techdocs.yml` / `cdk-deploy-monitor.yml`).
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/reusable-secret-leak-check.yml:
- Around line 163-174: The jq snippet that emits GitHub workflow annotations is
interpolating untrusted fields (.Attributes.path, .RuleID,
.Attributes["git.sha"]) directly into the ::error command and must strip CR/LF
characters to prevent workflow-command injection; update the jq expression used
on findings.json (the jq pipeline calling .[] | "::error file=" + ...) to
sanitize each interpolated value by removing \r and \n (e.g., use jq's gsub to
replace "\\r" and "\\n" with empty strings) for .Attributes.path, .RuleID and
the git SHA slice before concatenation so no newline/carriage-return can break
the annotation protocol.
- Around line 230-248: The PR-comment step can fail for fork/Dependabot PRs due
to GITHUB_TOKEN write restrictions; make it best-effort by either skipping it
for forked PRs or ignoring failures: update the "Post / update PR comment" step
to include a conditional skip (e.g. extend the existing if to require
github.event.pull_request.head.repo.fork == false) or wrap the shell commands
(the gh api and gh pr comment invocations that set/read EXISTING and call gh api
-X PATCH / gh pr comment) with non-fatal error handling (use set +e and ensure
gh commands end with || true so failures don’t fail the job) so gh pr comment
and gh api failures become informational only.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: de0ab9b2-b9d1-4e61-89e0-a13eab27f3c3
📒 Files selected for processing (1)
.github/workflows/reusable-secret-leak-check.yml
Two findings, both real:
1. **Major** - workflow-command annotation injection. The `::error
file=...,line=...::msg` lines built from JSON could be split or
spoofed if any interpolated value (path, rule id, commit SHA)
contained a CR/LF or a literal `::` sequence. betterleaks' own
output is unlikely to do that for legitimate findings, but a
crafted path in a malicious repo could. Added a jq `safe`
function that strips CR/LF and rewrites `::` to a non-parsable
placeholder, applied to every interpolated field.
2. **Major** - the PR-comment step is unreachable from fork PRs and
Dependabot PRs because `GITHUB_TOKEN` is read-only in those
contexts regardless of `permissions:` (GitHub override). Previous
code would hard-fail the step with a 403. Now:
- `continue-on-error: true` so the step doesn't fail the job
- Each `gh` call wrapped to detect failure and emit a clean
`::warning::` instead of a 403 stack trace
- The actual pass/fail of the job is decided by the dedicated
"Fail if findings" step below, not by whether commenting worked
Inline annotations + workflow artifact still surface findings on
fork / Dependabot PRs - the comment is a UX nicety, not the gate.
Summary
Adds
reusable-secret-leak-check.yml, an org-wide reusable workflow that any Geolonia repo can call frompull_requestto block new secret commits before merge.Pairs with the weekly org-wide audit in
geolonia-operations/.github/workflows/secret-leak-audit.yml:geolonia-operationspull_requesttriggerTracking issue:
geolonia/geolonia-operations#60.How it works
Scans only the commits the PR adds:
betterleaks git . --log-opts="<base-sha>..<head-sha>". Runs the upstream betterleaks container pinned by immutable digest (v1.2.0).Outputs:
Inputs
betterleaks-imagev1.2.0fail-on-findingstruefalsefor warn-only mode while a repo cleans up legacy false positives.config-path''.betterleaks.tomlconfig path.base-ref''pull_requesttriggers.validate-secretsfalsePermissions
No org-level token.
GITHUB_TOKENis sufficient since the scan is scoped to the caller repo.Consumer pattern (after tagging v1)
Override defaults per repo if needed:
Failure semantics + body cap (inherited from secret-leak-audit.yml)
null(clean) or top-level array (findings). Anything else triggersscan_failed=trueand explicit failure.--redacton by default. Detected secret values never appear in logs, annotations, or PR comments.Test plan
v1so consumers canuses: ...@v1geolonia-operationsfirst (self-dogfood); confirm the comment + annotation behavior on a test PROut of scope
Summary by CodeRabbit
New Features
Chores