From 3bdc33e55b2162eb7b6badbeb442df57e46fba3e Mon Sep 17 00:00:00 2001 From: Hayden <154503486+groupthinking@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:52:22 -0500 Subject: [PATCH] fix(ci): stop uv.lock PyPI hashes failing the secret scan `gitleaks (working tree)` failed on every pull request. uv records each artifact as a PyPI download URL whose path embeds the artifact's own content hash, and the segment for parso 0.8.7 has enough length and entropy to match the default `square-access-token` rule. Nothing in a pull request's diff could cause or clear it. Allowlist the pattern by line, anchored on the public PyPI CDN host, rather than excluding the lockfile by path. A path exclusion would also hide a private index URL that embeds credentials inline, which is the case this job exists to catch. Verified with the pinned gitleaks 8.18.4: the working tree now reports no leaks, and a copy of the same offending URL rehosted on `pypi.internal.example.com` is still reported. tests/unit/test_secret_scan_config.py pins the shape of the suppression so it cannot be widened later: lockfiles stay scanned, the regex keeps its host anchor and `/packages/` prefix, `regexTarget` stays `line`, and the workflow keeps loading the config. Fixes #1158 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .gitleaks.toml | 15 ++++ tests/unit/test_secret_scan_config.py | 112 ++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 tests/unit/test_secret_scan_config.py diff --git a/.gitleaks.toml b/.gitleaks.toml index 1d91e9d8e..66506a07c 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -6,6 +6,21 @@ useDefault = true [allowlist] description = "Paths excluded from secret scanning" +# Match against the whole source line rather than the extracted secret, so the +# regexes below can require the surrounding package-URL context. +regexTarget = "line" +regexes = [ + # Dependency lockfiles record every artifact as a PyPI download URL whose path + # embeds the artifact's own content hash, e.g. + # https://files.pythonhosted.org/packages/30/4b/90c9378...a7/parso-0.8.7.tar.gz + # Those hex segments are public integrity digests, but their length and + # entropy collide with generic provider-token rules (parso 0.8.7 currently + # trips `square-access-token`). Anchoring on the public PyPI CDN host keeps + # the rest of the lockfile scanned: a private index URL carrying real + # credentials (https://user:token@pypi.internal/...) does not match this and + # is still reported. + '''https://files\.pythonhosted\.org/packages/''', +] paths = [ # Vendored saved web pages from Google (AI Studio / APIs Explorer): contain # Google's own public page keys, not EventRelay credentials. diff --git a/tests/unit/test_secret_scan_config.py b/tests/unit/test_secret_scan_config.py new file mode 100644 index 000000000..d6fd3f8cd --- /dev/null +++ b/tests/unit/test_secret_scan_config.py @@ -0,0 +1,112 @@ +"""Guards for the gitleaks allowlist used by the ``gitleaks (working tree)`` job. + +``uv.lock`` records every artifact as a PyPI download URL whose path embeds the +artifact's own content hash. One of those hex segments has enough length and +entropy to trip the default ``square-access-token`` rule, so the secret scan +failed on every pull request regardless of its diff. + +The suppression has to stay narrow. Excluding the lockfile wholesale would also +hide a private index URL carrying real credentials, which is exactly the kind of +secret this job exists to catch. These checks pin the shape of the fix: + +* the lockfile itself is still scanned, +* the suppression is anchored to the public PyPI CDN host, and +* the workflow actually loads this configuration. +""" + +import tomllib +import unittest +from pathlib import Path + + +def _repo_root(): + for candidate in Path(__file__).resolve().parents: + if (candidate / ".gitleaks.toml").exists(): + return candidate + raise AssertionError("repository root not found") + + +REPO_ROOT = _repo_root() +CONFIG_PATH = REPO_ROOT / ".gitleaks.toml" +WORKFLOW_PATH = REPO_ROOT / ".github" / "workflows" / "secret-scan.yml" + +# Lockfiles are the files the false positive lives in. They must never be +# allowlisted by path, because a private index URL embeds its credentials +# inline and would then go unreported. +SCANNED_LOCKFILES = ("uv.lock", "package-lock.json", "poetry.lock") + + +def _config(): + with CONFIG_PATH.open("rb") as handle: + return tomllib.load(handle) + + +class SecretScanConfigTests(unittest.TestCase): + def setUp(self): + self.config = _config() + self.allowlist = self.config.get("allowlist", {}) + + def test_config_extends_the_default_ruleset(self): + """Dropping ``useDefault`` would silently disable every built-in rule.""" + self.assertTrue( + self.config.get("extend", {}).get("useDefault"), + ".gitleaks.toml must extend the default gitleaks ruleset", + ) + + def test_lockfiles_are_not_excluded_by_path(self): + """A path exclusion would hide real credentials in the same file.""" + paths = self.allowlist.get("paths", []) + for lockfile in SCANNED_LOCKFILES: + for pattern in paths: + self.assertNotIn( + lockfile, + pattern, + f"{lockfile} must stay scanned; found path allowlist " + f"{pattern!r}. Narrow the suppression to the benign " + f"pattern instead of excluding the file.", + ) + + def test_pypi_suppression_is_anchored_to_the_public_cdn_host(self): + """The regex must require the CDN host, not just a hash-shaped string.""" + regexes = self.allowlist.get("regexes", []) + self.assertTrue( + any("files" in r and "pythonhosted" in r for r in regexes), + "expected an allowlist regex anchored on files.pythonhosted.org; " + f"got {regexes!r}", + ) + for regex in regexes: + if "pythonhosted" not in regex: + continue + self.assertIn( + "/packages/", + regex, + "the suppression must require the /packages/ URL prefix so it " + "cannot match arbitrary text mentioning the host", + ) + + def test_regex_allowlist_matches_whole_lines(self): + """``regexes`` compare against the extracted secret unless retargeted. + + The secret here is a bare hex path segment, so the host anchor only + works when the allowlist is evaluated against the full line. + """ + if not self.allowlist.get("regexes"): + self.skipTest("no regex allowlist configured") + self.assertEqual( + self.allowlist.get("regexTarget"), + "line", + "regexTarget must be 'line' for the host-anchored regex to apply", + ) + + def test_workflow_loads_this_configuration(self): + """An allowlist the scan never reads is not a fix.""" + workflow = WORKFLOW_PATH.read_text(encoding="utf-8") + self.assertIn( + "--config .gitleaks.toml", + workflow, + "secret-scan.yml must run gitleaks with --config .gitleaks.toml", + ) + + +if __name__ == "__main__": + unittest.main()