software-intake-gatekeeper 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/CHANGELOG.md +12 -0
  2. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/PKG-INFO +8 -7
  3. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/README.md +7 -6
  4. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/pyproject.toml +1 -1
  5. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/__init__.py +1 -1
  6. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/diff.py +11 -1
  7. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/electron.py +4 -1
  8. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/tests/test_diff.py +46 -0
  9. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/.gitignore +0 -0
  10. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/LICENSE +0 -0
  11. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/SECURITY.md +0 -0
  12. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/action-requirements.txt +0 -0
  13. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/action.yml +0 -0
  14. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/demo-app-src/dist/settings.min.js +0 -0
  15. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/demo-app-src/main.js +0 -0
  16. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/demo-app-src/node_modules/@demo/ui-kit/package.json +0 -0
  17. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/demo-app-src/node_modules/lodash/index.js +0 -0
  18. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/demo-app-src/node_modules/lodash/package.json +0 -0
  19. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/demo-app-src/node_modules/minimist/package.json +0 -0
  20. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/demo-app-src/package.json +0 -0
  21. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/demo-app-src/preload.js +0 -0
  22. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/go-demo/go.mod +0 -0
  23. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/go-demo/go.sum +0 -0
  24. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/go-demo/main.go +0 -0
  25. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/intake-policy.toml +0 -0
  26. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/update-demo/v1/resources/app/main.js +0 -0
  27. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/update-demo/v1/resources/app/node_modules/lodash/package.json +0 -0
  28. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/update-demo/v1/resources/app/package.json +0 -0
  29. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/update-demo/v2/resources/app/main.js +0 -0
  30. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/update-demo/v2/resources/app/node_modules/axios/package.json +0 -0
  31. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/update-demo/v2/resources/app/node_modules/fast-native/build/Release/addon.node +0 -0
  32. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/update-demo/v2/resources/app/node_modules/lodash/package.json +0 -0
  33. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/update-demo/v2/resources/app/package.json +0 -0
  34. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/vulnerable-demo/dotnet/IntakeDemo.deps.json +0 -0
  35. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/vulnerable-demo/dotnet/IntakeDemo.runtimeconfig.json +0 -0
  36. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/examples/vulnerable-demo/resources/app.asar +0 -0
  37. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/__main__.py +0 -0
  38. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/asar.py +0 -0
  39. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/cli.py +0 -0
  40. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/engine.py +0 -0
  41. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/golang.py +0 -0
  42. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/model.py +0 -0
  43. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/osv.py +0 -0
  44. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/output.py +0 -0
  45. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/policy.py +0 -0
  46. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/py.typed +0 -0
  47. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/rules.py +0 -0
  48. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/src/intake_gatekeeper/scanner.py +0 -0
  49. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/tests/conftest.py +0 -0
  50. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/tests/test_asar.py +0 -0
  51. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/tests/test_cli.py +0 -0
  52. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/tests/test_electron.py +0 -0
  53. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/tests/test_golang.py +0 -0
  54. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/tests/test_live_osv.py +0 -0
  55. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/tests/test_osv.py +0 -0
  56. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/tests/test_output.py +0 -0
  57. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/tests/test_policy.py +0 -0
  58. {software_intake_gatekeeper-0.2.1 → software_intake_gatekeeper-0.2.2}/tests/test_scanner.py +0 -0
