Skip to content

chore(security): track the gate kit and close the binary secret-scan gap - #7

Merged
Jmeg8r merged 2 commits into
mainfrom
chore/track-gate-kit
Aug 8, 2026
Merged

chore(security): track the gate kit and close the binary secret-scan gap#7
Jmeg8r merged 2 commits into
mainfrom
chore/track-gate-kit

Conversation

@Jmeg8r

@Jmeg8r Jmeg8r commented Aug 8, 2026

Copy link
Copy Markdown
Owner

What

Puts this repo's local quality-gate kit under version control, and closes a hole in it.

Two commits:

  1. chore(security).gitleaks.toml and lefthook.yml existed on disk but were never
    committed. They ran locally, so nothing surfaced the gap, but they were unversioned,
    unreviewed, and would vanish on a fresh clone or git clean. Committed byte-identical to
    the shared kit, so this is an accurate baseline.
  2. fix(security) — the binary secret-scanning fix, plus the scanner version floor it
    depends on.

Why the fix is needed

gitleaks' built-in allowlist, inherited via [extend] useDefault = true, skips
pdf/xlsx/docx/bin/exe by path. The pre-commit secrets gate therefore printed
no leaks found after reading ~0 bytes of them.

Verified on gitleaks 8.30.1: a real compressed PDF containing a live AWS key pair passed
both the gitleaks and aikido gates untouched.

How it is fixed

scripts/scan-staged-binaries.sh defeats the allowlist by changing the path gitleaks
sees rather than the config — OOXML is copied to a *.zip temp name and traversed with
--max-archive-depth; PDFs go through pdftotext into gitleaks stdin. .gitleaks.toml
stays the single source of truth with every built-in rule live, so no rule is duplicated
and none can drift.

Raw-byte PDF scanning was evaluated and rejected: content streams are deflated, so a raw
scan reads 0 bytes and reports clean — a false green, worse than no gate. The self-test
asserts that directly.

The scans also route through scripts/gitleaks-guard.sh, which enforces a version floor:
allowlist semantics differ across gitleaks versions, so a scanner whose version you cannot
name has coverage you cannot state.

Reporting contract

Both gates report N-scanned and fail closed. A file that should have been readable but
was not comes back UNKNOWN, never clean. Formats with no text layer (images, fbx, exe,
scanned PDFs) are reported as NOT INSPECTED rather than passed silently.

Verification

  • scripts/selftest-binary-scan.sh — 10 cases, run in this repo, all passing
  • scripts/test-gitleaks-guard.sh — coverage claims asserted, run in this repo, passing
  • Fixtures are real: genuinely Flate-compressed PDFs and real zip containers, not text files
    with renamed extensions
  • Mutation-tested: reverting to raw-byte scanning fails the suite
  • Kit content verified byte-for-byte against the canonical source

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added automated secret scanning for pull requests, pushes, and manual runs.
    • Added protection against secrets in staged binary, document, archive, and PDF files.
    • Added custom detection rules and safe exclusions for project-specific credentials and placeholders.
    • Added pre-commit and pre-push checks for secrets, formatting, linting, configuration, large files, and tests.
    • Added verified scanner version checks and download integrity validation.
  • Tests
    • Added comprehensive validation for secret detection, binary scanning, version checks, exclusions, and edge cases.

.gitleaks.toml and lefthook.yml were provisioned onto disk but never
committed. They run locally, so nothing surfaced the gap -- but they were
unversioned, unreviewed, and would vanish on a fresh clone or git clean.

Committed as-is, byte-identical to the standard kit across all repos, so
this is an accurate baseline and any later change reads as a real diff.

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

cursor Bot commented Aug 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added repository-wide secret scanning with shared Gitleaks rules, version verification, staged binary inspection, Lefthook integration, self-tests, and a pinned full-history GitHub Actions workflow.

Changes

Secret scanning controls

Layer / File(s) Summary
Scanner configuration and version guard
.gitleaks.toml, scripts/gitleaks-version.env, scripts/gitleaks-guard.sh, scripts/test-gitleaks-guard.sh
Defines custom rules and allowlists, pins Gitleaks versions and checksums, validates installed versions, and tests text and staged scans.
Staged binary scanning
scripts/scan-staged-binaries.sh, scripts/selftest-binary-scan.sh
Scans staged archives, PDFs, SVG files, and opaque files. It blocks unknown results and detected secrets.
Local hook integration
lefthook.yml
Adds pre-commit and pre-push formatting, linting, secret scanning, binary scanning, validation, testing, and history scanning commands.
Reproducible CI scan
.github/workflows/secret-scan.yml
Runs read-only scans with pinned checksums, version validation, configuration self-tests, changed-file binary scanning, full-history scanning, and zero-byte protection.

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

Sequence Diagram(s)

sequenceDiagram
  participant Developer
  participant Lefthook
  participant gitleaks_guard
  participant scan_staged_binaries
  participant GitIndex
  Developer->>Lefthook: create commit or push
  Lefthook->>gitleaks_guard: scan staged text content
  gitleaks_guard->>GitIndex: read staged paths
  Lefthook->>scan_staged_binaries: scan staged binary content
  scan_staged_binaries->>GitIndex: read staged blobs
  Lefthook-->>Developer: allow or block operation
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: tracking the security gate kit and closing the binary secret-scanning gap.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/track-gate-kit

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

🧹 Nitpick comments (9)
.github/workflows/secret-scan.yml (2)

62-63: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a timeout and retries to the release download.

curl -sSfL has no time limit and no retry. A transient GitHub release outage or a stalled connection makes this step hang until the job timeout, and the secret scan never runs. The checksum check below still protects integrity, so this is a reliability change only.

♻️ Proposed change
-          curl -sSfL -o "$tarball" \
+          curl -sSfL --retry 3 --retry-all-errors --connect-timeout 10 --max-time 120 -o "$tarball" \
             "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_PINNED_VERSION}/${tarball}"
🤖 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/secret-scan.yml around lines 62 - 63, Update the release
download command in the secret-scan workflow to enforce a bounded
connection/overall timeout and retry transient failures, while preserving the
existing silent, fail-fast, and redirect-following behavior and leaving the
checksum verification unchanged.

34-36: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Set persist-credentials: false on checkout.

