software-intake-gatekeeper 0.2.2__tar.gz → 0.3.0__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.
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/.gitignore +1 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/CHANGELOG.md +23 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/PKG-INFO +51 -27
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/README.md +47 -24
- software_intake_gatekeeper-0.3.0/examples/rust-demo/Cargo.lock +16 -0
- software_intake_gatekeeper-0.3.0/examples/rust-demo/Cargo.toml +10 -0
- software_intake_gatekeeper-0.3.0/examples/rust-demo/src/main.rs +7 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/pyproject.toml +4 -3
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/__init__.py +1 -1
- software_intake_gatekeeper-0.3.0/src/intake_gatekeeper/authenticode.py +149 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/diff.py +34 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/engine.py +7 -1
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/model.py +13 -0
- software_intake_gatekeeper-0.3.0/src/intake_gatekeeper/objfile.py +170 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/output.py +49 -1
- software_intake_gatekeeper-0.3.0/src/intake_gatekeeper/packages.py +139 -0
- software_intake_gatekeeper-0.3.0/src/intake_gatekeeper/rust.py +92 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/scanner.py +102 -2
- software_intake_gatekeeper-0.3.0/tests/binfmt.py +98 -0
- software_intake_gatekeeper-0.3.0/tests/test_ecosystems.py +303 -0
- software_intake_gatekeeper-0.3.0/tests/test_real_artifacts.py +83 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/LICENSE +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/SECURITY.md +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/action-requirements.txt +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/action.yml +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/demo-app-src/dist/settings.min.js +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/demo-app-src/main.js +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/demo-app-src/node_modules/@demo/ui-kit/package.json +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/demo-app-src/node_modules/lodash/index.js +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/demo-app-src/node_modules/lodash/package.json +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/demo-app-src/node_modules/minimist/package.json +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/demo-app-src/package.json +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/demo-app-src/preload.js +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/go-demo/go.mod +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/go-demo/go.sum +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/go-demo/main.go +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/intake-policy.toml +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/update-demo/v1/resources/app/main.js +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/update-demo/v1/resources/app/node_modules/lodash/package.json +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/update-demo/v1/resources/app/package.json +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/update-demo/v2/resources/app/main.js +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/update-demo/v2/resources/app/node_modules/axios/package.json +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/update-demo/v2/resources/app/node_modules/fast-native/build/Release/addon.node +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/update-demo/v2/resources/app/node_modules/lodash/package.json +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/update-demo/v2/resources/app/package.json +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/vulnerable-demo/dotnet/IntakeDemo.deps.json +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/vulnerable-demo/dotnet/IntakeDemo.runtimeconfig.json +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/examples/vulnerable-demo/resources/app.asar +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/__main__.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/asar.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/cli.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/electron.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/golang.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/osv.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/policy.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/py.typed +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/src/intake_gatekeeper/rules.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/tests/conftest.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/tests/test_asar.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/tests/test_cli.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/tests/test_diff.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/tests/test_electron.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/tests/test_golang.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/tests/test_live_osv.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/tests/test_osv.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/tests/test_output.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/tests/test_policy.py +0 -0
- {software_intake_gatekeeper-0.2.2 → software_intake_gatekeeper-0.3.0}/tests/test_scanner.py +0 -0
|
@@ -3,6 +3,28 @@
|
|
|
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.3.0] - 2026-10-18
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Rust inventory from `cargo auditable` data (`.dep-v0` section) in ELF, PE and Mach-O, including
|
|
11
|
+
universal binaries, through a new bounded, dependency-free object-file reader. Build-only crates,
|
|
12
|
+
the root package and non-crates.io sources are listed but not sent to OSV.
|
|
13
|
+
- Python inventory from `*.dist-info/METADATA` and `*.egg-info` metadata (`pkg:pypi`, PEP 503 names).
|
|
14
|
+
- Maven inventory from `pom.properties` inside `.jar`, `.war` and `.ear` files, including nested
|
|
15
|
+
archives up to two levels (Spring Boot, WAR) read in memory with size and entry limits.
|
|
16
|
+
- Authenticode signer (subject, issuer, SHA-1 thumbprint) for PE files, without verification.
|
|
17
|
+
- Update check: removed signatures and a different signer on the same path need REVIEW; renewed
|
|
18
|
+
certificates from the same signer and newly signed files are listed only.
|
|
19
|
+
- Real-artifact CI job on Linux, Windows and macOS: `cargo auditable` builds compared with
|
|
20
|
+
`rust-audit-info`, Maven Central and PyPI artifacts with live OSV, and Windows'
|
|
21
|
+
`Get-AuthenticodeSignature`.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- The `nothing-recognized` gap now says how many binaries were fingerprinted without dependency data.
|
|
26
|
+
- Files that start with `MZ` but have no PE header are treated as non-PE instead of malformed.
|
|
27
|
+
|
|
6
28
|
## [0.2.2] - 2026-10-15
|
|
7
29
|
|
|
8
30
|
### Fixed
|
|
@@ -63,6 +85,7 @@ First public release and first GitHub Marketplace listing.
|
|
|
63
85
|
- No Rust `cargo auditable`, Python wheel, Java or native C/C++ inventories yet.
|
|
64
86
|
- Electron code rules detect literal settings only.
|
|
65
87
|
|
|
88
|
+
[0.3.0]: https://github.com/Sushant-Nemade/software-intake-gatekeeper/releases/tag/v0.3.0
|
|
66
89
|
[0.2.2]: https://github.com/Sushant-Nemade/software-intake-gatekeeper/releases/tag/v0.2.2
|
|
67
90
|
[0.2.1]: https://github.com/Sushant-Nemade/software-intake-gatekeeper/releases/tag/v0.2.1
|
|
68
91
|
[0.2.0]: https://github.com/Sushant-Nemade/software-intake-gatekeeper/releases/tag/v0.2.0
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: software-intake-gatekeeper
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Local, evidence-first intake checks for third-party apps and binaries: SBOM, known vulnerabilities and
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Local, evidence-first intake checks for third-party apps and binaries: SBOM (Go, Rust, npm, NuGet, Python, Maven), known vulnerabilities, Electron hardening and code-signing changes.
|
|
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
|
|
7
7
|
Project-URL: Changelog, https://github.com/Sushant-Nemade/software-intake-gatekeeper/blob/main/CHANGELOG.md
|
|
@@ -10,7 +10,7 @@ Project-URL: GitHub Marketplace, https://github.com/marketplace/actions/software
|
|
|
10
10
|
Author: Sushant Nemade
|
|
11
11
|
License-Expression: MIT
|
|
12
12
|
License-File: LICENSE
|
|
13
|
-
Keywords: cyclonedx,electron,osv,sarif,sbom,security,supply-chain
|
|
13
|
+
Keywords: authenticode,cargo-auditable,cyclonedx,electron,maven,osv,sarif,sbom,security,supply-chain
|
|
14
14
|
Classifier: Development Status :: 3 - Alpha
|
|
15
15
|
Classifier: Environment :: Console
|
|
16
16
|
Classifier: Intended Audience :: Developers
|
|
@@ -28,6 +28,7 @@ Requires-Dist: httpx<1,>=0.28
|
|
|
28
28
|
Requires-Dist: truststore>=0.10
|
|
29
29
|
Provides-Extra: dev
|
|
30
30
|
Requires-Dist: build>=1.2; extra == 'dev'
|
|
31
|
+
Requires-Dist: cryptography>=44; extra == 'dev'
|
|
31
32
|
Requires-Dist: cyclonedx-python-lib[json-validation]>=9; extra == 'dev'
|
|
32
33
|
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
33
34
|
Requires-Dist: pip-audit>=2.7; extra == 'dev'
|
|
@@ -45,28 +46,33 @@ Description-Content-Type: text/markdown
|
|
|
45
46
|
|
|
46
47
|
**Before you install, approve or ship a third-party app, find out what is inside it.**
|
|
47
48
|
|
|
48
|
-
Software Intake Gatekeeper reads an installed application, an Electron `.asar` archive
|
|
49
|
-
compiled binary. It never runs or unpacks it. It produces:
|
|
49
|
+
Software Intake Gatekeeper reads an installed application, an Electron `.asar` archive, a Java
|
|
50
|
+
archive or a compiled binary. It never runs or unpacks it. It produces:
|
|
50
51
|
|
|
51
|
-
- a **CycloneDX 1.6 SBOM** of the components that are actually bundled,
|
|
52
|
+
- a **CycloneDX 1.6 SBOM** of the components that are actually bundled: Go, Rust, npm, NuGet,
|
|
53
|
+
Python and Maven packages, read from evidence inside the files,
|
|
52
54
|
- **known vulnerabilities** for those exact versions from [OSV.dev](https://osv.dev) (only when you opt in),
|
|
53
55
|
- **Electron hardening checks** for the fuse wire inside the executable and risky `BrowserWindow`
|
|
54
56
|
settings in the app code,
|
|
57
|
+
- the **Authenticode signer** of each Windows executable,
|
|
55
58
|
- an **APPROVE / REVIEW / REJECT** decision from a versioned policy, with expiring waivers,
|
|
56
59
|
- an **update check** that compares two versions and gates on what the update *adds*: new
|
|
57
|
-
vulnerabilities, weakened Electron fuses, new network endpoints, new native modules,
|
|
60
|
+
vulnerabilities, weakened Electron fuses, new network endpoints, new native modules, removed
|
|
61
|
+
signatures and a different publisher,
|
|
58
62
|
- **SARIF** for GitHub code scanning, a **Markdown** summary for pull requests and a full **JSON** evidence report.
|
|
59
63
|
|
|
60
64
|
Every finding names the file and, for scripts, the line, column and snippet that produced it.
|
|
61
65
|
Anything the scanner could not verify is reported as a **coverage gap**. A blocking gap can
|
|
62
66
|
never lead to APPROVE: an unknown result is not treated as safe.
|
|
63
67
|
|
|
64
|
-
> Status: [v0.
|
|
68
|
+
> Status: [v0.3.0](https://github.com/Sushant-Nemade/software-intake-gatekeeper/releases/tag/v0.3.0),
|
|
65
69
|
> an alpha release on [PyPI](https://pypi.org/project/software-intake-gatekeeper/) and the
|
|
66
|
-
> [GitHub Marketplace](https://github.com/marketplace/actions/software-intake-gatekeeper).
|
|
67
|
-
>
|
|
68
|
-
>
|
|
69
|
-
>
|
|
70
|
+
> [GitHub Marketplace](https://github.com/marketplace/actions/software-intake-gatekeeper). Every
|
|
71
|
+
> parser is checked against real artifacts: Go binaries in three object formats, `cargo auditable`
|
|
72
|
+
> builds compared with the official `rust-audit-info` decoder on Linux, Windows and macOS, JARs from
|
|
73
|
+
> Maven Central, packages installed by pip, signed executables compared with Windows'
|
|
74
|
+
> `Get-AuthenticodeSignature`, and the live OSV API. The results are static, version-level
|
|
75
|
+
> evidence, not proof of exploitability.
|
|
70
76
|
|
|
71
77
|
Part of a small **software trust toolkit** with
|
|
72
78
|
[Privacy Flow Inspector](https://github.com/Sushant-Nemade/privacy-flow-inspector): this tool checks
|
|
@@ -79,6 +85,8 @@ what is **inside** an app; the inspector checks which personal data the app **se
|
|
|
79
85
|
| Who | Situation | What they get |
|
|
80
86
|
|---|---|---|
|
|
81
87
|
| IT / security intake | An employee requests a desktop tool (an Electron or Go CLI) | An SBOM, known CVEs and Electron hardening issues before approval, with a decision a reviewer can sign off |
|
|
88
|
+
| Platform and backend teams | A vendor ships a Spring Boot service, a Python tool or a Rust CLI | Maven coordinates from nested JARs (Log4Shell is found inside `BOOT-INF/lib`), Python package versions and Rust crates, each with advisories |
|
|
89
|
+
| Patch management | A new version of an approved app arrives | An update check that flags a removed signature or a different publisher before the rollout |
|
|
82
90
|
| Product teams shipping Electron | A release candidate is ready | A CI gate that fails when `contextIsolation` is off, `RunAsNode` is enabled or bundled packages have critical advisories |
|
|
83
91
|
| Supply-chain / compliance (EU CRA, medical-device SBOM expectations) | A vendor delivers a binary without an SBOM | A CycloneDX SBOM built from evidence inside the binary, with occurrences pointing to the source files |
|
|
84
92
|
| Procurement / vendor review | Comparing two vendor builds | Reproducible JSON and SBOM output that can be compared across versions |
|
|
@@ -94,9 +102,9 @@ python -m pip install software-intake-gatekeeper
|
|
|
94
102
|
To verify build provenance first, install the wheel from the GitHub release instead:
|
|
95
103
|
|
|
96
104
|
```powershell
|
|
97
|
-
gh release download v0.
|
|
98
|
-
gh attestation verify software_intake_gatekeeper-0.
|
|
99
|
-
python -m pip install software_intake_gatekeeper-0.
|
|
105
|
+
gh release download v0.3.0 -R Sushant-Nemade/software-intake-gatekeeper -p "*.whl"
|
|
106
|
+
gh attestation verify software_intake_gatekeeper-0.3.0-py3-none-any.whl -R Sushant-Nemade/software-intake-gatekeeper
|
|
107
|
+
python -m pip install software_intake_gatekeeper-0.3.0-py3-none-any.whl
|
|
100
108
|
```
|
|
101
109
|
|
|
102
110
|
Or install from a clone (needed for the demo below):
|
|
@@ -192,23 +200,29 @@ Components changed: 2 | endpoints +1/-0 | binaries +1/-0/~0
|
|
|
192
200
|
| Electron | Runtime version change and every fuse change; a fuse moving into its risky state is **weakened** |
|
|
193
201
|
| 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
202
|
| Executables and native modules | Added, removed or changed (SHA-256) PE, ELF and Mach-O files, including `.node` modules |
|
|
203
|
+
| Code signing | For the same file path: signature **removed**, **signer-changed** (different subject), `certificate-renewed` (same subject, new certificate) or `added` |
|
|
195
204
|
|
|
196
205
|
**REJECT** if a new finding reaches `reject_at`. **REVIEW** for new findings at `review_at`, weakened
|
|
197
|
-
fuses, new endpoints, new binaries, new components
|
|
198
|
-
**APPROVE** otherwise. The SARIF output contains only the new
|
|
206
|
+
fuses, new endpoints, new binaries, new components, removed signatures, a different signer or
|
|
207
|
+
blocking gaps in the new version. **APPROVE** otherwise. The SARIF output contains only the new
|
|
208
|
+
findings.
|
|
199
209
|
|
|
200
210
|
## What it inspects
|
|
201
211
|
|
|
202
212
|
| Evidence | Source inside the target | Output |
|
|
203
213
|
|---|---|---|
|
|
204
214
|
| Go modules and toolchain | Build info embedded by the Go linker (Go 1.18+) in PE, ELF and Mach-O executables | `pkg:golang/...` components, including `stdlib` and `replace` directives |
|
|
215
|
+
| Rust crates | The zlib-compressed `.dep-v0` section that [`cargo auditable`](https://github.com/rust-secure-code/cargo-auditable) embeds in ELF, PE and Mach-O (including universal) binaries | `pkg:cargo/...` components; build-only crates and non-crates.io sources are listed but not queried |
|
|
216
|
+
| Python packages | `*.dist-info/METADATA` and `*.egg-info` (`PKG-INFO`) on disk, for example a bundled `site-packages` | `pkg:pypi/...` components with PEP 503 names |
|
|
217
|
+
| Maven artifacts | `META-INF/maven/**/pom.properties` in `.jar`, `.war` and `.ear` files, including nested archives such as Spring Boot `BOOT-INF/lib` and `WEB-INF/lib` (two levels, read in memory) | `pkg:maven/...` components, also for shaded dependencies |
|
|
205
218
|
| npm packages | `node_modules/**/package.json` on disk and inside `.asar` archives | `pkg:npm/...` components; the app's own manifest is recorded but not queried |
|
|
206
219
|
| NuGet packages | `*.deps.json` (`package` and self-contained `runtimepack` entries) | `pkg:nuget/...` components |
|
|
207
220
|
| .NET shared framework | `*.runtimeconfig.json` | Minimum framework version, plus a note that the host runtime decides the patch level |
|
|
208
221
|
| Electron runtime | The fuse sentinel and `Electron/x.y.z` string in the executable | Electron version (queried as npm `electron`) and the fuse states |
|
|
209
222
|
| Electron app code | `.js/.cjs/.mjs/.ts` files outside `node_modules`, on disk or in `.asar` | `webPreferences` rule findings with line, column and snippet |
|
|
210
223
|
| Network endpoints | `http(s)://` and `ws(s)://` hosts referenced by that app code | Host list with up to three source files each (used by the update check) |
|
|
211
|
-
| Executables and native modules | PE, ELF and Mach-O files, including `.node` add-ons | Path, size, SHA-256 and kind (Go, Electron, native) |
|
|
224
|
+
| Executables and native modules | PE, ELF and Mach-O files, including `.node` add-ons | Path, size, SHA-256 and kind (Go, Rust, Electron, native) |
|
|
225
|
+
| Code signing | The Authenticode certificate table of PE files | Claimed signer subject, issuer and SHA-1 thumbprint (the value Windows shows); not cryptographically verified |
|
|
212
226
|
|
|
213
227
|
### Electron rules
|
|
214
228
|
|
|
@@ -354,23 +368,32 @@ and a `SHA256SUMS` file.
|
|
|
354
368
|
| Check | How |
|
|
355
369
|
|---|---|
|
|
356
370
|
| Go build info decoding | Compared with `go version -m` on real binaries built in CI for Windows PE, Linux ELF and macOS Mach-O |
|
|
371
|
+
| Rust `cargo auditable` | CI builds a fixture with `cargo auditable` 0.7.7 on Linux, Windows and macOS and requires the same crates as the official `rust-audit-info` 0.5.4 decoder, then finds RUSTSEC-2021-0003 through live OSV. Locally, Alpine Linux's official `ripgrep` 14.1.1 binary decoded to 42 crates, every one matching ripgrep's `Cargo.lock` |
|
|
372
|
+
| Maven and Python | CI downloads `log4j-core` 2.14.1 from Maven Central (SHA-1 checked), nests it in a Spring Boot style JAR and requires Log4Shell (CVE-2021-44228); pip installs `requests` 2.25.0 and CVE-2023-32681 must be found. Locally, `jackson-databind` 2.9.10 nested in a fat JAR returned 49 advisories |
|
|
373
|
+
| Authenticode | CI compares signer and thumbprint with Windows' `Get-AuthenticodeSignature` for a signed executable. Locally identical for VS Code, Node.js, Git and uv; catalog-signed and unsigned files report no embedded signature |
|
|
357
374
|
| ASAR parsing | Fixture packed by `@electron/asar` 4.3.1; listing matches the official tool |
|
|
358
375
|
| Fuse decoding | Wire layout taken from `@electron/fuses`. A read-only local check on an installed Electron app (VS Code) matched `npx @electron/fuses read` for all 9 fuses |
|
|
359
376
|
| 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
377
|
| OSV client | Mocked transport tests (batching, pagination, retries, alias grouping, withdrawn advisories) plus an opt-in live test (`INTAKE_LIVE_OSV=1`) |
|
|
361
378
|
| 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
379
|
| 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 |
|
|
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 |
|
|
380
|
+
| CI | Ubuntu (Python 3.11 and 3.13) and Windows (3.13): pytest, ruff, mypy `--strict`, build, twine, pip-audit. A real-artifact job on Ubuntu, Windows and macOS. 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 |
|
|
364
381
|
|
|
365
|
-
No third-party application files are committed.
|
|
366
|
-
|
|
382
|
+
No third-party application files are committed. Unit tests build minimal ELF, PE and Mach-O files
|
|
383
|
+
and sign them with throwaway certificates; real artifacts are downloaded and checked in CI.
|
|
367
384
|
|
|
368
385
|
## Limitations
|
|
369
386
|
|
|
370
387
|
- Version matching is not reachability. A vulnerable function may never be called; use waivers
|
|
371
388
|
with reasons when you have checked this.
|
|
372
|
-
- It reads Go build info only for Go 1.18+ (older binaries are reported as gaps)
|
|
373
|
-
|
|
389
|
+
- It reads Go build info only for Go 1.18+ (older binaries are reported as gaps) and Rust crates
|
|
390
|
+
only from binaries built with `cargo auditable`. Native C/C++ libraries are fingerprinted but not
|
|
391
|
+
identified.
|
|
392
|
+
- Python packages are read from installed metadata folders, not from wheels, eggs or
|
|
393
|
+
PyInstaller/zipapp bundles. JARs without `pom.properties` are counted as a coverage note.
|
|
394
|
+
- The Authenticode check reports who a signature *claims* to be from. It does not verify the
|
|
395
|
+
signature, the certificate chain or revocation; use `Get-AuthenticodeSignature` or `signtool
|
|
396
|
+
verify` for that. Catalog signatures and macOS code signatures are not read.
|
|
374
397
|
- Electron code rules are pattern-based. They catch literal settings, including minified ones,
|
|
375
398
|
but not values computed at runtime.
|
|
376
399
|
- Installers (`.exe`/`.msi`/`.dmg`) must be extracted or installed first. The tool does not unpack
|
|
@@ -380,12 +403,13 @@ transparency only.
|
|
|
380
403
|
|
|
381
404
|
## Roadmap
|
|
382
405
|
|
|
383
|
-
1.
|
|
384
|
-
2.
|
|
406
|
+
1. macOS code signatures (Team ID) in the update check.
|
|
407
|
+
2. Python wheels and PyInstaller bundles; Gradle and `MANIFEST.MF`-only JARs.
|
|
385
408
|
3. An optional adapter for reverse-engineering evidence tools behind an approval gate.
|
|
386
409
|
|
|
387
|
-
Shipped from earlier roadmap items:
|
|
388
|
-
PII-in-HAR inspector,
|
|
410
|
+
Shipped from earlier roadmap items: Rust, Python and Maven inventories with Authenticode signer
|
|
411
|
+
changes (v0.3.0), the version-to-version update check (v0.2.0) and the PII-in-HAR inspector,
|
|
412
|
+
released as the separate
|
|
389
413
|
[Privacy Flow Inspector](https://github.com/Sushant-Nemade/privacy-flow-inspector).
|
|
390
414
|
|
|
391
415
|
## Development
|
|
@@ -7,28 +7,33 @@
|
|
|
7
7
|
|
|
8
8
|
**Before you install, approve or ship a third-party app, find out what is inside it.**
|
|
9
9
|
|
|
10
|
-
Software Intake Gatekeeper reads an installed application, an Electron `.asar` archive
|
|
11
|
-
compiled binary. It never runs or unpacks it. It produces:
|
|
10
|
+
Software Intake Gatekeeper reads an installed application, an Electron `.asar` archive, a Java
|
|
11
|
+
archive or a compiled binary. It never runs or unpacks it. It produces:
|
|
12
12
|
|
|
13
|
-
- a **CycloneDX 1.6 SBOM** of the components that are actually bundled,
|
|
13
|
+
- a **CycloneDX 1.6 SBOM** of the components that are actually bundled: Go, Rust, npm, NuGet,
|
|
14
|
+
Python and Maven packages, read from evidence inside the files,
|
|
14
15
|
- **known vulnerabilities** for those exact versions from [OSV.dev](https://osv.dev) (only when you opt in),
|
|
15
16
|
- **Electron hardening checks** for the fuse wire inside the executable and risky `BrowserWindow`
|
|
16
17
|
settings in the app code,
|
|
18
|
+
- the **Authenticode signer** of each Windows executable,
|
|
17
19
|
- an **APPROVE / REVIEW / REJECT** decision from a versioned policy, with expiring waivers,
|
|
18
20
|
- an **update check** that compares two versions and gates on what the update *adds*: new
|
|
19
|
-
vulnerabilities, weakened Electron fuses, new network endpoints, new native modules,
|
|
21
|
+
vulnerabilities, weakened Electron fuses, new network endpoints, new native modules, removed
|
|
22
|
+
signatures and a different publisher,
|
|
20
23
|
- **SARIF** for GitHub code scanning, a **Markdown** summary for pull requests and a full **JSON** evidence report.
|
|
21
24
|
|
|
22
25
|
Every finding names the file and, for scripts, the line, column and snippet that produced it.
|
|
23
26
|
Anything the scanner could not verify is reported as a **coverage gap**. A blocking gap can
|
|
24
27
|
never lead to APPROVE: an unknown result is not treated as safe.
|
|
25
28
|
|
|
26
|
-
> Status: [v0.
|
|
29
|
+
> Status: [v0.3.0](https://github.com/Sushant-Nemade/software-intake-gatekeeper/releases/tag/v0.3.0),
|
|
27
30
|
> an alpha release on [PyPI](https://pypi.org/project/software-intake-gatekeeper/) and the
|
|
28
|
-
> [GitHub Marketplace](https://github.com/marketplace/actions/software-intake-gatekeeper).
|
|
29
|
-
>
|
|
30
|
-
>
|
|
31
|
-
>
|
|
31
|
+
> [GitHub Marketplace](https://github.com/marketplace/actions/software-intake-gatekeeper). Every
|
|
32
|
+
> parser is checked against real artifacts: Go binaries in three object formats, `cargo auditable`
|
|
33
|
+
> builds compared with the official `rust-audit-info` decoder on Linux, Windows and macOS, JARs from
|
|
34
|
+
> Maven Central, packages installed by pip, signed executables compared with Windows'
|
|
35
|
+
> `Get-AuthenticodeSignature`, and the live OSV API. The results are static, version-level
|
|
36
|
+
> evidence, not proof of exploitability.
|
|
32
37
|
|
|
33
38
|
Part of a small **software trust toolkit** with
|
|
34
39
|
[Privacy Flow Inspector](https://github.com/Sushant-Nemade/privacy-flow-inspector): this tool checks
|
|
@@ -41,6 +46,8 @@ what is **inside** an app; the inspector checks which personal data the app **se
|
|
|
41
46
|
| Who | Situation | What they get |
|
|
42
47
|
|---|---|---|
|
|
43
48
|
| IT / security intake | An employee requests a desktop tool (an Electron or Go CLI) | An SBOM, known CVEs and Electron hardening issues before approval, with a decision a reviewer can sign off |
|
|
49
|
+
| Platform and backend teams | A vendor ships a Spring Boot service, a Python tool or a Rust CLI | Maven coordinates from nested JARs (Log4Shell is found inside `BOOT-INF/lib`), Python package versions and Rust crates, each with advisories |
|
|
50
|
+
| Patch management | A new version of an approved app arrives | An update check that flags a removed signature or a different publisher before the rollout |
|
|
44
51
|
| Product teams shipping Electron | A release candidate is ready | A CI gate that fails when `contextIsolation` is off, `RunAsNode` is enabled or bundled packages have critical advisories |
|
|
45
52
|
| Supply-chain / compliance (EU CRA, medical-device SBOM expectations) | A vendor delivers a binary without an SBOM | A CycloneDX SBOM built from evidence inside the binary, with occurrences pointing to the source files |
|
|
46
53
|
| Procurement / vendor review | Comparing two vendor builds | Reproducible JSON and SBOM output that can be compared across versions |
|
|
@@ -56,9 +63,9 @@ python -m pip install software-intake-gatekeeper
|
|
|
56
63
|
To verify build provenance first, install the wheel from the GitHub release instead:
|
|
57
64
|
|
|
58
65
|
```powershell
|
|
59
|
-
gh release download v0.
|
|
60
|
-
gh attestation verify software_intake_gatekeeper-0.
|
|
61
|
-
python -m pip install software_intake_gatekeeper-0.
|
|
66
|
+
gh release download v0.3.0 -R Sushant-Nemade/software-intake-gatekeeper -p "*.whl"
|
|
67
|
+
gh attestation verify software_intake_gatekeeper-0.3.0-py3-none-any.whl -R Sushant-Nemade/software-intake-gatekeeper
|
|
68
|
+
python -m pip install software_intake_gatekeeper-0.3.0-py3-none-any.whl
|
|
62
69
|
```
|
|
63
70
|
|
|
64
71
|
Or install from a clone (needed for the demo below):
|
|
@@ -154,23 +161,29 @@ Components changed: 2 | endpoints +1/-0 | binaries +1/-0/~0
|
|
|
154
161
|
| Electron | Runtime version change and every fuse change; a fuse moving into its risky state is **weakened** |
|
|
155
162
|
| 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
163
|
| Executables and native modules | Added, removed or changed (SHA-256) PE, ELF and Mach-O files, including `.node` modules |
|
|
164
|
+
| Code signing | For the same file path: signature **removed**, **signer-changed** (different subject), `certificate-renewed` (same subject, new certificate) or `added` |
|
|
157
165
|
|
|
158
166
|
**REJECT** if a new finding reaches `reject_at`. **REVIEW** for new findings at `review_at`, weakened
|
|
159
|
-
fuses, new endpoints, new binaries, new components
|
|
160
|
-
**APPROVE** otherwise. The SARIF output contains only the new
|
|
167
|
+
fuses, new endpoints, new binaries, new components, removed signatures, a different signer or
|
|
168
|
+
blocking gaps in the new version. **APPROVE** otherwise. The SARIF output contains only the new
|
|
169
|
+
findings.
|
|
161
170
|
|
|
162
171
|
## What it inspects
|
|
163
172
|
|
|
164
173
|
| Evidence | Source inside the target | Output |
|
|
165
174
|
|---|---|---|
|
|
166
175
|
| Go modules and toolchain | Build info embedded by the Go linker (Go 1.18+) in PE, ELF and Mach-O executables | `pkg:golang/...` components, including `stdlib` and `replace` directives |
|
|
176
|
+
| Rust crates | The zlib-compressed `.dep-v0` section that [`cargo auditable`](https://github.com/rust-secure-code/cargo-auditable) embeds in ELF, PE and Mach-O (including universal) binaries | `pkg:cargo/...` components; build-only crates and non-crates.io sources are listed but not queried |
|
|
177
|
+
| Python packages | `*.dist-info/METADATA` and `*.egg-info` (`PKG-INFO`) on disk, for example a bundled `site-packages` | `pkg:pypi/...` components with PEP 503 names |
|
|
178
|
+
| Maven artifacts | `META-INF/maven/**/pom.properties` in `.jar`, `.war` and `.ear` files, including nested archives such as Spring Boot `BOOT-INF/lib` and `WEB-INF/lib` (two levels, read in memory) | `pkg:maven/...` components, also for shaded dependencies |
|
|
167
179
|
| npm packages | `node_modules/**/package.json` on disk and inside `.asar` archives | `pkg:npm/...` components; the app's own manifest is recorded but not queried |
|
|
168
180
|
| NuGet packages | `*.deps.json` (`package` and self-contained `runtimepack` entries) | `pkg:nuget/...` components |
|
|
169
181
|
| .NET shared framework | `*.runtimeconfig.json` | Minimum framework version, plus a note that the host runtime decides the patch level |
|
|
170
182
|
| Electron runtime | The fuse sentinel and `Electron/x.y.z` string in the executable | Electron version (queried as npm `electron`) and the fuse states |
|
|
171
183
|
| Electron app code | `.js/.cjs/.mjs/.ts` files outside `node_modules`, on disk or in `.asar` | `webPreferences` rule findings with line, column and snippet |
|
|
172
184
|
| Network endpoints | `http(s)://` and `ws(s)://` hosts referenced by that app code | Host list with up to three source files each (used by the update check) |
|
|
173
|
-
| Executables and native modules | PE, ELF and Mach-O files, including `.node` add-ons | Path, size, SHA-256 and kind (Go, Electron, native) |
|
|
185
|
+
| Executables and native modules | PE, ELF and Mach-O files, including `.node` add-ons | Path, size, SHA-256 and kind (Go, Rust, Electron, native) |
|
|
186
|
+
| Code signing | The Authenticode certificate table of PE files | Claimed signer subject, issuer and SHA-1 thumbprint (the value Windows shows); not cryptographically verified |
|
|
174
187
|
|
|
175
188
|
### Electron rules
|
|
176
189
|
|
|
@@ -316,23 +329,32 @@ and a `SHA256SUMS` file.
|
|
|
316
329
|
| Check | How |
|
|
317
330
|
|---|---|
|
|
318
331
|
| Go build info decoding | Compared with `go version -m` on real binaries built in CI for Windows PE, Linux ELF and macOS Mach-O |
|
|
332
|
+
| Rust `cargo auditable` | CI builds a fixture with `cargo auditable` 0.7.7 on Linux, Windows and macOS and requires the same crates as the official `rust-audit-info` 0.5.4 decoder, then finds RUSTSEC-2021-0003 through live OSV. Locally, Alpine Linux's official `ripgrep` 14.1.1 binary decoded to 42 crates, every one matching ripgrep's `Cargo.lock` |
|
|
333
|
+
| Maven and Python | CI downloads `log4j-core` 2.14.1 from Maven Central (SHA-1 checked), nests it in a Spring Boot style JAR and requires Log4Shell (CVE-2021-44228); pip installs `requests` 2.25.0 and CVE-2023-32681 must be found. Locally, `jackson-databind` 2.9.10 nested in a fat JAR returned 49 advisories |
|
|
334
|
+
| Authenticode | CI compares signer and thumbprint with Windows' `Get-AuthenticodeSignature` for a signed executable. Locally identical for VS Code, Node.js, Git and uv; catalog-signed and unsigned files report no embedded signature |
|
|
319
335
|
| ASAR parsing | Fixture packed by `@electron/asar` 4.3.1; listing matches the official tool |
|
|
320
336
|
| Fuse decoding | Wire layout taken from `@electron/fuses`. A read-only local check on an installed Electron app (VS Code) matched `npx @electron/fuses read` for all 9 fuses |
|
|
321
337
|
| 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
338
|
| OSV client | Mocked transport tests (batching, pagination, retries, alias grouping, withdrawn advisories) plus an opt-in live test (`INTAKE_LIVE_OSV=1`) |
|
|
323
339
|
| 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
340
|
| 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 |
|
|
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 |
|
|
341
|
+
| CI | Ubuntu (Python 3.11 and 3.13) and Windows (3.13): pytest, ruff, mypy `--strict`, build, twine, pip-audit. A real-artifact job on Ubuntu, Windows and macOS. 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 |
|
|
326
342
|
|
|
327
|
-
No third-party application files are committed.
|
|
328
|
-
|
|
343
|
+
No third-party application files are committed. Unit tests build minimal ELF, PE and Mach-O files
|
|
344
|
+
and sign them with throwaway certificates; real artifacts are downloaded and checked in CI.
|
|
329
345
|
|
|
330
346
|
## Limitations
|
|
331
347
|
|
|
332
348
|
- Version matching is not reachability. A vulnerable function may never be called; use waivers
|
|
333
349
|
with reasons when you have checked this.
|
|
334
|
-
- It reads Go build info only for Go 1.18+ (older binaries are reported as gaps)
|
|
335
|
-
|
|
350
|
+
- It reads Go build info only for Go 1.18+ (older binaries are reported as gaps) and Rust crates
|
|
351
|
+
only from binaries built with `cargo auditable`. Native C/C++ libraries are fingerprinted but not
|
|
352
|
+
identified.
|
|
353
|
+
- Python packages are read from installed metadata folders, not from wheels, eggs or
|
|
354
|
+
PyInstaller/zipapp bundles. JARs without `pom.properties` are counted as a coverage note.
|
|
355
|
+
- The Authenticode check reports who a signature *claims* to be from. It does not verify the
|
|
356
|
+
signature, the certificate chain or revocation; use `Get-AuthenticodeSignature` or `signtool
|
|
357
|
+
verify` for that. Catalog signatures and macOS code signatures are not read.
|
|
336
358
|
- Electron code rules are pattern-based. They catch literal settings, including minified ones,
|
|
337
359
|
but not values computed at runtime.
|
|
338
360
|
- Installers (`.exe`/`.msi`/`.dmg`) must be extracted or installed first. The tool does not unpack
|
|
@@ -342,12 +364,13 @@ transparency only.
|
|
|
342
364
|
|
|
343
365
|
## Roadmap
|
|
344
366
|
|
|
345
|
-
1.
|
|
346
|
-
2.
|
|
367
|
+
1. macOS code signatures (Team ID) in the update check.
|
|
368
|
+
2. Python wheels and PyInstaller bundles; Gradle and `MANIFEST.MF`-only JARs.
|
|
347
369
|
3. An optional adapter for reverse-engineering evidence tools behind an approval gate.
|
|
348
370
|
|
|
349
|
-
Shipped from earlier roadmap items:
|
|
350
|
-
PII-in-HAR inspector,
|
|
371
|
+
Shipped from earlier roadmap items: Rust, Python and Maven inventories with Authenticode signer
|
|
372
|
+
changes (v0.3.0), the version-to-version update check (v0.2.0) and the PII-in-HAR inspector,
|
|
373
|
+
released as the separate
|
|
351
374
|
[Privacy Flow Inspector](https://github.com/Sushant-Nemade/privacy-flow-inspector).
|
|
352
375
|
|
|
353
376
|
## Development
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "rust-demo"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"smallvec",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[[package]]
|
|
13
|
+
name = "smallvec"
|
|
14
|
+
version = "1.6.0"
|
|
15
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
16
|
+
checksum = "1a55ca5f3b68e41c979bf8c46a6f1da892ca4db8f94023ce0bd32407573b1ac0"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "rust-demo"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
publish = false
|
|
6
|
+
description = "Fictional fixture for Software Intake Gatekeeper; smallvec 1.6.0 is pinned on purpose (RUSTSEC-2021-0003)."
|
|
7
|
+
license = "MIT"
|
|
8
|
+
|
|
9
|
+
[dependencies]
|
|
10
|
+
smallvec = "=1.6.0"
|
|
@@ -4,14 +4,14 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "software-intake-gatekeeper"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Local, evidence-first intake checks for third-party apps and binaries: SBOM, known vulnerabilities and
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "Local, evidence-first intake checks for third-party apps and binaries: SBOM (Go, Rust, npm, NuGet, Python, Maven), known vulnerabilities, Electron hardening and code-signing changes."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
license-files = ["LICENSE"]
|
|
12
12
|
requires-python = ">=3.11"
|
|
13
13
|
authors = [{ name = "Sushant Nemade" }]
|
|
14
|
-
keywords = ["sbom", "cyclonedx", "sarif", "osv", "electron", "supply-chain", "security"]
|
|
14
|
+
keywords = ["sbom", "cyclonedx", "sarif", "osv", "electron", "supply-chain", "security", "cargo-auditable", "maven", "authenticode"]
|
|
15
15
|
classifiers = [
|
|
16
16
|
"Development Status :: 3 - Alpha",
|
|
17
17
|
"Environment :: Console",
|
|
@@ -34,6 +34,7 @@ dependencies = [
|
|
|
34
34
|
[project.optional-dependencies]
|
|
35
35
|
dev = [
|
|
36
36
|
"build>=1.2",
|
|
37
|
+
"cryptography>=44",
|
|
37
38
|
"cyclonedx-python-lib[json-validation]>=9",
|
|
38
39
|
"mypy>=1.13",
|
|
39
40
|
"pip-audit>=2.7",
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Read the Authenticode signer identity from a PE file without verifying it.
|
|
2
|
+
|
|
3
|
+
Only identifies who the embedded signature claims to be from. Cryptographic validity, chain trust,
|
|
4
|
+
revocation and catalog signatures are outside this module.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import struct
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
from intake_gatekeeper import objfile
|
|
14
|
+
from intake_gatekeeper.golang import Bytes
|
|
15
|
+
|
|
16
|
+
_MAX_TABLE = 16 * 1024 * 1024
|
|
17
|
+
_SIGNED_DATA = bytes.fromhex("2a864886f70d010702")
|
|
18
|
+
_OID_CN = bytes.fromhex("550403")
|
|
19
|
+
_OID_O = bytes.fromhex("55040a")
|
|
20
|
+
_STRING_TAGS = {
|
|
21
|
+
0x0C: "utf-8",
|
|
22
|
+
0x13: "ascii",
|
|
23
|
+
0x16: "ascii",
|
|
24
|
+
0x14: "latin-1",
|
|
25
|
+
0x1E: "utf-16-be",
|
|
26
|
+
0x1C: "utf-32-be",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class Signer:
|
|
32
|
+
subject: str
|
|
33
|
+
issuer: str
|
|
34
|
+
serial: str
|
|
35
|
+
sha1: str
|
|
36
|
+
sha256: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SignatureError(ValueError):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _tlv(data: bytes, pos: int, end: int) -> tuple[int, int, int]:
|
|
44
|
+
"""Return (tag, content start, content end) of the DER element at pos."""
|
|
45
|
+
if pos + 2 > end:
|
|
46
|
+
raise SignatureError("truncated DER element")
|
|
47
|
+
tag, first = data[pos], data[pos + 1]
|
|
48
|
+
pos += 2
|
|
49
|
+
if first < 0x80:
|
|
50
|
+
length = first
|
|
51
|
+
else:
|
|
52
|
+
count = first & 0x7F
|
|
53
|
+
if count == 0 or count > 4 or pos + count > end:
|
|
54
|
+
raise SignatureError("unsupported DER length")
|
|
55
|
+
length = int.from_bytes(data[pos : pos + count], "big")
|
|
56
|
+
pos += count
|
|
57
|
+
if pos + length > end:
|
|
58
|
+
raise SignatureError("DER element exceeds its container")
|
|
59
|
+
return tag, pos, pos + length
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _children(data: bytes, start: int, end: int) -> list[tuple[int, int, int, int]]:
|
|
63
|
+
"""(tag, content start, content end, element start) for each element in a range."""
|
|
64
|
+
items = []
|
|
65
|
+
while start < end:
|
|
66
|
+
tag, inner, stop = _tlv(data, start, end)
|
|
67
|
+
items.append((tag, inner, stop, start))
|
|
68
|
+
start = stop
|
|
69
|
+
return items
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _name(data: bytes, start: int, end: int) -> str:
|
|
73
|
+
parts: dict[bytes, str] = {}
|
|
74
|
+
for _set_tag, set_start, set_end, _ in _children(data, start, end):
|
|
75
|
+
for _seq_tag, seq_start, seq_end, _ in _children(data, set_start, set_end):
|
|
76
|
+
items = _children(data, seq_start, seq_end)
|
|
77
|
+
if len(items) != 2 or items[0][0] != 0x06:
|
|
78
|
+
continue
|
|
79
|
+
oid = data[items[0][1] : items[0][2]]
|
|
80
|
+
tag, value_start, value_end, _ = items[1]
|
|
81
|
+
encoding = _STRING_TAGS.get(tag)
|
|
82
|
+
if oid in (_OID_CN, _OID_O) and encoding and oid not in parts:
|
|
83
|
+
parts[oid] = data[value_start:value_end].decode(encoding, errors="replace")
|
|
84
|
+
labels = [f"CN={parts[_OID_CN]}"] if _OID_CN in parts else []
|
|
85
|
+
if _OID_O in parts:
|
|
86
|
+
labels.append(f"O={parts[_OID_O]}")
|
|
87
|
+
return ", ".join(labels) or "(no CN or O)"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _certificate(data: bytes, start: int, end: int) -> tuple[bytes, bytes, bytes]:
|
|
91
|
+
"""Return (serial, issuer name, subject name) contents of one certificate."""
|
|
92
|
+
_tag, tbs_start, tbs_end, _ = _children(data, start, end)[0]
|
|
93
|
+
fields = _children(data, tbs_start, tbs_end)
|
|
94
|
+
if fields and fields[0][0] == 0xA0:
|
|
95
|
+
fields = fields[1:]
|
|
96
|
+
if len(fields) < 5:
|
|
97
|
+
raise SignatureError("certificate is missing fields")
|
|
98
|
+
serial = data[fields[0][1] : fields[0][2]]
|
|
99
|
+
issuer = data[fields[2][1] : fields[2][2]]
|
|
100
|
+
subject = data[fields[4][1] : fields[4][2]]
|
|
101
|
+
return serial, issuer, subject
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def parse_pkcs7(blob: bytes) -> Signer:
|
|
105
|
+
_tag, info_start, info_end = _tlv(blob, 0, len(blob))
|
|
106
|
+
info = _children(blob, info_start, info_end)
|
|
107
|
+
if len(info) < 2 or blob[info[0][1] : info[0][2]] != _SIGNED_DATA or info[1][0] != 0xA0:
|
|
108
|
+
raise SignatureError("not a PKCS#7 SignedData structure")
|
|
109
|
+
_tag, signed_start, signed_end, _ = _children(blob, info[1][1], info[1][2])[0]
|
|
110
|
+
parts = _children(blob, signed_start, signed_end)
|
|
111
|
+
certificates = next((p for p in parts if p[0] == 0xA0), None)
|
|
112
|
+
signer_infos = parts[-1]
|
|
113
|
+
if certificates is None or signer_infos[0] != 0x31:
|
|
114
|
+
raise SignatureError("SignedData has no certificates or signer")
|
|
115
|
+
signer_info = _children(blob, signer_infos[1], signer_infos[2])[0]
|
|
116
|
+
sid = _children(blob, signer_info[1], signer_info[2])[1]
|
|
117
|
+
if sid[0] != 0x30:
|
|
118
|
+
raise SignatureError("signer identified by key id, not issuer and serial")
|
|
119
|
+
issuer_tlv, serial_tlv = _children(blob, sid[1], sid[2])[:2]
|
|
120
|
+
wanted_issuer = blob[issuer_tlv[1] : issuer_tlv[2]]
|
|
121
|
+
wanted_serial = blob[serial_tlv[1] : serial_tlv[2]]
|
|
122
|
+
for _tag, cert_start, cert_end, cert_begin in _children(blob, certificates[1], certificates[2]):
|
|
123
|
+
serial, issuer, subject = _certificate(blob, cert_start, cert_end)
|
|
124
|
+
if serial == wanted_serial and issuer == wanted_issuer:
|
|
125
|
+
der = blob[cert_begin:cert_end]
|
|
126
|
+
return Signer(
|
|
127
|
+
subject=_name(subject, 0, len(subject)),
|
|
128
|
+
issuer=_name(issuer, 0, len(issuer)),
|
|
129
|
+
serial=serial.lstrip(b"\x00").hex().upper() or "00",
|
|
130
|
+
sha1=hashlib.sha1(der, usedforsecurity=False).hexdigest().upper(),
|
|
131
|
+
sha256=hashlib.sha256(der).hexdigest(),
|
|
132
|
+
)
|
|
133
|
+
raise SignatureError("signer certificate is not included in the signature")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def read_signer(data: Bytes) -> Signer | None:
|
|
137
|
+
"""Signer of the first embedded Authenticode signature; None when the file has none."""
|
|
138
|
+
try:
|
|
139
|
+
table = objfile.pe_certificate_table(data)
|
|
140
|
+
except objfile.FormatError as error:
|
|
141
|
+
raise SignatureError(f"PE headers are malformed: {error}") from None
|
|
142
|
+
if table is None:
|
|
143
|
+
return None
|
|
144
|
+
if table.size > _MAX_TABLE or table.size < 8:
|
|
145
|
+
raise SignatureError("certificate table size is out of range")
|
|
146
|
+
length, _revision, kind = struct.unpack("<IHH", data[table.offset : table.offset + 8])
|
|
147
|
+
if kind != 0x0002 or not 8 < length <= table.size:
|
|
148
|
+
raise SignatureError("certificate table holds no PKCS#7 signature")
|
|
149
|
+
return parse_pkcs7(bytes(data[table.offset + 8 : table.offset + length]))
|