Secret Scanning
Consize treats a detected credential as a blocking security event. The Secret scan CI gate uses Gitleaks 8.30.1 to inspect both the current tracked source and every commit introduced by a pull request. This second scan matters because a credential remains exposed in Git history even when a later commit deletes it.
What the gate scans
For pull requests and merge queues, CI checks out complete history and scans:
- an archive of the current
HEAD, which contains tracked files only; and - the complete
merge-base..HEADcommit range.
For pushes to main, the gate scans the current tracked source and the commits
between the previous and current revisions. A missing commit, shallow checkout,
all-zero push base, empty comparison, or missing merge base fails the job rather
than producing a misleading clean result.
Repository owners must also run a full reachable-history scan when this control is introduced and after any approved history rewrite:
scripts/security/install-gitleaks.sh /tmp/gitleaks
GITLEAKS_BIN=/tmp/gitleaks SCAN_CONTEXT=scheduled_scan \
scripts/security/run-gitleaks.sh history /tmp/gitleaks-history.json
go run ./cmd/security-policy evaluate \
--policy .github/security/policy.json \
--exceptions .github/security/exceptions.json \
--report /tmp/gitleaks-history.json \
--output /tmp/gitleaks-history-decision.json
Tool integrity and output handling
The installer downloads a pinned official Gitleaks release over HTTPS and
verifies a platform-specific SHA-256 digest before extraction. Version and
checksums are reviewed in scripts/security/install-gitleaks.sh; updates must
change all three together and cite the corresponding official release.
Gitleaks runs with full redaction. Its temporary raw report is deleted at the
end of the scan and is never uploaded. cmd/gitleaks-report rejects unknown or
unredacted scanner fields and creates the common finding format using only:
- rule ID;
- repository-relative path and line;
- abbreviated commit ID;
- a stable fingerprint derived from metadata.
CI uploads only normalized redacted reports and policy decisions. Scanner errors, invalid JSON, absent reports, and normalization errors fail the gate.
Placeholders and false positives
.gitleaks.toml permits a short list of unmistakable placeholder values such
as replace-me and not-a-secret. It does not exclude environment files,
source directories, generated reports, or whole rules.
A real false positive must be scanned first and then recorded as an exact
false_positive entry in .github/security/exceptions.json. The entry needs a
rule ID, redacted fingerprint, exact component and path, owner, independent
approver when required, evidence that the value cannot authenticate, expiry,
review date, and remediation issue. The common policy validator rejects broad,
expired, malformed, and automatically renewed exceptions. Never place the
matched value in an exception or issue.
Live-secret response
If a finding may be a working credential:
- revoke or rotate it immediately;
- review provider activity, scope, and downstream copies;
- remove it from current source and CI or local configuration;
- determine whether reachable Git history contains it;
- obtain repository-owner approval before rewriting published history;
- record a redacted incident outcome and rotate related credentials when compromise cannot be excluded.
Deleting a file or commit does not revoke a credential. A live-secret exposure cannot be accepted through the ordinary exception registry.
Local verification
scripts/security/install-gitleaks.sh /tmp/gitleaks
GITLEAKS_BIN=/tmp/gitleaks go test ./internal/gitleaksreport ./cmd/gitleaks-report
GITLEAKS_BIN=/tmp/gitleaks scripts/security/test-gitleaks.sh
GITLEAKS_BIN=/tmp/gitleaks SCAN_CONTEXT=build \
scripts/security/run-gitleaks.sh snapshot /tmp/gitleaks-snapshot.json
The regression script creates a disposable repository, commits a synthetic non-credential token, deletes it in a later commit, and proves that the range scan still finds it without exposing the value. It also proves that documented placeholders pass and invalid or shallow comparison ranges fail.