From 369b64ed69572337fe7ec1f88cc0e5db014981a7 Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Thu, 17 Sep 2026 03:52:42 +0800 Subject: [PATCH] fix(contract): identify virtualenv roots by marker, not by name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `loopx check` in the canonical checkout of this project reports 35 boundary errors; the same commit in a clean worktree reports 7. The extra 28 are credential and `private_ip` hits under `.venv-conn/lib/python3.13/site-packages/` — anyio's and httpx's own source — because `DEFAULT_SKIP_DIRS` lists the literal name `.venv` while the project's environment is `.venv-conn`. Every project whose virtualenv is not named exactly `.venv` has its `site-packages` scanned as repository content, so the gate reports vendored third-party code as if it were this repository's. The scan walk now skips a directory when it is a virtualenv root, identified by its `pyvenv.cfg` marker rather than by a name. The marker is what actually distinguishes an environment from a directory that happens to be called `venv`, so the rule does not need a growing name list and does not skip an ordinary directory that merely looks like one. A scan root that is itself a virtualenv is skipped the same way the existing name-based skip handles it, still returning any tracked files. Validated: - `scan_public_boundary([])` drops from 35 hits to the 7 tracked-fixture hits that #4593 repairs; `iter_scan_files` returns 0 paths under `.venv-conn` or `.venv` where it previously returned the vendored tree. - New `tests/test_contract_scan_unreadable_files.py::test_iter_scan_files_skips_virtualenv_roots_by_marker` proves the marker decides: a `.venv-conn` with `pyvenv.cfg` is skipped, the same directory layout without the marker is still scanned, and the identical bytes outside a virtualenv still produce a hit, so the skip is not passing by reading nothing. - `pytest tests/test_contract_scan_unreadable_files.py` 5 passed; `examples/benchmark-candidate-source-boundary-smoke.py` ok. The three `tests/architecture/test_semantic_*` failures in the wider `-k "contract or boundary or scan"` selection reproduce unchanged on origin/main and are unrelated to this change. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- loopx/contract.py | 22 ++++++++++-- tests/test_contract_scan_unreadable_files.py | 38 ++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/loopx/contract.py b/loopx/contract.py index ae1db7a0f8..6f38729338 100644 --- a/loopx/contract.py +++ b/loopx/contract.py @@ -119,6 +119,20 @@ r"[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*\s*[,)\]}]" ) +# A virtualenv is identified by its marker file rather than by its name. +# `DEFAULT_SKIP_DIRS` can only list the names it already knows, so a project +# that names its environment `.venv-conn`, `venv311`, or anything else would +# have `site-packages` scanned as repository content, producing credential and +# private-IP hits from vendored third-party source. +VIRTUALENV_MARKER_FILE = "pyvenv.cfg" + + +def is_virtualenv_root(path: Path) -> bool: + try: + return (path / VIRTUALENV_MARKER_FILE).is_file() + except OSError: + return False + def _credential_match_is_reference(line: str, match: re.Match[str]) -> bool: value = line[match.start() :] @@ -752,14 +766,18 @@ def iter_scan_files(scan_root: Path) -> list[Path]: root_parts = set(scan_root.parts) if any(part in DEFAULT_SKIP_DIRS or part.endswith(".egg-info") for part in root_parts): return sorted(tracked_files) + if is_virtualenv_root(scan_root): + return sorted(tracked_files) for dir_path, dir_names, file_names in os.walk(scan_root): + current_dir = Path(dir_path) dir_names[:] = [ name for name in dir_names - if name not in DEFAULT_SKIP_DIRS and not name.endswith(".egg-info") + if name not in DEFAULT_SKIP_DIRS + and not name.endswith(".egg-info") + and not is_virtualenv_root(current_dir / name) ] - current_dir = Path(dir_path) for file_name in file_names: path = (current_dir / file_name).resolve() if path.name.endswith(".local.json"): diff --git a/tests/test_contract_scan_unreadable_files.py b/tests/test_contract_scan_unreadable_files.py index 9968fb9b7b..b9c9b68dea 100644 --- a/tests/test_contract_scan_unreadable_files.py +++ b/tests/test_contract_scan_unreadable_files.py @@ -33,6 +33,44 @@ def test_scan_public_boundary_survives_dangling_symlink(tmp_path: Path) -> None: assert payload["unreadable_files"] == [] +def test_iter_scan_files_skips_virtualenv_roots_by_marker(tmp_path: Path) -> None: + """A virtualenv is skipped by its marker file, not by its directory name. + + The skip list can only name environments it already knows. A project whose + environment is called `.venv-conn` or `venv311` would otherwise have + `site-packages` scanned as repository content, and vendored third-party + source would be reported as credential and private-IP hits. The payload is + assembled from split literals so this fixture does not trip the scan it is + describing. + """ + + keyword = "tok" + "en=" + payload = f'AUTH = "{keyword}syntheticsyntheticsynthetic"\n' + (tmp_path / "module.py").write_text(payload, encoding="utf-8") + + venv = tmp_path / ".venv-conn" + vendored = venv / "lib" / "site-packages" / "vendored.py" + vendored.parent.mkdir(parents=True) + (venv / "pyvenv.cfg").write_text("home = /usr/bin\n", encoding="utf-8") + vendored.write_text(payload, encoding="utf-8") + + unmarked = tmp_path / "vendor-copy" / "lib" / "site-packages" / "vendored.py" + unmarked.parent.mkdir(parents=True) + unmarked.write_text(payload, encoding="utf-8") + + scanned = iter_scan_files(tmp_path) + + assert tmp_path / "module.py" in scanned + assert vendored not in scanned + # The marker is what decides: the same layout without `pyvenv.cfg` is a + # normal directory and is still scanned. + assert unmarked in scanned + # The same bytes outside a virtualenv are still scanned and still hit, so + # the marker skip cannot be passing by reading nothing at all. + assert scan_public_boundary([venv])["hits"] == [] + assert scan_public_boundary([tmp_path / "module.py"])["hits"] + + def test_tracked_product_runtime_source_is_not_private_state( tmp_path: Path, monkeypatch: pytest.MonkeyPatch,