From ac687ea8d381719166e0947102ec0d44ce7a7c03 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Fri, 8 May 2026 01:03:03 +0200 Subject: [PATCH] verify-action-build: ignore IDE config dirs in vendored node_modules diff Some upstream npm packages ship author IDE config trees (the case-in-point: node_modules/tunnel/.idea/* in benchmark-action's vendored deps). These leaked into a fresh `npm ci` but not the published vendored copy, producing noisy "Files only in rebuilt" warnings that aren't security-relevant. Add .idea, .vscode, .vs, .fleet, .zed to the noisy_dirs filter already used for npm install metadata. Two new tests pin both sides of the behaviour: IDE dirs ignored, but other dot-dirs that do carry runtime content (.bin) still compared. --- .../test_diff_node_modules.py | 63 +++++++++++++++++++ .../verify_action_build/diff_node_modules.py | 13 +++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/utils/tests/verify_action_build/test_diff_node_modules.py b/utils/tests/verify_action_build/test_diff_node_modules.py index 5116a1bea..b45715367 100644 --- a/utils/tests/verify_action_build/test_diff_node_modules.py +++ b/utils/tests/verify_action_build/test_diff_node_modules.py @@ -100,6 +100,69 @@ def test_noisy_files_ignored(self, tmp_path): ) assert result is True + def test_ide_config_dirs_ignored(self, tmp_path): + # Some upstream npm packages ship author IDE config trees + # (``node_modules/tunnel/.idea/*`` was the case-in-point in + # benchmark-action's vendored deps). These are not runtime code + # and shouldn't break verification when they leak into one side + # of the comparison and not the other. + orig = tmp_path / "original" + rebuilt = tmp_path / "rebuilt" + orig.mkdir() + rebuilt.mkdir() + + (orig / "tunnel").mkdir() + (rebuilt / "tunnel").mkdir() + (orig / "tunnel" / "index.js").write_text("code") + (rebuilt / "tunnel" / "index.js").write_text("code") + + # Only the rebuilt tree carries IDE files (the upstream package + # tarball includes them; the published vendored copy was scrubbed). + (rebuilt / "tunnel" / ".idea").mkdir() + (rebuilt / "tunnel" / ".idea" / "encodings.xml").write_text("") + (rebuilt / "tunnel" / ".idea" / "modules.xml").write_text("") + (rebuilt / "tunnel" / ".idea" / "node-tunnel.iml").write_text("") + (rebuilt / "tunnel" / ".idea" / "workspace.xml").write_text("") + + # Mirror the same noise on the original side under a different + # package, just to be sure we filter symmetrically. + (orig / "lodash").mkdir() + (rebuilt / "lodash").mkdir() + (orig / "lodash" / "index.js").write_text("code") + (rebuilt / "lodash" / "index.js").write_text("code") + (orig / "lodash" / ".vscode").mkdir() + (orig / "lodash" / ".vscode" / "settings.json").write_text("{}") + + result = diff_node_modules( + orig, rebuilt, "test", "repo", "a" * 40, + ) + assert result is True + + def test_other_dotdirs_in_packages_still_compared(self, tmp_path): + # Defensive: a non-IDE dot-directory inside a package that DOES + # carry meaningful content (``.bin``, ``.eslintrc``, etc.) must + # still be compared, not silently skipped. Use ``.bin`` here + # since that's a real npm convention for package executables. + orig = tmp_path / "original" + rebuilt = tmp_path / "rebuilt" + orig.mkdir() + rebuilt.mkdir() + + (orig / "pkg").mkdir() + (rebuilt / "pkg").mkdir() + (orig / "pkg" / "index.js").write_text("orig") + (rebuilt / "pkg" / "index.js").write_text("orig") + (orig / "pkg" / ".bin").mkdir() + (rebuilt / "pkg" / ".bin").mkdir() + (orig / "pkg" / ".bin" / "tool").write_text("ORIGINAL BINARY") + (rebuilt / "pkg" / ".bin" / "tool").write_text("DIFFERENT BINARY") + + result = diff_node_modules( + orig, rebuilt, "test", "repo", "a" * 40, + ) + # Content differs in .bin/tool — diff must still flag it. + assert result is False + def test_package_json_install_fields_ignored(self, tmp_path): import json orig = tmp_path / "original" diff --git a/utils/verify_action_build/diff_node_modules.py b/utils/verify_action_build/diff_node_modules.py index 695758639..55ef48820 100644 --- a/utils/verify_action_build/diff_node_modules.py +++ b/utils/verify_action_build/diff_node_modules.py @@ -32,9 +32,18 @@ def diff_node_modules( """Compare original vs rebuilt node_modules. Return True if they match.""" blob_url = f"https://github.com/{org}/{repo}/blob/{commit_hash}/node_modules" - # Metadata files that legitimately differ between installs + # Metadata files that legitimately differ between installs. noisy_files = {".package-lock.json", ".yarn-integrity"} - noisy_dirs = {".cache", ".package-lock.json"} + # Directories that occasionally leak into a vendored ``node_modules`` (or + # into a fresh ``npm ci``) but are not meaningful for verification: + # npm's per-install ``.cache``/lock metadata, plus IDE config trees that + # some upstream npm packages ship — see + # ``node_modules/tunnel/.idea/*`` in benchmark-action's vendored deps — + # which are author tooling artifacts, not runtime code. + noisy_dirs = { + ".cache", ".package-lock.json", + ".idea", ".vscode", ".vs", ".fleet", ".zed", + } def collect_files(base: Path) -> dict[Path, str]: """Collect all files under base with their SHA256 hashes."""