@@ -3,6 +3,17 @@
3
3
  All notable changes are recorded here. The project follows [Semantic Versioning](https://semver.org/);
4
4
  while the major version is 0, minor releases may change behaviour.
5
5
 
6
+ ## [0.2.2] - 2026-10-15
7
+
8
+ ### Fixed
9
+
10
+ - Update check: code findings in bundles whose file names carry a content hash
11
+ (`index-Cz8Eo_n9.js`, `main.3f2a1b9c.js`) are matched across versions instead of being reported
12
+ as new and resolved. Found on a real MarkText 0.21.1 to 0.21.2 comparison, where it removed 10
13
+ false new findings.
14
+ - Network endpoints only count URLs that start a string literal. URLs in comments and license
15
+ headers are no longer reported; on the same comparison, new endpoints dropped from 44 to 1.
16
+
6
17
  ## [0.2.1] - 2026-10-15
7
18
 
8
19
  ### Added
@@ -52,6 +63,7 @@ First public release and first GitHub Marketplace listing.
52
63
  - No Rust `cargo auditable`, Python wheel, Java or native C/C++ inventories yet.
53
64
  - Electron code rules detect literal settings only.
54
65
 
66
+ [0.2.2]: https://github.com/Sushant-Nemade/software-intake-gatekeeper/releases/tag/v0.2.2
55
67
  [0.2.1]: https://github.com/Sushant-Nemade/software-intake-gatekeeper/releases/tag/v0.2.1
56
68
  [0.2.0]: https://github.com/Sushant-Nemade/software-intake-gatekeeper/releases/tag/v0.2.0
57
69
  [0.1.0]: https://github.com/Sushant-Nemade/software-intake-gatekeeper/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: software-intake-gatekeeper
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Local, evidence-first intake checks for third-party apps and binaries: SBOM, known vulnerabilities and Electron hardening.
5
5
  Project-URL: Homepage, https://github.com/Sushant-Nemade/software-intake-gatekeeper
6
6
  Project-URL: Source, https://github.com/Sushant-Nemade/software-intake-gatekeeper
@@ -61,7 +61,7 @@ Every finding names the file and, for scripts, the line, column and snippet that
61
61
  Anything the scanner could not verify is reported as a **coverage gap**. A blocking gap can
62
62
  never lead to APPROVE: an unknown result is not treated as safe.
63
63
 
64
- > Status: [v0.2.1](https://github.com/Sushant-Nemade/software-intake-gatekeeper/releases/tag/v0.2.1),
64
+ > Status: [v0.2.2](https://github.com/Sushant-Nemade/software-intake-gatekeeper/releases/tag/v0.2.2),
65
65
  > an alpha release on [PyPI](https://pypi.org/project/software-intake-gatekeeper/) and the
66
66
  > [GitHub Marketplace](https://github.com/marketplace/actions/software-intake-gatekeeper). The CLI is
67
67
  > tested on Windows and Linux with real Go binaries in three object formats, an ASAR archive packed
@@ -94,9 +94,9 @@ python -m pip install software-intake-gatekeeper
94
94
  To verify build provenance first, install the wheel from the GitHub release instead:
95
95
 
96
96
  ```powershell
97
- gh release download v0.2.1 -R Sushant-Nemade/software-intake-gatekeeper -p "*.whl"
98
- gh attestation verify software_intake_gatekeeper-0.2.1-py3-none-any.whl -R Sushant-Nemade/software-intake-gatekeeper
99
- python -m pip install software_intake_gatekeeper-0.2.1-py3-none-any.whl
97
+ gh release download v0.2.2 -R Sushant-Nemade/software-intake-gatekeeper -p "*.whl"
98
+ gh attestation verify software_intake_gatekeeper-0.2.2-py3-none-any.whl -R Sushant-Nemade/software-intake-gatekeeper
99
+ python -m pip install software_intake_gatekeeper-0.2.2-py3-none-any.whl
100
100
  ```
101
101
 
102
102
  Or install from a clone (needed for the demo below):
@@ -187,10 +187,10 @@ Components changed: 2 | endpoints +1/-0 | binaries +1/-0/~0
187
187
 
188
188
  | The update check reports | How it is matched |
189
189
  |---|---|
190
- | New and resolved findings | Advisory id plus package name (so a version bump that keeps the bug counts as persisting), or rule plus file |
190
+ | New and resolved findings | Advisory id plus package name (so a version bump that keeps the bug counts as persisting), or rule plus file. Content hashes in bundle names (`index-Cz8Eo_n9.js`, `main.3f2a1b9c.js`) are ignored, so a rebuilt bundle does not look new |
191
191
  | Component changes | Added, removed or changed versions per ecosystem and name |
192
192
  | Electron | Runtime version change and every fuse change; a fuse moving into its risky state is **weakened** |
193
- | Network endpoints | Hosts in `http(s)://` and `ws(s)://` URLs in app code (outside `node_modules`), without XML namespaces or local addresses |
193
+ | Network endpoints | Hosts of `http(s)://` and `ws(s)://` URLs that start a string literal in app code (outside `node_modules`); URLs in comments and license headers, XML namespaces and local addresses are skipped |
194
194
  | Executables and native modules | Added, removed or changed (SHA-256) PE, ELF and Mach-O files, including `.node` modules |
195
195
 
196
196
  **REJECT** if a new finding reaches `reject_at`. **REVIEW** for new findings at `review_at`, weakened
@@ -359,6 +359,7 @@ and a `SHA256SUMS` file.
359
359
  | Output formats | CycloneDX validated with `cyclonedx-python-lib` against the 1.6 schema in tests. SARIF validated against the OASIS 2.1.0 schema during development |
360
360
  | OSV client | Mocked transport tests (batching, pagination, retries, alias grouping, withdrawn advisories) plus an opt-in live test (`INTAKE_LIVE_OSV=1`) |
361
361
  | Scale | Read-only scan of a locally installed VS Code: 3,243 files and 5,451 archive entries in about 19 s with a warm file cache |
362
+ | Real update | Update check of the open-source editor MarkText 0.21.1 to 0.21.2 (official Windows zips, checksums verified; 20,072 ASAR entries, 505 npm packages, live OSV) in about 30 s. It showed 27 resolved advisories, 49 new packages and changed native modules. The first run also reported 10 new and 10 resolved code findings that were only a renamed bundle (`index-BBEVtY79.js` to `index-Cz8Eo_n9.js`), and 44 new endpoints taken from code comments; v0.2.2 fixed both, leaving 0 new findings and 1 new endpoint |
362
363
  | CI | Ubuntu (Python 3.11 and 3.13) and Windows (3.13): pytest, ruff, mypy `--strict`, build, twine, pip-audit. The action itself runs on Ubuntu, Windows and macOS against the demo with live OSV, plus fail-on, invalid-input and update-check (`compare-to`) runs |
363
364
 
364
365
  No third-party application files are committed. The local real-app checks are listed for
@@ -23,7 +23,7 @@ Every finding names the file and, for scripts, the line, column and snippet that
23
23
  Anything the scanner could not verify is reported as a **coverage gap**. A blocking gap can
24
24
  never lead to APPROVE: an unknown result is not treated as safe.
25
25
 
26
- > Status: [v0.2.1](https://github.com/Sushant-Nemade/software-intake-gatekeeper/releases/tag/v0.2.1),
26
+ > Status: [v0.2.2](https://github.com/Sushant-Nemade/software-intake-gatekeeper/releases/tag/v0.2.2),
27
27
  > an alpha release on [PyPI](https://pypi.org/project/software-intake-gatekeeper/) and the
28
28
  > [GitHub Marketplace](https://github.com/marketplace/actions/software-intake-gatekeeper). The CLI is
29
29
  > tested on Windows and Linux with real Go binaries in three object formats, an ASAR archive packed
@@ -56,9 +56,9 @@ python -m pip install software-intake-gatekeeper
56
56
  To verify build provenance first, install the wheel from the GitHub release instead:
57
57
 
58
58
  ```powershell
59
- gh release download v0.2.1 -R Sushant-Nemade/software-intake-gatekeeper -p "*.whl"
60
- gh attestation verify software_intake_gatekeeper-0.2.1-py3-none-any.whl -R Sushant-Nemade/software-intake-gatekeeper
61
- python -m pip install software_intake_gatekeeper-0.2.1-py3-none-any.whl
59
+ gh release download v0.2.2 -R Sushant-Nemade/software-intake-gatekeeper -p "*.whl"
60
+ gh attestation verify software_intake_gatekeeper-0.2.2-py3-none-any.whl -R Sushant-Nemade/software-intake-gatekeeper
61
+ python -m pip install software_intake_gatekeeper-0.2.2-py3-none-any.whl
62
62
  ```
63
63
 
64
64
  Or install from a clone (needed for the demo below):
@@ -149,10 +149,10 @@ Components changed: 2 | endpoints +1/-0 | binaries +1/-0/~0
149
149
 
150
150
  | The update check reports | How it is matched |
151
151
  |---|---|
152
- | New and resolved findings | Advisory id plus package name (so a version bump that keeps the bug counts as persisting), or rule plus file |
152
+ | New and resolved findings | Advisory id plus package name (so a version bump that keeps the bug counts as persisting), or rule plus file. Content hashes in bundle names (`index-Cz8Eo_n9.js`, `main.3f2a1b9c.js`) are ignored, so a rebuilt bundle does not look new |
153
153
  | Component changes | Added, removed or changed versions per ecosystem and name |
154
154
  | Electron | Runtime version change and every fuse change; a fuse moving into its risky state is **weakened** |
155
- | Network endpoints | Hosts in `http(s)://` and `ws(s)://` URLs in app code (outside `node_modules`), without XML namespaces or local addresses |
155
+ | Network endpoints | Hosts of `http(s)://` and `ws(s)://` URLs that start a string literal in app code (outside `node_modules`); URLs in comments and license headers, XML namespaces and local addresses are skipped |
156
156
  | Executables and native modules | Added, removed or changed (SHA-256) PE, ELF and Mach-O files, including `.node` modules |
157
157
 
158
158
  **REJECT** if a new finding reaches `reject_at`. **REVIEW** for new findings at `review_at`, weakened
@@ -321,6 +321,7 @@ and a `SHA256SUMS` file.
321
321
  | Output formats | CycloneDX validated with `cyclonedx-python-lib` against the 1.6 schema in tests. SARIF validated against the OASIS 2.1.0 schema during development |
322
322
  | OSV client | Mocked transport tests (batching, pagination, retries, alias grouping, withdrawn advisories) plus an opt-in live test (`INTAKE_LIVE_OSV=1`) |
323
323
  | Scale | Read-only scan of a locally installed VS Code: 3,243 files and 5,451 archive entries in about 19 s with a warm file cache |
324
+ | Real update | Update check of the open-source editor MarkText 0.21.1 to 0.21.2 (official Windows zips, checksums verified; 20,072 ASAR entries, 505 npm packages, live OSV) in about 30 s. It showed 27 resolved advisories, 49 new packages and changed native modules. The first run also reported 10 new and 10 resolved code findings that were only a renamed bundle (`index-BBEVtY79.js` to `index-Cz8Eo_n9.js`), and 44 new endpoints taken from code comments; v0.2.2 fixed both, leaving 0 new findings and 1 new endpoint |
324
325
  | CI | Ubuntu (Python 3.11 and 3.13) and Windows (3.13): pytest, ruff, mypy `--strict`, build, twine, pip-audit. The action itself runs on Ubuntu, Windows and macOS against the demo with live OSV, plus fail-on, invalid-input and update-check (`compare-to`) runs |
325
326
 
326
327
  No third-party application files are committed. The local real-app checks are listed for
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "software-intake-gatekeeper"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "Local, evidence-first intake checks for third-party apps and binaries: SBOM, known vulnerabilities and Electron hardening."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """Software Intake Gatekeeper: evidence-first intake checks for third-party software."""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.2.2"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
5
6
  from dataclasses import dataclass, field
6
7
 
7
8
  from intake_gatekeeper.electron import FUSE_RULES
@@ -53,10 +54,19 @@ class UpdateDiff:
53
54
  reasons: list[str] = field(default_factory=list)
54
55
 
55
56
 
57
+ # Content hashes that bundlers put in file names (index-Cz8Eo_n9.js, main.3f2a1b9c.js) change every build.
58
+ _CONTENT_HASH = re.compile(r"([.-])(?=[A-Za-z_-]*\d)[A-Za-z0-9_-]{8,}(?=\.(?:m?js|cjs|css)$)")
59
+
60
+
61
+ def stable_path(path: str) -> str:
62
+ head, slash, name = path.rpartition("/")
63
+ return head + slash + _CONTENT_HASH.sub(r"\1*", name)
64
+
65
+
56
66
  def _finding_key(finding: Finding) -> tuple[str, str]:
57
67
  if finding.component_purl:
58
68
  return finding.rule_id, finding.component_purl.rsplit("@", 1)[0]
59
- return finding.rule_id, finding.location.path if finding.location else ""
69
+ return finding.rule_id, stable_path(finding.location.path) if finding.location else ""
60
70
 
61
71
 
62
72
  def _versions(report: Report) -> dict[tuple[str, str], set[str]]:
@@ -130,7 +130,10 @@ def find_electron_versions(data: Bytes) -> list[str]:
130
130
  return sorted(versions)
131
131
 
132
132
 
133
- _URL_HOST = re.compile(r"(?:https?|wss?)://([A-Za-z0-9-]{1,63}(?:\.[A-Za-z0-9-]{1,63})+)(?![A-Za-z0-9-])")
133
+ # Only URLs that start a string literal: code calls those, while comments and license headers do not.
134
+ _URL_HOST = re.compile(
135
+ r"[\"'`](?:https?|wss?)://([A-Za-z0-9-]{1,63}(?:\.[A-Za-z0-9-]{1,63})+)(?![A-Za-z0-9-])"
136
+ )
134
137
  # Namespace and spec URLs that appear in almost every bundle but are not network endpoints.
135
138
  _NOT_ENDPOINTS = {
136
139
  "www.w3.org",
@@ -159,3 +159,49 @@ def test_endpoints_ignore_namespaces_and_local_hosts() -> None:
159
159
  'c="http://127.0.0.1/" d="wss://socket.vendor.example" e="https://cdn.vendor.example."'
160
160
  )
161
161
  assert find_endpoints(text) == {"api.vendor.example", "socket.vendor.example", "cdn.vendor.example"}
162
+
163
+
164
+ def test_endpoints_skip_urls_in_comments_and_prose() -> None:
165
+ from intake_gatekeeper.electron import find_endpoints
166
+
167
+ text = (
168
+ "/*! lib v1 | (c) https://license-host.example */\n"
169
+ "// see https://docs.example/issue\n"
170
+ 'throw new Error("Read https://help.example/x");\n'
171
+ "fetch('https://api.vendor.example/v1'); new WebSocket(`wss://live.vendor.example/s`);\n"
172
+ )
173
+ assert find_endpoints(text) == {"api.vendor.example", "live.vendor.example"}
174
+
175
+
176
+ @pytest.mark.parametrize(
177
+ ("path", "expected"),
178
+ [
179
+ (
180
+ "resources/app.asar!/out/renderer/assets/index-Cz8Eo_n9.js",
181
+ "resources/app.asar!/out/renderer/assets/index-*.js",
182
+ ),
183
+ ("dist/main.3f2a1b9c.js", "dist/main.*.js"),
184
+ ("dist/vendors-chunk.0a1b2c3d4e5f.css", "dist/vendors-chunk.*.css"),
185
+ ("resources/app/main.js", "resources/app/main.js"),
186
+ ("out/index-renderer.js", "out/index-renderer.js"),
187
+ ("out/polyfills-es2015.js", "out/polyfills-es2015.js"),
188
+ ("index-Cz8Eo_n9.js", "index-*.js"),
189
+ ],
190
+ )
191
+ def test_stable_path_ignores_content_hashes(path: str, expected: str) -> None:
192
+ from intake_gatekeeper.diff import stable_path
193
+
194
+ assert stable_path(path) == expected
195
+
196
+
197
+ def test_renamed_bundle_keeps_findings_persisting(tmp_path: Path) -> None:
198
+ code = "new BrowserWindow({ webPreferences: { nodeIntegration: true } });\n"
199
+ old, new = tmp_path / "old" / "assets", tmp_path / "new" / "assets"
200
+ old.mkdir(parents=True)
201
+ new.mkdir(parents=True)
202
+ (old / "index-BBEVtY79.js").write_text(code, encoding="utf-8")
203
+ (new / "index-Cz8Eo_n9.js").write_text(code, encoding="utf-8")
204
+ diff = compare(_scan(old.parent), _scan(new.parent), OFFLINE)
205
+ assert diff.new_findings == [] and diff.resolved_findings == []
206
+ assert diff.persisting_findings == 1
207
+ assert diff.decision is Decision.APPROVE