A Gitleaks Crash on a Multi-File Commit, and a Supply-Chain Gate Was Born
The secret-scanner hook crashed on a multi-file commit. One missing flag in the hook definition, and it ended with checksum-pinned CI.
TL;DR
The gitleaks system hook kept crashing on multifile commits because it mishandles the file list. Instead of waiting for a fix I switched to a local hook that runs a checksum verified binary on the staged diff. I apply the same strict pinning in CI with hashed downloads and minimal permissions accepting manual updates for stronger supply chain security.
A Local Hook Whose Checksum I Hold
While committing multiple files at once, my terminal suddenly threw an error. The gitleaks-system pre-commit hook crashed outright without any clear message.
At first, I guessed the gitleaks binary on my local machine was corrupted, or maybe something went wrong during installation. I even tried reinstalling and clearing the cache. But after checking the documentation and issue tracker, it turned out to be a known problem. The official .pre-commit-hooks.yaml file uses language: system for the gitleaks-system block and lacks the pass_filenames: false setting [1].
As a result, when there are multi-file changes, this hook fails because it tries to process an unexpectedly long or malformed list of files. This issue has been reported since version 8.27.2. A fix was proposed upstream, but when I checked, the upstream block I was using was still broken in the version running at the time [2].
I wasn't going to wait for upstream to release a patch. The solution was to build my own local hook named gitleaks-local. This hook runs a sha256-verified binary directly against the staged diff, bypassing the problematic built-in system execution.
- repo: local
hooks:
- id: gitleaks-local
name: Detect hardcoded secrets (checksum-verified install)
entry: gitleaks git --pre-commit --redact --staged --no-banner --verbose
language: system
pass_filenames: false
CI: the only action is checkout, and it is pinned
I apply the exact same security philosophy to my CI pipeline. Here, zero blind trust is the absolute rule.
The only uses: I allow in my workflow is actions/checkout pinned to a full commit SHA (version 4). I don't call any third-party actions, because they often request unnecessary access to secrets or the GITHUB_TOKEN [3]. The gitleaks 8.30.1 and actionlint 1.7.12 binaries are downloaded directly via curl from official sources, then immediately validated using sha256sum -c. The checksum hashes are pinned directly inside the workflow file, not fetched dynamically from the latest release.
- name: Install gitleaks (checksum-pinned)
run: |
curl -sSfL -o /tmp/gitleaks.tar.gz \
"https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz"
echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb /tmp/gitleaks.tar.gz" | sha256sum -c -
mkdir -p "$HOME/bin" && tar -xzf /tmp/gitleaks.tar.gz -C "$HOME/bin" gitleaks
I also lock permissions down tightly to just contents: read. If the pipeline only needs to read code to scan it, it shouldn't get write access. Period.
The trade-offs I accept
This approach has real consequences. Updates become manual. I have to diligently check for new releases and change the SHA hashes manually in two different places.
This means there is a double checksum: one source of truth in the local hook, and another in the CI workflow. For some, this might feel tedious and slow. But it is a trade-off I gladly accept.
The CI supply chain is a very real attack surface. Provenance in the SLSA v1.0 standard means nothing without verification. We must check the builder identity, signature, and buildType against our expectations, not just blindly trust a slapped-on label [4]. Imagine if someone sneaked malicious code into an action we use; without SHA pinning, we would execute it without a second thought.
GitHub can now even force admins to enforce SHA pinning via the allowed-actions policy, and workflows using unpinned actions will fail outright [5]. Securing the repo supply chain gateway requires extra effort, but a good night's sleep is worth far more than momentary convenience.