feat(secrets): gitleaks subprocess backend with regex fallback (closes #159)#164
Merged
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
10 tasks
d04a653 to
994158c
Compare
…#159) Upgrade `codelens secrets` to use gitleaks as the primary backend when available (600+ maintained rules, entropy scoring, structured JSON output). Falls back to the existing regex scanner when gitleaks is not installed or `--no-gitleaks` is set. Gitleaks is an opt-in upgrade, never a hard dependency. New files: - scripts/gitleaks_backend.py — self-contained gitleaks integration (454 lines) - _gitleaks_available() — detects binary via `gitleaks version` - _run_gitleaks() — invokes `gitleaks detect --no-git --report-format json`, writes to temp file, parses JSON, handles both list and {Results:[...]} schemas (older gitleaks versions), cleans up temp file - _normalize_gitleaks_findings() — maps gitleaks JSON fields to CodeLens finding schema (RuleID→type, File→file, StartLine→line, Secret→masked match/value, Tags→tags, Fingerprint→fingerprint) - _infer_severity() — heuristic severity mapping (aws-access-key/private-key/ github-pat/stripe-secret/etc → critical; 'critical'/'high'/'medium'/'low' in rule ID or tags → matching severity; default → high) - _mask_secret() — first 4 chars + *** (matches secrets_engine convention) - scan_with_gitleaks() — public entry point; returns CodeLens-shaped result dict with backend='gitleaks', or None if gitleaks unavailable - GitleaksError — caught by caller, falls back to regex with warning - tests/test_secrets_gitleaks.py — 56 tests (543 lines) - Detection (mocked subprocess: available, not-found, timeout, nonzero, unexpected exception) - Masking (long, short, empty, 4-char, 5-char) - Severity inference (aws/private-key/github-pat/stripe → critical; tags with high/medium/low; default → high) - Normalization (basic, masking, abs→rel paths, tags as string, empty, non-dict entries, missing fields) - Stats/risk/recommendations (counts, files_with_findings, critical→high →medium→low risk cascade, recommendation content) - _run_gitleaks (mocked: JSON parse, empty file, missing file, nonzero exit, timeout, invalid JSON, {Results:[...]} schema) - scan_with_gitleaks (full integration mocked: result dict shape, severity filter, nonexistent workspace) - CLI integration (subprocess): --no-gitleaks in help, regex backend + install hint when gitleaks absent, --no-gitleaks suppresses hint, stats.backend field set Modified files: - scripts/commands/secrets.py (73 lines): - Add --no-gitleaks flag (force regex backend) - execute() tries gitleaks first (unless --no-gitleaks); on gitleaks failure, falls back to regex with stderr warning (never crashes) - Tags result with backend='regex' or 'gitleaks' - When gitleaks unavailable, adds gitleaks_hint field with install URL - stats.backend also set so compact/ai formatters pick it up - SKILL.md — add 'Gitleaks-Backed Secrets Scanner' to v8.2 capability pillars with install instructions Design decisions: - gitleaks logic in NEW module (gitleaks_backend.py), not in secrets_engine.py — avoids collision with PR #148 (which touches secrets_engine.py for confidence scores). commands/secrets.py is the wiring point. - Gitleaks is OPT-IN: if not installed, regex runs unchanged with a hint. Never a hard dependency. - --no-gitleaks forces regex even if gitleaks is available — useful for testing, offline environments, or when gitleaks produces unexpected results. - --no-git flag passed to gitleaks (scan working tree, not git history) to match the regex backend's behavior. Git history scanning can be added later via a --git-history flag if needed. - Normalized findings include 'backend' field so consumers can tell which scanner produced each finding (useful when mixing backends in future). Verified: - 56 new tests pass (test_secrets_gitleaks.py — all subprocess mocked) - 109 passed regression check (test_secrets_engine + test_cli + test_command_count + test_command_registry — no regressions) - Command count unchanged at 70 (same command, upgraded backend) - End-to-end: --no-gitleaks in help, regex backend + hint when gitleaks absent, --no-gitleaks suppresses hint, stats.backend set Findings (per pre-flight SKILL.md — flag to BOS): - PR #148 (issue #5, open) touches scripts/secrets_engine.py (+16 lines, adding confidence scores). This PR touches scripts/commands/secrets.py (different file). No collision. Both can merge independently. The gitleaks backend's normalized findings include a 'confidence' field position in the schema — PR #148's confidence.py module can score gitleaks findings the same way as regex findings if desired.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Closes #159
Summary
Upgrades
codelens secretsto use gitleaks as the primary backend when available (600+ maintained rules, entropy scoring, structured JSON output). Falls back to the existing regex scanner when gitleaks is not installed or--no-gitleaksis set. Gitleaks is an opt-in upgrade, never a hard dependency.What changed
New files (2)
scripts/gitleaks_backend.py— self-contained gitleaks integration (454 lines)_gitleaks_available()— detects binary viagitleaks version_run_gitleaks()— invokesgitleaks detect --no-git --report-format json, writes to temp file, parses JSON, handles both list and{Results:[...]}schemas (older gitleaks versions), cleans up temp file_normalize_gitleaks_findings()— maps gitleaks JSON fields to CodeLens finding schema (RuleID→type, File→file, StartLine→line, Secret→masked match/value, Tags→tags, Fingerprint→fingerprint)_infer_severity()— heuristic severity mapping (aws-access-key/private-key/github-pat/stripe-secret/etc → critical; 'critical'/'high'/'medium'/'low' in rule ID or tags → matching severity; default → high)_mask_secret()— first 4 chars +***(matchessecrets_engine._mask_valueconvention)scan_with_gitleaks()— public entry point; returns CodeLens-shaped result dict withbackend='gitleaks', orNoneif gitleaks unavailableGitleaksError— caught by caller, falls back to regex with warningtests/test_secrets_gitleaks.py— 56 tests (543 lines)Modified files (2)
scripts/commands/secrets.py(73 lines added):--no-gitleaksflag (force regex backend)execute()tries gitleaks first (unless--no-gitleaks); on gitleaks failure, falls back to regex with stderr warning (never crashes)backend='regex'or'gitleaks'gitleaks_hintfield with install URLstats.backendalso set so compact/ai formatters pick it upSKILL.md— add "Gitleaks-Backed Secrets Scanner" to v8.2 capability pillars with install instructionsAcceptance criteria (from issue body)
_gitleaks_available()detection inscripts/commands/secrets.py— ingitleaks_backend.py(called fromcommands/secrets.py)_run_gitleaks()uses--report-format json --report-path <tempfile>_normalize_gitleaks_findings()execute()catchesGitleaksError, prints stderr warning, runsdetect_secrets()--no-gitleaksflag to force regex fallback (useful for testing) — added toadd_args()stats.backendfield in output:"gitleaks"or"regex"— set in bothscan_with_gitleaks()andexecute()regex pathgitleaks_hintfield with install URLSKILL.mdupdated: note that gitleaks improves accuracy, link to install docs — added as capability pillar [ARCH] Replace flat registry with true graph data model (nodes + edges) #8sync_command_count.py --applyrun (count unchanged — same command, upgraded backend) — verified count = 70Design decisions
gitleaks logic in NEW module (
gitleaks_backend.py), not insecrets_engine.py— avoids collision with PR feat(agent-ux): confidence scores per finding + schema_version in JSON output (closes #5) #148 (which touchessecrets_engine.pyfor confidence scores).commands/secrets.pyis the wiring point;secrets_engine.pyis untouched.Gitleaks is OPT-IN — if not installed, regex runs unchanged with a hint. Never a hard dependency. This matches the issue's explicit requirement: "Some environments cannot install binaries (locked-down CI, read-only filesystems). The regex fallback ensures
codelens secretsalways works, everywhere, without setup."--no-gitflag passed to gitleaks — scans the working tree only (not git history) to match the regex backend's behavior. Git history scanning can be added later via a--git-historyflag if needed. This keeps the two backends semantically equivalent.Normalized findings include
backendfield — so consumers can tell which scanner produced each finding (useful for debugging false positives/negatives, and for future mixed-backend scenarios).Severity inference is heuristic — gitleaks doesn't have a native severity field. The heuristic maps high-value secret types (aws-access-key, private-key, github-pat, etc.) to
critical, checks for severity keywords in rule ID/tags, and defaults tohigh(gitleaks rules are curated, so default-high is reasonable). Documented in the_infer_severitydocstring.GitleaksErrorcaught, never crashes — the command always returns a result. Gitleaks failure produces a stderr warning and falls back to regex. This matches the issue's "graceful fallback" requirement.Tests
tests/test_secrets_gitleaks.pyTestGitleaksAvailable(5) — detection: available, not-found, timeout, nonzero, exceptionTestGitleaksVersion(2) — version string / NoneTestMaskSecret(5) — long, short, empty, 4-char, 5-charTestInferSeverity(12) — all high-value markers, tag-based, rule-ID-based, defaultTestNormalizeFindings(7) — basic, masking, abs→rel paths, tags-as-string, empty, non-dict, missing fieldsTestStatsRiskRecs(9) — counts, files_with_findings, risk cascade, recommendation contentTestRunGitleaks(7) — JSON parse, empty file, missing file, nonzero exit, timeout, invalid JSON,{Results:[...]}schemaTestScanWithGitleaks(4) — returns None when unavailable, full result dict, severity filter, nonexistent workspaceTestCliIntegration(4) —--no-gitleaksin help, regex backend + hint when gitleaks absent,--no-gitleakssuppresses hint,stats.backendsettest_secrets_engine+test_cli+test_command_count+test_command_registry→ 53 passed, 0 failed (no regressions)End-to-end verification
Findings (per pre-flight SKILL.md — flag to BOS)
scripts/secrets_engine.py(+16 lines, adding confidence scores viascripts/confidence.py). This PR touchesscripts/commands/secrets.py(different file) and addsscripts/gitleaks_backend.py(new file). No file collision — both can merge independently. The gitleaks backend's normalized findings include the same schema fields (type,file,line,match,severity,category) that PR feat(agent-ux): confidence scores per finding + schema_version in JSON output (closes #5) #148'sconfidence.pyexpects, so confidence scoring will work on gitleaks findings the same way as regex findings.Branch
feat/issue-159-secrets-gitleaks-backend