The job runs no git operation that needs the token after checkout. actions/checkout writes the token into .git/config by default. Disabling persistence removes that credential from the workspace that this job then scans and shares with every later step.

🛡️ Proposed change
       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
         with:
           fetch-depth: 0   # the history scan needs the whole history
+          persist-credentials: false
🤖 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/secret-scan.yml around lines 34 - 36, Update the
actions/checkout step in the secret-scan workflow to set persist-credentials to
false alongside fetch-depth, ensuring the checkout token is not retained in
.git/config for subsequent steps.

Source: Linters/SAST tools

scripts/test-gitleaks-guard.sh (1)

311-315: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Pass --config explicitly on the staged scan.

scan() passes --config "$CONFIG". staged_scan() does not. It relies on gitleaks discovering $STAGE/.gitleaks.toml from the working directory. The failure mode is safe, because a missed discovery makes the coderabbit-api-key assertion fail. It is still an unnecessary difference between the two helpers, and the copy at line 364 exists only to serve that discovery.

An explicit flag removes both the copy and the assumption:

♻️ Proposed change
-  ( cd "$STAGE" && "$GUARD" git --staged --redact --no-banner --log-level error --exit-code 0 \
+  ( cd "$STAGE" && "$GUARD" git --staged --config "$CONFIG" \
+      --redact --no-banner --log-level error --exit-code 0 \
       --report-format json --report-path "$1" >/dev/null 2>&1 ) || rc=$?

Keep the copy if you also want to prove that discovery works. If so, state that intent in the comment at line 364.

Also applies to: 364-365

🤖 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/test-gitleaks-guard.sh` around lines 311 - 315, Update staged_scan()
to pass --config "$CONFIG" explicitly, matching scan(), and remove the
now-unnecessary staged .gitleaks.toml copy around the referenced setup lines;
retain that copy only if the test intentionally validates configuration
discovery and document that intent in its comment.
.gitleaks.toml (2)

155-155: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Anchor the fixtures exclusion like its neighbours.

(^|/)tests?/fixtures/ is the anchored form. The current entry has no path-segment anchor, so it also matches directories whose names only end in test/tests, for example unittests/fixtures/ or src/smoketests/fixtures/. Those paths are then excluded from scanning without anyone intending it. This is the same class of over-match the comment block records for the image entry.

♻️ Proposed change
-  '''(^|/)tests?/fixtures/''',
+  '''(^|/)tests?/fixtures/''',

Note: the diff above is a no-op placeholder; the actual change is to line 155:

  '''(^|/)tests?/fixtures/''',
🤖 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 @.gitleaks.toml at line 155, Update the fixtures exclusion pattern in the
gitleaks configuration to use the path-segment anchor `(^|/)tests?/fixtures/`,
matching the neighboring exclusions and preventing matches for directory names
that merely end in “test” or “tests”.

158-164: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Restrict global allowlist regexes to exact placeholders or specific rules.

The unanchored patterns suppress valid keys containing sample, dummy, or xxxx. Anchor placeholder patterns to the complete secret or move them to rule-specific allowlists.

🤖 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 @.gitleaks.toml around lines 158 - 164, Restrict the global regexes in
regexes to exact placeholder values by anchoring the placeholder alternatives,
or move them into rule-specific allowlists. Ensure valid secrets containing
words such as sample, dummy, or xxxx are not globally suppressed, while
preserving detection of complete placeholder values and the existing
environment-variable reference patterns.
scripts/gitleaks-guard.sh (1)

60-65: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Replace sort -V with a portable version comparison

The local macOS hook uses BSD sort, which does not support -V. Under set -euo pipefail, the assignment exits before die() can report the cause. Use portable comparison logic or detect unsupported -V and call die(). Also preserve prerelease information; 8.30.1-rc1 currently passes as 8.30.1.

🤖 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/gitleaks-guard.sh` around lines 60 - 65, Update the version check
around GITLEAKS_MIN_VERSION and found to avoid relying on non-portable sort -V,
ensuring unsupported tooling cannot terminate before die() reports the issue.
Implement portable comparison logic that correctly handles prerelease
identifiers such as 8.30.1-rc1 rather than treating them as the final 8.30.1
release, while preserving the existing upgrade error behavior.
scripts/scan-staged-binaries.sh (1)

292-293: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Route a git cat-file failure into the unknown bucket.

Line 292 and Line 306 run git cat-file blob ":$path" unguarded. If extraction of the staged blob fails, set -e aborts the run. The gate then exits non-zero with a bare git message, and the remaining staged files are never classified. The exit code is still fail-closed, but the report loses both the cause and the coverage.

♻️ Proposed change (apply the same pattern at Line 306)
-    git cat-file blob ":$path" > "$staged"
+    if ! git cat-file blob ":$path" > "$staged" 2>/dev/null; then
+      unknown+=("$path (could not read the staged blob)")
+      continue
+    fi
     run_gitleaks_archive "$path" "$staged"
🤖 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/scan-staged-binaries.sh` around lines 292 - 293, Guard both
staged-blob extraction commands in the scanning flow around run_gitleaks_archive
so a failed git cat-file does not trigger set -e termination. Record the
extraction failure through the script’s existing unknown classification and
reporting path, then continue processing remaining staged files while preserving
the fail-closed exit status.
scripts/selftest-binary-scan.sh (1)

216-239: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the archive canary causes.

Case 10 exercises only the stdin canary. The archive branch in scripts/scan-staged-binaries.sh maps four distinct causes (bad, nopython, buildfail, norule) to four distinct messages, and none of them is asserted. That mapping is the exact cause-conflation the split was made to prevent, so a wrong mapping would pass this suite.

A minimal addition: stage an .xlsx under the same stub PATH and assert "cannot traverse archives". A second case can strip the canary rule from the copied .gitleaks.toml and assert the norule message.

🧪 Proposed additional case
+# 11. Archive branch must name the archive canary, not the stdin one.
+new_repo c11
+build_xlsx book.xlsx "token $SECRET"
+git add book.xlsx
+set +e
+STUB_OUT="$(PATH="$WORK/stub:$PATH" "$SUT" 2>&1)"; STUB_RC=$?
+set -e
+if [ "$STUB_RC" -ne 0 ] && grep -q 'cannot traverse archives' <<<"$STUB_OUT"; then
+  ok "unsupported gitleaks names the ARCHIVE canary for xlsx"
+else bad "archive canary cause not reported (rc=$STUB_RC)" "$STUB_OUT"; fi
🤖 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/selftest-binary-scan.sh` around lines 216 - 239, Extend the self-test
after the existing stdin canary case to cover the archive canary in
scripts/scan-staged-binaries.sh: stage an .xlsx file while using the stub
gitleaks PATH and assert the failure output contains “cannot traverse archives”.
Add a second scenario using a copied configuration with the archive canary rule
removed, and assert the distinct norule message, preserving nonzero exit checks
for both cases.
lefthook.yml (1)

100-102: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Invoke the binary scan the same way as the other scripts.

Line 84 uses ./scripts/gitleaks-guard.sh, and this command uses scripts/scan-staged-binaries.sh. Use one form. Invoking through bash also removes the dependency on the executable bit, which is not preserved by every checkout.

♻️ Proposed change
     secrets-binaries:
       tags: security
-      run: scripts/scan-staged-binaries.sh
+      run: bash ./scripts/scan-staged-binaries.sh
🤖 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 `@lefthook.yml` around lines 100 - 102, Update the secrets-binaries hook
command to invoke scan-staged-binaries.sh consistently with the other scripts,
using the repository-relative ./scripts form and bash so execution does not
depend on the file’s executable bit.
🤖 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/secret-scan.yml:
- Around line 101-108: Update the gitleaks result handling around the existing
zero-byte check to first assert that the expected scan-summary line is present
in out; if it is missing, emit a workflow error and fail the step as UNKNOWN.
Only evaluate the scanned-byte value after this presence check, preserving the
existing failure for zero bytes and the final exit with rc for valid summaries.

In `@lefthook.yml`:
- Around line 240-245: Update the selftest-binary-scan hook to invoke
scripts/selftest-binary-scan.sh unconditionally, removing the executable check
so missing or non-executable scripts cause the job to fail rather than silently
pass. Keep the existing security tag and hook structure unchanged.
- Around line 144-152: Quote the {staged_files} expansion in the loop within the
staged-file size check so paths containing spaces remain a single filename;
preserve the existing file validation and oversized-file rejection behavior.
- Around line 19-27: Set pre-commit.parallel to false in the pre-commit
configuration so the format-python command completes and stages formatter
changes before the secrets and secrets-binaries scans execute, preserving the
existing stage_fixed behavior.

In `@scripts/scan-staged-binaries.sh`:
- Around line 98-134: Detect a missing gitleaks executable explicitly in both
canary_stdin_ok and canary_archive_ok, returning a distinct status code while
preserving their existing outcomes for scan failures and configuration issues.
Update the return-code contract comment for canary_archive_ok, map the new
archive status in the caller as archive_canary=nogitleaks, and report “gitleaks
is not installed”; also distinguish canary_stdin_ok’s missing-gitleaks status
from its normal failure in the PDF branch.

---

Nitpick comments:
In @.github/workflows/secret-scan.yml:
- Around line 62-63: Update the release download command in the secret-scan
workflow to enforce a bounded connection/overall timeout and retry transient
failures, while preserving the existing silent, fail-fast, and
redirect-following behavior and leaving the checksum verification unchanged.
- Around line 34-36: Update the actions/checkout step in the secret-scan
workflow to set persist-credentials to false alongside fetch-depth, ensuring the
checkout token is not retained in .git/config for subsequent steps.

In @.gitleaks.toml:
- Line 155: Update the fixtures exclusion pattern in the gitleaks configuration
to use the path-segment anchor `(^|/)tests?/fixtures/`, matching the neighboring
exclusions and preventing matches for directory names that merely end in “test”
or “tests”.
- Around line 158-164: Restrict the global regexes in regexes to exact
placeholder values by anchoring the placeholder alternatives, or move them into
rule-specific allowlists. Ensure valid secrets containing words such as sample,
dummy, or xxxx are not globally suppressed, while preserving detection of
complete placeholder values and the existing environment-variable reference
patterns.

In `@lefthook.yml`:
- Around line 100-102: Update the secrets-binaries hook command to invoke
scan-staged-binaries.sh consistently with the other scripts, using the
repository-relative ./scripts form and bash so execution does not depend on the
file’s executable bit.

In `@scripts/gitleaks-guard.sh`:
- Around line 60-65: Update the version check around GITLEAKS_MIN_VERSION and
found to avoid relying on non-portable sort -V, ensuring unsupported tooling
cannot terminate before die() reports the issue. Implement portable comparison
logic that correctly handles prerelease identifiers such as 8.30.1-rc1 rather
than treating them as the final 8.30.1 release, while preserving the existing
upgrade error behavior.

In `@scripts/scan-staged-binaries.sh`:
- Around line 292-293: Guard both staged-blob extraction commands in the
scanning flow around run_gitleaks_archive so a failed git cat-file does not
trigger set -e termination. Record the extraction failure through the script’s
existing unknown classification and reporting path, then continue processing
remaining staged files while preserving the fail-closed exit status.

In `@scripts/selftest-binary-scan.sh`:
- Around line 216-239: Extend the self-test after the existing stdin canary case
to cover the archive canary in scripts/scan-staged-binaries.sh: stage an .xlsx
file while using the stub gitleaks PATH and assert the failure output contains
“cannot traverse archives”. Add a second scenario using a copied configuration
with the archive canary rule removed, and assert the distinct norule message,
preserving nonzero exit checks for both cases.

In `@scripts/test-gitleaks-guard.sh`:
- Around line 311-315: Update staged_scan() to pass --config "$CONFIG"
explicitly, matching scan(), and remove the now-unnecessary staged
.gitleaks.toml copy around the referenced setup lines; retain that copy only if
the test intentionally validates configuration discovery and document that
intent in its comment.
🪄 Autofix

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e59f757-fc67-4ec4-a2d7-31afaca74694

📥 Commits

Reviewing files that changed from the base of the PR and between d4f74ae and 1432159.

📒 Files selected for processing (8)
  • .github/workflows/secret-scan.yml
  • .gitleaks.toml
  • lefthook.yml
  • scripts/gitleaks-guard.sh
  • scripts/gitleaks-version.env
  • scripts/scan-staged-binaries.sh
  • scripts/selftest-binary-scan.sh
  • scripts/test-gitleaks-guard.sh

Comment on lines +101 to +108
# A check that passed while scanning zero input is not a pass. gitleaks
# reports the volume it examined; if that is zero, the green tick means
# "not inspected", which is a different claim from "clean".
if printf '%s' "$out" | grep -q 'scanned ~0 bytes'; then
echo "::error::the scan examined 0 bytes — treat this as UNKNOWN, not clean"
exit 1
fi
exit "$rc"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

The zero-byte check goes inert if the gitleaks summary wording changes.

This guard depends on the literal string scanned ~0 bytes in gitleaks log output. Nothing asserts that the summary line is present at all. Three changes make the check silently stop working, with no failure:

  • gitleaks changes the summary wording or the unit in a later release.
  • someone lowers --log-level info, which removes the summary line.
  • the summary moves to stdout-only or stderr-only in a way that the current 2>&1 capture no longer covers.

In each case the step reports green while performing no zero-byte verification. That is the "not inspected reads as clean" failure this workflow exists to prevent.

Assert the summary line exists before you interpret it:

🛡️ Proposed change
           # A check that passed while scanning zero input is not a pass. gitleaks
           # reports the volume it examined; if that is zero, the green tick means
           # "not inspected", which is a different claim from "clean".
+          # Assert the summary line FIRST. Without it the grep below cannot
+          # distinguish "scanned plenty" from "wording changed and this check
+          # no longer runs".
+          if ! printf '%s' "$out" | grep -qE 'scanned ~?[0-9]'; then
+            echo "::error::no scan-volume summary in the gitleaks output — this job"
+            echo "::error::can no longer prove the scan examined anything. Update the"
+            echo "::error::pattern in this step for gitleaks ${GITLEAKS_PINNED_VERSION}."
+            exit 1
+          fi
           if printf '%s' "$out" | grep -q 'scanned ~0 bytes'; then
             echo "::error::the scan examined 0 bytes — treat this as UNKNOWN, not clean"
             exit 1
           fi
           exit "$rc"

The pinned version makes this stable today. The assertion is what makes the next version bump report the breakage instead of hiding it.

📝 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
# A check that passed while scanning zero input is not a pass. gitleaks
# reports the volume it examined; if that is zero, the green tick means
# "not inspected", which is a different claim from "clean".
if printf '%s' "$out" | grep -q 'scanned ~0 bytes'; then
echo "::error::the scan examined 0 bytes — treat this as UNKNOWN, not clean"
exit 1
fi
exit "$rc"
# A check that passed while scanning zero input is not a pass. gitleaks
# reports the volume it examined; if that is zero, the green tick means
# "not inspected", which is a different claim from "clean".
# Assert the summary line FIRST. Without it the grep below cannot
# distinguish "scanned plenty" from "wording changed and this check
# no longer runs".
if ! printf '%s' "$out" | grep -qE 'scanned ~?[0-9]'; then
echo "::error::no scan-volume summary in the gitleaks output — this job"
echo "::error::can no longer prove the scan examined anything. Update the"
echo "::error::pattern in this step for gitleaks ${GITLEAKS_PINNED_VERSION}."
exit 1
fi
if printf '%s' "$out" | grep -q 'scanned ~0 bytes'; then
echo "::error::the scan examined 0 bytes — treat this as UNKNOWN, not clean"
exit 1
fi
exit "$rc"
🤖 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/secret-scan.yml around lines 101 - 108, Update the
gitleaks result handling around the existing zero-byte check to first assert
that the expected scan-summary line is present in out; if it is missing, emit a
workflow error and fail the step as UNKNOWN. Only evaluate the scanned-byte
value after this presence check, preserving the existing failure for zero bytes
and the final exit with rc for valid summaries.

Comment thread lefthook.yml
Comment thread lefthook.yml Outdated
Comment thread lefthook.yml
Comment on lines +240 to +245
selftest-binary-scan:
tags: security
run: |
if [ -x scripts/selftest-binary-scan.sh ]; then
scripts/selftest-binary-scan.sh
fi

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

Do not skip the self-test silently.

If scripts/selftest-binary-scan.sh is missing or loses its executable bit, this job exits 0 and prints nothing. A skipped verification then looks the same as a passing one, which is the failure mode this cohort exists to close. The neighbouring gate at Line 200 calls its script unconditionally.

🔧 Proposed change
     selftest-binary-scan:
       tags: security
       run: |
-        if [ -x scripts/selftest-binary-scan.sh ]; then
-          scripts/selftest-binary-scan.sh
-        fi
+        if [ -f scripts/selftest-binary-scan.sh ]; then
+          bash ./scripts/selftest-binary-scan.sh
+        else
+          echo "✗ scripts/selftest-binary-scan.sh is missing — the binary secret gate is unverified"
+          exit 1
+        fi
📝 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
selftest-binary-scan:
tags: security
run: |
if [ -x scripts/selftest-binary-scan.sh ]; then
scripts/selftest-binary-scan.sh
fi
selftest-binary-scan:
tags: security
run: |
if [ -f scripts/selftest-binary-scan.sh ]; then
bash ./scripts/selftest-binary-scan.sh
else
echo "✗ scripts/selftest-binary-scan.sh is missing — the binary secret gate is unverified"
exit 1
fi
🤖 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 `@lefthook.yml` around lines 240 - 245, Update the selftest-binary-scan hook to
invoke scripts/selftest-binary-scan.sh unconditionally, removing the executable
check so missing or non-executable scripts cause the job to fail rather than
silently pass. Keep the existing security tag and hook structure unchanged.

Comment on lines +98 to +134
canary_stdin_ok() {
local rc=0
printf 'token %s\n' "$CANARY_TOKEN" \
| gitleaks stdin --no-banner --redact --config "$CONFIG" >/dev/null 2>&1 || rc=$?
[ "$rc" -eq 1 ]
}

# Returns:
# 0 = archive traversal works
# 1 = gitleaks cannot traverse archives
# 2 = python3 is absent, so the probe could not be built
# 3 = gitleaks scanned the probe and found nothing -> the config lost the rule
# 4 = python3 is present but building the zip failed
#
# Four codes for four causes, and the contract is spelled out because the first
# version of this split collapsed 2 and 4 into one code while the caller printed
# "python3 missing" for both -- reintroducing, at smaller scale, the exact
# cause-conflation this function was split up to fix.
canary_archive_ok() {
local rc=0 z="$TMPDIR_SCAN/canary.zip"
command -v python3 >/dev/null 2>&1 || return 2
python3 - "$z" "$CANARY_TOKEN" <<'PY' 2>/dev/null || return 4
import sys, zipfile
with zipfile.ZipFile(sys.argv[1], "w", zipfile.ZIP_DEFLATED) as z:
z.writestr("payload.txt", "token " + sys.argv[2])
PY
gitleaks dir --no-banner --redact --config "$CONFIG" \
--max-archive-depth "$MAX_ARCHIVE_DEPTH" "$z" >/dev/null 2>&1 || rc=$?
rm -f "$z"
# rc=1 means gitleaks found the canary: the traversal path works. rc=0 means it
# scanned and found nothing, which on a file containing the token means this
# repo's .gitleaks.toml no longer carries the rule the canary matches -- a
# config problem, not a version problem, and it must not be reported as one.
[ "$rc" -eq 1 ] && return 0
[ "$rc" -eq 0 ] && return 3
return 1
}

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

Report a missing gitleaks as its own cause.

Neither canary checks that gitleaks is on PATH. If the binary is absent, the shell returns 127, both canaries report bad, and the gate prints "needs 8.x". That sends the developer to an upgrade for a tool that is not installed. The PDF branch already names its missing extractor at Line 295; apply the same treatment here.

🔧 Proposed fix: add a distinct "not installed" state
 canary_stdin_ok() {
   local rc=0
+  command -v gitleaks >/dev/null 2>&1 || return 2
   printf 'token %s\n' "$CANARY_TOKEN" \
     | gitleaks stdin --no-banner --redact --config "$CONFIG" >/dev/null 2>&1 || rc=$?
   [ "$rc" -eq 1 ]
 }
 canary_archive_ok() {
   local rc=0 z="$TMPDIR_SCAN/canary.zip"
+  command -v gitleaks >/dev/null 2>&1 || return 5
   command -v python3 >/dev/null 2>&1 || return 2

Then map the new codes in the caller: 5) archive_canary=nogitleaks ;; with the message "gitleaks is not installed", and change the PDF branch to distinguish canary_stdin_ok return 2 from return 1. Update the return-code contract comment at Lines 105-110 in the same change.

🤖 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/scan-staged-binaries.sh` around lines 98 - 134, Detect a missing
gitleaks executable explicitly in both canary_stdin_ok and canary_archive_ok,
returning a distinct status code while preserving their existing outcomes for
scan failures and configuration issues. Update the return-code contract comment
for canary_archive_ok, map the new archive status in the caller as
archive_canary=nogitleaks, and report “gitleaks is not installed”; also
distinguish canary_stdin_ok’s missing-gitleaks status from its normal failure in
the PDF branch.

@Jmeg8r
Jmeg8r force-pushed the chore/track-gate-kit branch from 1432159 to b1ed8e9 Compare August 8, 2026 21:39

@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: 2

🧹 Nitpick comments (1)
.github/workflows/secret-scan.yml (1)

15-18: 🔒 Security & Privacy | 🔵 Trivial

Verify that PR scan enforcement is independent of the PR ref.

Line 15 runs this workflow for pull requests. Lines 52-61 and 111-112 load the scanner contract and self-test from that same checked-out ref. A PR can change the workflow, rules, pins, and self-test together. Confirm that an organization or repository policy requires an independent protected scan for PRs. Otherwise this scan is reproducible after merge, but it is not authoritative for untrusted PRs. GitHub evaluates workflows from the event-associated ref, and its workflow-execution protections exist to apply policy outside an individual workflow file. (docs.github.com)

Also applies to: 52-61, 108-112

🤖 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/secret-scan.yml around lines 15 - 18, Ensure pull_request
executions of the secret-scan workflow are enforced by an organization or
repository-level protected policy independent of the PR’s checked-out ref, so
changes to the workflow, scanner contract, rules, pins, or self-test cannot
bypass enforcement. Update the workflow configuration around on.pull_request and
the referenced scan/self-test steps only as needed to integrate with that
independent protection, while preserving push scans for main.
🤖 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 `@scripts/scan-staged-binaries.sh`:
- Around line 313-341: Guard both git cat-file blob reads in the staged-binary
and PDF branches so failures do not terminate the scan under set -e. When either
read fails, add the affected path to unknown with a clear blob-read failure
reason and continue to the next file, preserving the requirement that every
staged file is classified and the existing downstream processing for successful
reads.

In `@scripts/selftest-binary-scan.sh`:
- Around line 281-291: Update the SVG test around new_repo c13 to require the
scanner to return a nonzero status and report “SECRET” in diagram.svg. Retain
the existing rejection of “NOT INSPECTED,” so the test fails both when SVG is
treated as opaque and when its content is silently ignored.

---

Nitpick comments:
In @.github/workflows/secret-scan.yml:
- Around line 15-18: Ensure pull_request executions of the secret-scan workflow
are enforced by an organization or repository-level protected policy independent
of the PR’s checked-out ref, so changes to the workflow, scanner contract,
rules, pins, or self-test cannot bypass enforcement. Update the workflow
configuration around on.pull_request and the referenced scan/self-test steps
only as needed to integrate with that independent protection, while preserving
push scans for main.
🪄 Autofix

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 613452e7-b733-4b1e-b53d-7a842f161484

📥 Commits

Reviewing files that changed from the base of the PR and between 1432159 and b1ed8e9.

📒 Files selected for processing (6)
  • .github/workflows/secret-scan.yml
  • .gitleaks.toml
  • lefthook.yml
  • scripts/gitleaks-guard.sh
  • scripts/scan-staged-binaries.sh
  • scripts/selftest-binary-scan.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/gitleaks-guard.sh

Comment thread scripts/scan-staged-binaries.sh Outdated
Comment thread scripts/selftest-binary-scan.sh
@Jmeg8r
Jmeg8r force-pushed the chore/track-gate-kit branch from b1ed8e9 to 120da41 Compare August 8, 2026 22:00

@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: 2

🧹 Nitpick comments (2)
scripts/selftest-binary-scan.sh (1)

157-161: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Case 2 depends on the exact gitleaks banner text.

The assertion matches the literal string no leaks found. If gitleaks changes that wording, the case fails and reports "raw-byte scan now finds it", which names the wrong cause. The same conflation is called out at Line 155 for the fixture. Assert the exit status instead, and keep the string match only as a secondary signal.

♻️ Proposed refactor
-if gitleaks stdin --no-banner --redact --config .gitleaks.toml < secret.pdf 2>&1 \
-     | grep -q "no leaks found"; then
+RAW_RC=0
+gitleaks stdin --no-banner --redact --config .gitleaks.toml < secret.pdf >/dev/null 2>&1 || RAW_RC=$?
+if [ "$RAW_RC" -eq 0 ]; then
   ok "raw-byte scan of the same PDF finds nothing (why pdftotext is required)"
-else bad "raw-byte scan now finds it — revisit the pdftotext dependency" ""; fi
+elif [ "$RAW_RC" -eq 1 ]; then
+  bad "raw-byte scan now finds it — revisit the pdftotext dependency" ""
+else bad "raw-byte probe errored (rc=$RAW_RC), cause unknown" ""; fi
🤖 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/selftest-binary-scan.sh` around lines 157 - 161, Update case 2 in the
raw-byte scan around gitleaks so it asserts the command’s exit status as the
primary success condition, while retaining the “no leaks found” grep only as a
secondary signal. Adjust the failure message to distinguish a gitleaks wording
change from an actual leak detection, consistent with the fixture assertion near
this case.
scripts/scan-staged-binaries.sh (1)

333-339: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared stdin canary and rename it.

The same seven lines appear in the PDF branch and the SVG branch. The variable is named pdf_canary, but it now gates every stdin route, including SVG. Extract one helper and rename the memo to stdin_canary. The two copies can otherwise drift apart.

♻️ Proposed refactor
+require_stdin_canary() {   # returns 1 and records the cause when unusable
+  local path="$1"
+  if [ -z "$stdin_canary" ]; then
+    if canary_stdin_ok; then stdin_canary=ok; else stdin_canary=bad; fi
+  fi
+  [ "$stdin_canary" = ok ] && return 0
+  unknown+=("$path (gitleaks on PATH has no working \`stdin\` scan — needs 8.x)")
+  return 1
+}

Then each branch calls require_stdin_canary "$path" || continue.

Also applies to: 355-364

🤖 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/scan-staged-binaries.sh` around lines 333 - 339, Extract the
duplicated stdin-canary logic from the PDF and SVG branches into a shared
require_stdin_canary helper that accepts the path, uses the stdin_canary memo,
and records the existing unknown result before signaling failure. Replace both
branch-local blocks with require_stdin_canary "$path" || continue, and rename
all remaining pdf_canary references to stdin_canary.
🤖 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 `@scripts/scan-staged-binaries.sh`:
- Around line 279-281: Update the report rewrite around esc_probe, esc_label,
and the following sed command to handle embedded newlines in staged filenames
and labels. Escape newlines in both operands before interpolation, or replace
the rewrite with an equivalent awk-based substitution that passes both values as
data, while preserving delimiter, BRE metacharacter, and ampersand escaping and
ensuring the scan reaches the summary.

In `@scripts/selftest-binary-scan.sh`:
- Around line 307-323: Update Case 13’s PRIMARY_RC handling after invoking
gitleaks-guard.sh to inspect PRIMARY_OUT for a guard error such as version
rejection before classifying the result. Report guard failures separately, and
only emit the “primary gate now scans .svg” failure when the command succeeded
as a guard invocation but returned a scan finding.

---

Nitpick comments:
In `@scripts/scan-staged-binaries.sh`:
- Around line 333-339: Extract the duplicated stdin-canary logic from the PDF
and SVG branches into a shared require_stdin_canary helper that accepts the
path, uses the stdin_canary memo, and records the existing unknown result before
signaling failure. Replace both branch-local blocks with require_stdin_canary
"$path" || continue, and rename all remaining pdf_canary references to
stdin_canary.

In `@scripts/selftest-binary-scan.sh`:
- Around line 157-161: Update case 2 in the raw-byte scan around gitleaks so it
asserts the command’s exit status as the primary success condition, while
retaining the “no leaks found” grep only as a secondary signal. Adjust the
failure message to distinguish a gitleaks wording change from an actual leak
detection, consistent with the fixture assertion near this case.
🪄 Autofix

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

Review profile: CHILL

Plan: Pro Plus

Run ID: f40b1871-b67e-411e-8223-bc8dcd0d6a8f

📥 Commits

Reviewing files that changed from the base of the PR and between b1ed8e9 and 120da41.

📒 Files selected for processing (3)
  • scripts/scan-staged-binaries.sh
  • scripts/selftest-binary-scan.sh
  • scripts/test-gitleaks-guard.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/test-gitleaks-guard.sh

Comment thread scripts/scan-staged-binaries.sh
Comment on lines +307 to +323
set +e
PRIMARY_OUT=$("$SCRIPT_DIR/gitleaks-guard.sh" git --staged --redact --no-banner --verbose 2>&1)
PRIMARY_RC=$?
set -e
run_sut
if [ "$SUT_RC" -ne 0 ] && grep -q "SECRET in diagram.svg" <<<"$SUT_OUT"; then
ok "svg: secret in a staged .svg is detected (via the stdin route)"
else
bad "svg NOT detected — it is XML text with no gate (rc=$SUT_RC)" "$SUT_OUT"
fi
# Record the upstream exclusion as a fact rather than an assumption: if this ever
# starts failing, gitleaks changed its built-in allowlist and the extra route can go.
if [ "$PRIMARY_RC" -eq 0 ]; then
ok "confirmed: the primary gate still cannot see .svg (upstream allowlist)"
else
bad "primary gate now scans .svg — TEXT_ALLOWLISTED_EXTS may be redundant" "$PRIMARY_OUT"
fi

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
# Confirm the exit codes gitleaks-guard.sh uses for a version rejection versus a scan finding.
fd -t f 'gitleaks-guard.sh' --exec rg -n -C4 'exit [0-9]|VERSION|floor|too old|require'

Repository: Jmeg8r/diffusiongemma-benchmark

Length of output: 14132


🏁 Script executed:

#!/bin/bash
# Inspect the staged-scan helper, the self-test case, and all relevant guard call sites
# to determine whether PRIMARY_RC can distinguish a finding from a guard failure.
fd -t f 'selftest-binary-scan.sh|scan-staged-binaries.sh|gitleaks-guard.sh' --exec sh -c '
  for f do
    echo "===== $f ====="
    sed -n "1,130p" "$f" | cat -n
    echo
  done
' sh
printf '%s\n' '===== relevant self-test references ====='
rg -n -C8 'PRIMARY_OUT|PRIMARY_RC|gitleaks-guard|run_sut|svg NOT detected|upstream allowlist' scripts/selftest-binary-scan.sh

Repository: Jmeg8r/diffusiongemma-benchmark

Length of output: 38053


Distinguish guard failures from scan findings in Case 13.

gitleaks-guard.sh returns 1 for version rejection and for scanner findings. Check PRIMARY_OUT for a guard error before reporting that the primary gate scans .svg; otherwise a rejected version produces the wrong failure message.

🤖 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/selftest-binary-scan.sh` around lines 307 - 323, Update Case 13’s
PRIMARY_RC handling after invoking gitleaks-guard.sh to inspect PRIMARY_OUT for
a guard error such as version rejection before classifying the result. Report
guard failures separately, and only emit the “primary gate now scans .svg”
failure when the command succeeded as a guard invocation but returned a scan
finding.

@Jmeg8r
Jmeg8r force-pushed the chore/track-gate-kit branch from 120da41 to 78a1b39 Compare August 8, 2026 22:53

@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: 1

🤖 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/secret-scan.yml:
- Around line 143-149: Update the binary-file staging pipeline in the
secret-scan workflow to preserve NUL-delimited paths from git diff through git
add, removing the newline-based tr/grep/while processing that can corrupt
filenames. Use a NUL-aware filtering and iteration approach while keeping the
existing binary extensions, diff filters, and scan-staged-binaries.sh invocation
unchanged.
🪄 Autofix

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

Review profile: CHILL

Plan: Pro Plus

Run ID: a182a066-1945-4fc3-9c4b-3048921559d2

📥 Commits

Reviewing files that changed from the base of the PR and between 120da41 and 78a1b39.

📒 Files selected for processing (5)
  • .github/workflows/secret-scan.yml
  • .gitleaks.toml
  • lefthook.yml
  • scripts/scan-staged-binaries.sh
  • scripts/selftest-binary-scan.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • lefthook.yml

Comment thread .github/workflows/secret-scan.yml Outdated
@Jmeg8r
Jmeg8r force-pushed the chore/track-gate-kit branch from 78a1b39 to aa178cf Compare August 8, 2026 23:15

@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: 3

🤖 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/secret-scan.yml:
- Around line 202-204: Update the notices in the workflow step to scope the
limitation to the full-history scan, and state that binary scanning covers only
changed supported files. Include all supported formats—pdf, xlsx, docx, pptx,
svg, and zip—and remove the inaccurate claim that no scanner reads the listed
binaries.
- Around line 15-19: Update the workflow triggers and job structure so the
required secret scan runs from protected default-branch code, using a required
protected-branch workflow or pull_request_target that treats pull-request
content only as data. Keep proposed scanner configuration, pins, helper scripts,
and self-tests validated in a separate non-authoritative pull-request job, and
ensure the authoritative job never executes pull-request files.
- Around line 137-149: Update the “Scan this PR's binary files” step to select
the base commit from github.event.before for push events, while retaining the
merge-base logic for pull requests. Pass the event-before SHA through env,
reject an all-zero initial-push SHA, and verify the SHA is reachable before
assigning it to base; fail closed when validation fails so the binary scan
cannot be skipped.
🪄 Autofix

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 2fb24994-c085-4816-a196-ea2bfecd6b95

📥 Commits

Reviewing files that changed from the base of the PR and between 78a1b39 and aa178cf.

📒 Files selected for processing (1)
  • .github/workflows/secret-scan.yml

Comment on lines +15 to +19
on:
pull_request:
push:
branches: [main]
workflow_dispatch:

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 | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- workflow files ---'
git ls-files '.github/workflows/secret-scan.yml' 'scripts/gitleaks*' '.gitleaks.toml'

printf '%s\n' '--- workflow outline and relevant sections ---'
wc -l .github/workflows/secret-scan.yml
sed -n '1,70p' .github/workflows/secret-scan.yml
sed -n '150,220p' .github/workflows/secret-scan.yml

printf '%s\n' '--- scanner configuration and helper scripts ---'
for f in .gitleaks.toml scripts/gitleaks-version.env scripts/gitleaks-guard.sh; do
  if [ -f "$f" ]; then
    printf '\n--- %s ---\n' "$f"
    sed -n '1,240p' "$f"
  fi
done

printf '%s\n' '--- self-test references and workflow expressions ---'
rg -n 'self.?test|gitleaks|github\.(base_ref|event|sha|ref)|checkout|pull_request_target|secret-scan|required' \
  .github/workflows .gitleaks.toml scripts 2>/dev/null || true

Repository: Jmeg8r/diffusiongemma-benchmark

Length of output: 44813


🏁 Script executed:

#!/bin/bash
set -eu

sed -n '1,240p' .github/workflows/secret-scan.yml

Repository: Jmeg8r/diffusiongemma-benchmark

Length of output: 10840


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- exact review locations ---'
nl -ba .github/workflows/secret-scan.yml | sed -n '13,45p;60,135p'

printf '%s\n' '--- static call-graph check ---'
python3 - <<'PY'
from pathlib import Path

workflow = Path(".github/workflows/secret-scan.yml").read_text()
required = [
    "on:\n  pull_request:",
    "actions/checkout@",
    "scripts/test-gitleaks-guard.sh",
    "scripts/selftest-binary-scan.sh",
    "scripts/scan-staged-binaries.sh",
    "scripts/gitleaks-guard.sh",
    "scripts/gitleaks-version.env",
    ".gitleaks.toml",
]
for item in required:
    print(f"{item!r}: {item in workflow}")

checkout = workflow.index("actions/checkout@")
first_execution = min(
    workflow.index("scripts/test-gitleaks-guard.sh"),
    workflow.index("scripts/selftest-binary-scan.sh"),
    workflow.index("scripts/scan-staged-binaries.sh"),
    workflow.index("scripts/gitleaks-guard.sh"),
)
print("checkout_before_pr_file_execution:", checkout < first_execution)
print("job_name_context:", "jobs:\n  scan:" in workflow)
PY

Repository: Jmeg8r/diffusiongemma-benchmark

Length of output: 599


🌐 Web query:

GitHub Actions official documentation pull_request workflow runs workflow file from merge commit pull_request_target base branch context required status checks

💡 Result:

The pull_request_target event is a specialized GitHub Actions trigger designed for scenarios that require elevated privileges—such as accessing repository secrets or write permissions—when processing pull requests [1][2][3]. Contextual Differences Unlike the standard pull_request event, which runs in the context of the pull request's merge commit (a temporary merge of the head and base branches), the pull_request_target event runs in the context of the repository's default branch [1][4][2]. This ensures that the workflow file itself is always sourced from the trusted default branch, preventing attackers from modifying workflow logic via a pull request to gain unauthorized access [5][4]. Security and Checkout Behavior Because pull_request_target operates with the base repository's secrets and tokens, it is highly sensitive to malicious code injection, often referred to as a "pwn request" [6][2]. If a workflow is configured to check out and execute code from the untrusted fork (e.g., using actions/checkout with the pull request head SHA), that untrusted code will run with the workflow's elevated privileges [5][6][3]. To mitigate these risks, recent security updates have significantly tightened behavior: - As of June 2026, actions/checkout v7 by default refuses to fetch fork pull request code in pull_request_target workflows [6]. - The workflow execution context is fixed to the default branch to prevent outdated workflow files from being exploited [4]. - If a workflow genuinely requires testing code from a fork, developers must explicitly opt out of these protections [5][6]. This should be treated as a major security decision, as it potentially grants the fork's code access to the repository's environment secrets [6]. When building workflows for this event, it is recommended to use the github.base_ref and github.head_ref contexts to conditionally execute jobs based on the target or source branches [1][7]. If checking out code is necessary, it is safer to rely on immutable references (such as the specific commit SHA) rather than volatile merge refs if possible, though strict validation is required when handling untrusted fork inputs [6][8].

Citations:


Run the required scan from protected default-branch code.

A pull request controls this workflow and the checked-out scanner configuration, version pins, helper scripts, and self-tests. It can change the authoritative job to return success without performing the required scan.

Use a protected-branch required workflow, or a pull_request_target workflow that scans pull-request content only as data. Keep validation of proposed scanner changes in a separate non-authoritative pull-request job. Do not execute pull-request files in the authoritative job.

🤖 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/secret-scan.yml around lines 15 - 19, Update the workflow
triggers and job structure so the required secret scan runs from protected
default-branch code, using a required protected-branch workflow or
pull_request_target that treats pull-request content only as data. Keep proposed
scanner configuration, pins, helper scripts, and self-tests validated in a
separate non-authoritative pull-request job, and ensure the authoritative job
never executes pull-request files.

Comment thread .github/workflows/secret-scan.yml
Comment thread .github/workflows/secret-scan.yml
@Jmeg8r

Jmeg8r commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

⚠️ This branch's lefthook.yml is behind the kit on two fixes. Merging as-is commits both defects into tracked history. Verified on this branch specifically, not inferred — full detail and the affected list in coderabbit-local-kit#11.

1. Stale pytest step blocks every push in shell-only repos. This branch carries:

elif [ -d tests ]; then
  pytest -q --maxfail=1

A tests/ directory doesn't imply Python. In a shell-only repo pytest collects nothing and exits 5, lefthook reads any non-zero as a failed gate, and every push is blocked with nothing naming the cause. Fixed in kit#4 (84829e9), which dispatches on the collection exit code — 0 run, 5 skip, anything else fail loudly.

2. Missing today's no-big-files fix (kit#7 0e9557f, kit#10 1a2f98c). Without it: staging a large file then deleting it commits the blob unchecked (lefthook skips the job entirely), a symlink→large-file type change bypasses the gate, and symlinks and git-lfs assets are blocked as false positives.

Quick check on this branch:

grep -c collect_rc lefthook.yml             # want 4, currently 0
grep -c 'elif \[ -d tests \]' lefthook.yml  # want 0, currently 1
grep -c 'diff-filter=ACMRT' lefthook.yml    # want 1, currently 0

Take both blocks from templates/lefthook.yml on coderabbit-local-kit main.

No urgency — this PR is BEHIND/BLOCKED, so nothing reaches main until the branch is updated. Flagging rather than pushing: this branch belongs to an active campaign and heads here were seen moving mid-inspection, so an outside push risks a race.

gitleaks' built-in allowlist, inherited via [extend] useDefault, skips
pdf/xlsx/docx/bin/exe BY PATH, so the secrets gate reported "no leaks
found" after reading ~0 bytes of them.

scripts/scan-staged-binaries.sh closes it by changing the PATH gitleaks
sees rather than the config: OOXML is copied to a *.zip temp name and
traversed with --max-archive-depth, PDFs go through pdftotext into
`gitleaks stdin`. .gitleaks.toml stays the single source of truth with
every built-in rule live, so no rule is duplicated and none can drift.

Raw-byte PDF scanning was evaluated and rejected: content streams are
deflated, so a raw scan reads 0 bytes and reports clean.

The scans also now route through scripts/gitleaks-guard.sh, which
enforces a version floor -- allowlist semantics differ across gitleaks
versions, so a scanner whose version you cannot name has coverage you
cannot state. Both gates report N-scanned and fail closed: UNKNOWN,
never clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Jmeg8r
Jmeg8r force-pushed the chore/track-gate-kit branch from aa178cf to a363222 Compare August 8, 2026 23:40
@Jmeg8r
Jmeg8r merged commit c081f9a into main Aug 8, 2026
2 checks passed
@Jmeg8r
Jmeg8r deleted the chore/track-gate-kit branch August 8, 2026 23:45
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