description = "Scoped path allowlist for dummy-cred test fixtures (GR-GAP-005)"
Root cause. The gitreins builtin secrets scanner (guard_manager.py → _builtin_secrets_scan / _load_gitleaks_allowlist, GR-GAP-005) honors only path entries from the top-level .gitleaks.toml [allowlist] table. It never parses //gitleaks:allow inline directives, so annotating the dummy-cred fixtures inline is silently ineffective. The correct fix is scoped path allowlist entries — narrow globs matching only the fixture files, so CI noise disappears without weakening real secret detection.
Fix 1 (primary) — .gitleaks.toml: add scoped path allowlist entries for the dummy-cred fixtures, never broad regexes and never all of test/:
# .gitleaks.toml
[allowlist]
description = "Scoped path allowlist for dummy-cred test fixtures (GR-GAP-005)"
paths = [
# dummy-cred fixtures live in dedicated test dirs; allow ONLY those
'''test/fixtures/dummy-cred/**''',
'''internal/**/testdata/dummy-cred/**''',
# generated snapshots of the fixture (never shipped)
'''**/dummy-cred.snap''',
]
Optional per-rule scoping (upstream-gitleaks parity; the builtin loader reads the top-level table):
[[rules]]
id = "dummy-cred"
regex = '''(?i)dummy[-_ ]?cred(?:ential)?["']?\s*[:=]\s*["'][A-Za-z0-9+/=_-]{12,}["']'''
[rules.allowlist]
paths = ['''test/fixtures/dummy-cred/**''', '''internal/**/testdata/dummy-cred/**''']
Fix 2 (companion) — guard_manager.py: make _load_gitleaks_allowlist translate gitleaks globs correctly so the scoped entries actually match (anchored regexes, **/ = zero-or-more dirs, ** crosses /, */? don't), normalize Windows separators, and make _builtin_secrets_scan skip allowlisted paths before scanning:
def _glob_to_regex(glob: str) -> re.Pattern:
"""gitleaks/gitignore-style path glob -> anchored regex."""
parts: list[str] = []
i, n = 0, len(glob)
while i < n:
c = glob[i]
if c == "*":
if i + 2 < n and glob[i + 1] == "*" and glob[i + 2] == "/":
parts.append("(?:.*/)?") # **/ zero-or-more dirs
i += 3
elif i + 1 < n and glob[i + 1] == "*":
parts.append(".*") # ** crosses /
i += 2
else:
parts.append("[^/]*") # * within one segment
i += 1
elif c == "?":
parts.append("[^/]"); i += 1
elif c == "[":
j = i + 1
if j < n and glob[j] in "!^": j += 1
if j < n and glob[j] == "]": j += 1
while j < n and glob[j] != "]": j += 1
if j >= n:
parts.append(re.escape("[")); i += 1
else:
stuff = glob[i + 1 : j].replace("\\", "\\\\")
if stuff.startswith("!"): stuff = "^" + stuff[1:]
elif stuff.startswith("^"): stuff = "\\" + stuff
parts.append("[" + stuff + "]"); i = j + 1
else:
parts.append(re.escape(c)); i += 1
return re.compile("(?s:" + "".join(parts) + r")\Z")
def _load_gitleaks_allowlist(config_path: Path) -> list[re.Pattern]:
if not config_path.exists():
return []
try:
data = tomllib.loads(config_path.read_text(encoding="utf-8"))
except (tomllib.TOMLDecodeError, OSError):
return []
allowlist = data.get("allowlist", {}) or {}
out = []
for entry in allowlist.get("paths", []) or []:
entry = str(entry).strip().strip("'\"")
if not entry:
continue
entry = entry.replace("\\", "/") # Windows separators
out.append(_glob_to_regex(entry))
return out
def _builtin_secrets_scan(repo_root: Path, gitleaks_config: Path) -> list[Finding]:
allow = _load_gitleaks_allowlist(gitleaks_config)
findings = []
for f in sorted(repo_root.rglob("*")):
if not f.is_file() or f.name == ".gitleaks.toml":
continue
rel = f.relative_to(repo_root).as_posix()
if any(rx.match(rel) for rx in allow):
continue # scoped path allowlist -> suppress fixture noise
... # per-line rule regex matching unchanged
return findings
Verified in a faithful sandbox replicating the scanner contract (`/tmp/grgap005`: `guard_manager.py`, `.gitleaks.toml`, `test/fixtures/dummy-cred/…`, `src/main.go`). **10/10 checks pass**, lint clean, harness idempotent (3 consecutive runs green). | # | Check | Result | |---|-------|--------| | 1 | `test/fixtures/dummy-cred/**` matches nested files, not `src/main.go`, not sibling `dummy-credx/` | PASS | | 2 | `**/dummy-cred.snap` matches root-level AND fixture snaps | PASS | | 3 | Baseline (empty allowlist): dummy-cred fixture flagged (noise reproduced) | PASS | | 4 | **Inline `//gitleaks:allow` ignored**: `src/main.go:11` with trailing `//gitleaks:allow` still reported (`[generic-secret] Token = "dummy-cred-back…"`) — proves why path entries are required | PASS | | 5 | After fix: zero findings under `test/fixtures/dummy-cred/` | PASS | | 6 | Real secrets still flagged: `aws-access-key` + `generic-secret` in `src/main.go` | PASS | | 7 | Scoping: `src/fixtures/dummy-cred/leak.go` (outside the globs) still flagged | PASS | | 8 | Missing `.gitleaks.toml` → empty allowlist, full scan, no crash | PASS | | 9 | Root-level + fixture `.snap` suppressed in live scan; non-dummy `src/real.snap` still flagged | PASS | | 10 | Windows `\` separators in entries normalized to `/` | PASS | Edge cases covered: nested fixture subdirectories, globs must be anchored (no prefix leakage), `**/` at root level, broken/absent TOML config, stale-state independence, exact-scope narrowing (a same-named dir outside `test/` is not allowlisted). ---
{"model": "deepseek-v4-flash", "problem_class": "go-gitleaks-builtin-scanner-inline-allow-ineffective", "result": "passed", "tests": 10}