Skip to content

Bound gitleaks scans on macOS: per-user lock and 6 GiB footprint watchdog (Jetsam 2026-09-24) - #193

Merged
seathatflowsinourveins merged 2 commits into
mainfrom
claude/mystifying-franklin-12a679
Sep 24, 2026
Merged

seathatflowsinourveins merged 2 commits into
mainfrom
claude/mystifying-franklin-12a679

Conversation

@seathatflowsinourveins

@seathatflowsinourveins seathatflowsinourveins commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Why

At 09:45 and 09:46 EDT on 2026-09-24, Jetsam on the 24 GB MacBook Pro (M5 Pro, macOS 26.5.1) recorded three concurrent gitleaks 8.30.1 processes. Together they used 52.8 GiB, then 58.2 GiB, with the compressor holding about 80 to 84 GiB of data. macOS killed 405 processes, then 4,255.

Root cause. gitleaks' filter() calls report.Finding.Redact for every finding. Redact runs strings.ReplaceAll over Finding.Line, so it keeps one full copy of the matched line per finding. In git mode, a -U0 hunk holds whole lines. The generated explorer (docs/ecosystem/index.html) is one 12,889,851-byte line with 2,335 findings. That is about 30 GB, or 28 GiB, matching the 28.25 GiB process in the Jetsam report.

  • The two largest processes came from the global pre-commit hook: gitleaks git --pre-commit --staged --redact …. The hook ran on scratch commits from tests/test_catalog_freshness_propose.py, whose TrackedExplorerSubprocessTests force-adds the explorer. These runs happened before Make tests hermetic against global and system git config #181 made the tests' git config hermetic.
  • The recipe's all-refs gitleaks git --redact=100 hits the same problem: 240 commits in the history touch the explorer.
  • dir mode reads files in bounded chunks, so it stays near 0.13 GiB.

What changes

  • adoption/tools/gitleaks-guarded-macos is a macOS counterpart to gitleaks-guarded, using only the Python standard library. It keeps upstream arguments, output and exit codes.
    • Lock: one scan per user through a flock on ~/.local/state/ecosystem-gitleaks.lock. A read-only descriptor is enough, so sandboxed clients can take it. A second scan waits up to 60 s, then exits 75.
    • Caps: every 50 ms it sums the physical footprint (the figure Jetsam uses) of the scan's whole process tree. Above 6 GiB it SIGKILLs the tree and exits 137. After 600 s it stops the tree and exits 124. Callers can lower either cap but never raise it.
    • Refusals: exits 78 before any scan starts, for example when the binary or lock is unusable.
  • tests/test_gitleaks_guarded_macos.py: structural checks on every host, a refusal check off macOS, and on macOS integration checks against stub binaries. They cover exit codes, the memory cap on a grandchild, the runtime cap, lock busy/wait/serialisation/holding, and each refusal.
  • Receipt: evidence/receipts/gitleaks-macos-memory-bound-20260924.json holds sanitized before/after measurements, registered in manifests/evidence.json. It is not added to the gitleaks component's evidence_ids, because tests/test_stack_lifecycle would then require a row in the token-native saturation audit, which belongs to another lane. The harness is under evidence/artifacts/gitleaks-macos-memory-bound-20260924/.
  • Docs: adoption/tools/README.md gets a macOS section that replaces "macOS has no equivalent". recipes/README.md and adoption/update.md point macOS runs at the guard.

Measurements (this Mac, one scan at a time)

Peak is the sampled physical footprint of the process group (gitleaks + git + guard). The "before" runs were stopped by the measurement harness at its own cap, not by gitleaks.

Invocation Before After (through the guard)
Global hook, one staged line 0.05 GiB, exit 0 0.06 GiB, exit 0
Global hook, whole tree staged (104 MB diff) 0.49 GiB, exit 1 (71 unfingerprinted findings) 0.51 GiB, exit 1 (same findings)
Global hook, tree + generated explorer staged > 8 GiB in 19 s (harness kill) 6.09 GiB, exit 137
Real git commit through the installed hook, explorer staged gitleaks > 6 GiB in 17 s (harness kill) scratch copy of the hook calling the guard: exit 137 at 6.09 GiB, commit blocked
Two hook scans at once, explorer staged (the incident) serialised: 18 s + 18 s, both exit 137, combined peak 6.05 GiB
Recipe gitleaks git --redact=100, all refs > 8 GiB in 36 s (harness kill) 6.06 GiB, exit 137
CI history scope (--log-opts=HEAD --max-target-megabytes 2) not run 2.89 GiB, exit 0, 478 commits, no leaks
stack.json gitleaks dir … "${PROJECT_ROOT}" (worktree) 0.11 GiB, exit 1 (219) 0.12 GiB, exit 1 (219)
restart-receipt gitleaks dir . (worktree) 0.11 GiB, exit 0 0.12 GiB, exit 0
gitleaks dir . in the main checkout (714 MB incl. nested worktrees) 0.12 GiB, exit 1 (1,314) 0.13 GiB, exit 1 (1,314)
gitleaks dir . in a worktree with a 979 MB .runtime 0.11 GiB, exit 1 (18) 0.12 GiB, exit 1 (18)

Rejected alternatives, all measured on the same host:

  • --max-target-megabytes fails open. It skips the oversized fragment at debug log level, so the staged explorer's 2,335 findings came back as no leaks found with exit 0.
  • Top-level path exclusions skip whole files in both modes (see the .gitleaks.toml header), and dir memory was already bounded.
  • GOMEMLIMIT=4GiB still crossed the cap, because the growth is live findings.
  • An unprivileged Jetsam task limit (memorystatus_control) returns EPERM.

Limits. This is a user-space watchdog, not a kernel limit: measured overshoot was 0.04–0.20 GiB. If the guard itself is SIGKILLed, the scan runs on unwatched, though it keeps the lock. There is no CPU or task cap. A scan stopped at the cap produces no result. That is fail-closed, not coverage: this repository's all-refs history still has no complete redacted pass on macOS.

Hand-off (not edited here)

The global hook (~/.config/git/hooks/pre-commit) is managed by agent-ecosystem's config/git/hooks/dispatch. Its owner under the single-writer decision in seathatflowsinourveins/agent-ecosystem#28 is the foundation-lane-setup session. The change is handed off in seathatflowsinourveins/agent-ecosystem#40, which that session has taken. It reviewed this guard at b8cc293 and will vendor it pinned at the merge commit. The hand-off covers installing the guard, creating the lock once outside any sandbox, having the dispatcher call the guard, and routing the history-mirror scans through it. Nothing was installed on this host's PATH, no lock was created at the default path, and nothing global was edited.

Checks

  • python3 scripts/validate.py: passed (69 components, 144 receipts, 5,239 hashed files).
  • GITLEAKS_TESTS_REQUIRED=1 python3 -m unittest tests.test_gitleaks_config tests.test_adoption_guarded_runners tests.test_gitleaks_guarded_macos: 66 tests OK, 13 skipped (12 need a systemd --user scope; one runs only off macOS). Passes both sandboxed and unsandboxed.
  • python3 -m unittest discover -s tests (unsandboxed, this Mac, head a1d446b): 3,958 tests, 1 failure, 511 skipped. The failure is LlamaWrapperQuotingTests.test_install_path_with_shell_syntax_is_data_in_the_wrapper, the known real-Mac failure (SIP strips DYLD_LIBRARY_PATH) that Make the llama wrapper quoting test pass on SIP-enabled Macs (wrapper unaffected) #178 addresses; this PR doesn't touch it.
  • An earlier full run on b8cc293 also failed tests/test_stack_lifecycle because of the component evidence link, fixed in a1d446b. That run also had 21 guard-test errors: an aborted local rebase briefly deleted the guard while it ran, so those errors are not results.
  • CI-configuration gitleaks scan of this commit, through the guard, inside the sandbox: 1 commit, no leaks.

Follow-ups

🤖 Generated with Claude Code

…hdog

The 2026-09-24 09:45/09:46 Jetsam events (three gitleaks processes at 52.8,
then 58.2 GiB) came from gitleaks 8.30.1 redaction: filter() calls
Finding.Redact for every finding, and Redact copies the finding's whole line.
A git-mode hunk holds whole lines, so the generated explorer's 2,335 findings
on one 12.9 MB line retain about 28 GiB. The global pre-commit hook hit it on
test scratch commits (before #181); the recipe's all-refs history scan hits it
on the 240 commits that touch the explorer.

adoption/tools/gitleaks-guarded-macos gives macOS the Linux launcher's caps
without a cgroup: one scan per user through a flock that waits up to 60 s
(exit 75), a watchdog that sums the scan tree's physical footprint every 50 ms
and kills it above 6 GiB (exit 137) or after 600 s (exit 124), and refusals
(exit 78) before any scan. Gitleaks arguments, output and exit codes are
otherwise unchanged. --max-target-megabytes and path exclusions were measured
and rejected: the size skip turned 2,335 findings into "no leaks found".

Before/after peaks for every candidate invocation on this Mac are in
evidence/receipts/gitleaks-macos-memory-bound-20260924.json. The global hook
belongs to agent-ecosystem and is handed off to its owner, not edited here.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

tests/test_stack_lifecycle requires every stack.json evidence id to appear in
the token-native saturation audit's public_receipts row, which another lane
owns. The receipt stays registered in manifests/evidence.json on its own;
stack.json is back to its base bytes and hash.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@seathatflowsinourveins seathatflowsinourveins added the lane:foundation Foundation lane: Claude/Codex setup, hosts, memory, RAG, research, workers label Sep 24, 2026
@seathatflowsinourveins
seathatflowsinourveins merged commit 59d7ae7 into main Sep 24, 2026
22 of 25 checks passed
@seathatflowsinourveins
seathatflowsinourveins deleted the claude/mystifying-franklin-12a679 branch September 24, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lane:foundation Foundation lane: Claude/Codex setup, hosts, memory, RAG, research, workers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant