gate-cli 0.1.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.
- gate_cli-0.1.0/.gitignore +10 -0
- gate_cli-0.1.0/LICENSE +21 -0
- gate_cli-0.1.0/PKG-INFO +149 -0
- gate_cli-0.1.0/README.md +126 -0
- gate_cli-0.1.0/pyproject.toml +36 -0
- gate_cli-0.1.0/src/gate/__init__.py +1 -0
- gate_cli-0.1.0/src/gate/checks/__init__.py +0 -0
- gate_cli-0.1.0/src/gate/checks/cve.py +42 -0
- gate_cli-0.1.0/src/gate/checks/integrity.py +59 -0
- gate_cli-0.1.0/src/gate/checks/maintainer.py +32 -0
- gate_cli-0.1.0/src/gate/checks/quarantine.py +18 -0
- gate_cli-0.1.0/src/gate/checks/scripts.py +51 -0
- gate_cli-0.1.0/src/gate/cli.py +283 -0
- gate_cli-0.1.0/src/gate/config.py +27 -0
- gate_cli-0.1.0/src/gate/hooks/__init__.py +0 -0
- gate_cli-0.1.0/src/gate/hooks/precommit.py +56 -0
- gate_cli-0.1.0/src/gate/output.py +59 -0
- gate_cli-0.1.0/src/gate/registry/__init__.py +0 -0
- gate_cli-0.1.0/src/gate/registry/npm.py +85 -0
- gate_cli-0.1.0/src/gate/registry/pypi.py +81 -0
- gate_cli-0.1.0/src/gate/sbom.py +86 -0
- gate_cli-0.1.0/tests/__init__.py +0 -0
- gate_cli-0.1.0/tests/test_config.py +44 -0
- gate_cli-0.1.0/tests/test_cve.py +68 -0
- gate_cli-0.1.0/tests/test_hooks.py +96 -0
- gate_cli-0.1.0/tests/test_integrity.py +108 -0
- gate_cli-0.1.0/tests/test_maintainer.py +42 -0
- gate_cli-0.1.0/tests/test_quarantine.py +44 -0
- gate_cli-0.1.0/tests/test_registry_npm.py +80 -0
- gate_cli-0.1.0/tests/test_registry_pypi.py +72 -0
- gate_cli-0.1.0/tests/test_sbom.py +85 -0
- gate_cli-0.1.0/tests/test_scripts.py +71 -0
gate_cli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mika
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gate_cli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gate-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Supply chain security scanner for npm and pip packages
|
|
5
|
+
Project-URL: Homepage, https://github.com/Mhacker1020/gate
|
|
6
|
+
Project-URL: Repository, https://github.com/Mhacker1020/gate
|
|
7
|
+
Project-URL: Issues, https://github.com/Mhacker1020/gate/issues
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: cve,npm,pip,sbom,security,supply-chain
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# gate
|
|
25
|
+
|
|
26
|
+
Supply chain security scanner for npm and pip packages.
|
|
27
|
+
|
|
28
|
+
Checks packages for known CVEs, quarantines newly published versions, and warns about suspicious install scripts — before they hit your project.
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
✓ flask 3.1.1
|
|
32
|
+
✗ requests 2.28.0
|
|
33
|
+
CVE-2023-32681: Unintended leak of Proxy-Authorization header
|
|
34
|
+
⚠ urllib3 2.3.0
|
|
35
|
+
Published 2 day(s) ago (quarantine window: 7 days)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Why
|
|
39
|
+
|
|
40
|
+
Supply chain attacks increasingly target the window between a package being published and being detected as malicious. Existing tools (Trivy, Snyk, Dependabot) catch *known* CVEs but miss:
|
|
41
|
+
|
|
42
|
+
- Newly published malicious versions not yet in any database
|
|
43
|
+
- Install scripts that run arbitrary code on `pip install`
|
|
44
|
+
|
|
45
|
+
Gate adds a quarantine window — new versions are flagged until the community has had time to catch problems.
|
|
46
|
+
|
|
47
|
+
**Zero runtime dependencies.** A supply chain security tool that trusts its own supply chain is not a security tool.
|
|
48
|
+
|
|
49
|
+
## Checks
|
|
50
|
+
|
|
51
|
+
| Check | What it catches |
|
|
52
|
+
|-------|----------------|
|
|
53
|
+
| CVE scan | Known vulnerabilities via OSV.dev |
|
|
54
|
+
| Quarantine window | Versions published within N days |
|
|
55
|
+
| Install scripts | npm packages running suspicious install hooks |
|
|
56
|
+
| Hash verification | Detects tampered packages via lock file integrity checks |
|
|
57
|
+
| Maintainer change | Flags when a package owner has changed between versions |
|
|
58
|
+
| SBOM export | Generates a CycloneDX 1.6 Software Bill of Materials |
|
|
59
|
+
|
|
60
|
+
## Installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install gate-cli
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Requires Python 3.12+.
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
### Check a single package
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
gate check requests
|
|
74
|
+
gate check requests==2.28.0
|
|
75
|
+
gate check lodash --npm
|
|
76
|
+
gate check lodash==4.17.15 --npm
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Scan all packages in a project
|
|
80
|
+
|
|
81
|
+
Automatically detects `requirements.txt` or `package-lock.json`:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
gate scan
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Exit code is non-zero if errors are found — suitable for CI pipelines.
|
|
88
|
+
|
|
89
|
+
### Export a CycloneDX SBOM
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
gate scan --sbom # print to stdout
|
|
93
|
+
gate scan --sbom report.cdx.json # write to file
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Install as a git pre-commit hook
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
gate init
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Gate will run automatically on every `git commit` when lock files change. To remove:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
gate uninstall
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Configuration
|
|
109
|
+
|
|
110
|
+
Create `.gate.toml` in your project root to override defaults:
|
|
111
|
+
|
|
112
|
+
```toml
|
|
113
|
+
quarantine_days = 14
|
|
114
|
+
|
|
115
|
+
fail_on = ["critical_cve", "install_script"]
|
|
116
|
+
warn_on = ["recent_release"]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
| Option | Default | Description |
|
|
120
|
+
|--------|---------|-------------|
|
|
121
|
+
| `quarantine_days` | `7` | Days a new release must age before passing |
|
|
122
|
+
| `fail_on` | `["critical_cve", "install_script"]` | Conditions that block the commit / exit 1 |
|
|
123
|
+
| `warn_on` | `["recent_release"]` | Conditions that warn but allow through |
|
|
124
|
+
|
|
125
|
+
Move `recent_release` from `warn_on` to `fail_on` to enforce the quarantine window strictly.
|
|
126
|
+
|
|
127
|
+
## Supported ecosystems
|
|
128
|
+
|
|
129
|
+
| Ecosystem | Lock file | Registry |
|
|
130
|
+
|-----------|-----------|----------|
|
|
131
|
+
| PyPI | `requirements.txt` | pypi.org |
|
|
132
|
+
| npm | `package-lock.json` | registry.npmjs.org |
|
|
133
|
+
|
|
134
|
+
CVE data is sourced from [OSV.dev](https://osv.dev) — Google's open vulnerability database.
|
|
135
|
+
|
|
136
|
+
## Contributing
|
|
137
|
+
|
|
138
|
+
Gate is open source and built for the community. Contributions welcome.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
git clone https://github.com/Mhacker1020/gate
|
|
142
|
+
cd gate
|
|
143
|
+
pip install -e ".[dev]"
|
|
144
|
+
python -m pytest
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT
|
gate_cli-0.1.0/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# gate
|
|
2
|
+
|
|
3
|
+
Supply chain security scanner for npm and pip packages.
|
|
4
|
+
|
|
5
|
+
Checks packages for known CVEs, quarantines newly published versions, and warns about suspicious install scripts — before they hit your project.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
✓ flask 3.1.1
|
|
9
|
+
✗ requests 2.28.0
|
|
10
|
+
CVE-2023-32681: Unintended leak of Proxy-Authorization header
|
|
11
|
+
⚠ urllib3 2.3.0
|
|
12
|
+
Published 2 day(s) ago (quarantine window: 7 days)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
Supply chain attacks increasingly target the window between a package being published and being detected as malicious. Existing tools (Trivy, Snyk, Dependabot) catch *known* CVEs but miss:
|
|
18
|
+
|
|
19
|
+
- Newly published malicious versions not yet in any database
|
|
20
|
+
- Install scripts that run arbitrary code on `pip install`
|
|
21
|
+
|
|
22
|
+
Gate adds a quarantine window — new versions are flagged until the community has had time to catch problems.
|
|
23
|
+
|
|
24
|
+
**Zero runtime dependencies.** A supply chain security tool that trusts its own supply chain is not a security tool.
|
|
25
|
+
|
|
26
|
+
## Checks
|
|
27
|
+
|
|
28
|
+
| Check | What it catches |
|
|
29
|
+
|-------|----------------|
|
|
30
|
+
| CVE scan | Known vulnerabilities via OSV.dev |
|
|
31
|
+
| Quarantine window | Versions published within N days |
|
|
32
|
+
| Install scripts | npm packages running suspicious install hooks |
|
|
33
|
+
| Hash verification | Detects tampered packages via lock file integrity checks |
|
|
34
|
+
| Maintainer change | Flags when a package owner has changed between versions |
|
|
35
|
+
| SBOM export | Generates a CycloneDX 1.6 Software Bill of Materials |
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install gate-cli
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Requires Python 3.12+.
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
### Check a single package
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
gate check requests
|
|
51
|
+
gate check requests==2.28.0
|
|
52
|
+
gate check lodash --npm
|
|
53
|
+
gate check lodash==4.17.15 --npm
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Scan all packages in a project
|
|
57
|
+
|
|
58
|
+
Automatically detects `requirements.txt` or `package-lock.json`:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
gate scan
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Exit code is non-zero if errors are found — suitable for CI pipelines.
|
|
65
|
+
|
|
66
|
+
### Export a CycloneDX SBOM
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
gate scan --sbom # print to stdout
|
|
70
|
+
gate scan --sbom report.cdx.json # write to file
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Install as a git pre-commit hook
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
gate init
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Gate will run automatically on every `git commit` when lock files change. To remove:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
gate uninstall
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
Create `.gate.toml` in your project root to override defaults:
|
|
88
|
+
|
|
89
|
+
```toml
|
|
90
|
+
quarantine_days = 14
|
|
91
|
+
|
|
92
|
+
fail_on = ["critical_cve", "install_script"]
|
|
93
|
+
warn_on = ["recent_release"]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
| Option | Default | Description |
|
|
97
|
+
|--------|---------|-------------|
|
|
98
|
+
| `quarantine_days` | `7` | Days a new release must age before passing |
|
|
99
|
+
| `fail_on` | `["critical_cve", "install_script"]` | Conditions that block the commit / exit 1 |
|
|
100
|
+
| `warn_on` | `["recent_release"]` | Conditions that warn but allow through |
|
|
101
|
+
|
|
102
|
+
Move `recent_release` from `warn_on` to `fail_on` to enforce the quarantine window strictly.
|
|
103
|
+
|
|
104
|
+
## Supported ecosystems
|
|
105
|
+
|
|
106
|
+
| Ecosystem | Lock file | Registry |
|
|
107
|
+
|-----------|-----------|----------|
|
|
108
|
+
| PyPI | `requirements.txt` | pypi.org |
|
|
109
|
+
| npm | `package-lock.json` | registry.npmjs.org |
|
|
110
|
+
|
|
111
|
+
CVE data is sourced from [OSV.dev](https://osv.dev) — Google's open vulnerability database.
|
|
112
|
+
|
|
113
|
+
## Contributing
|
|
114
|
+
|
|
115
|
+
Gate is open source and built for the community. Contributions welcome.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
git clone https://github.com/Mhacker1020/gate
|
|
119
|
+
cd gate
|
|
120
|
+
pip install -e ".[dev]"
|
|
121
|
+
python -m pytest
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gate-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Supply chain security scanner for npm and pip packages"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
keywords = ["security", "supply-chain", "npm", "pip", "cve", "sbom"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Environment :: Console",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Topic :: Security",
|
|
21
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/Mhacker1020/gate"
|
|
26
|
+
Repository = "https://github.com/Mhacker1020/gate"
|
|
27
|
+
Issues = "https://github.com/Mhacker1020/gate/issues"
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
dev = ["pytest"]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
gate = "gate.cli:main"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel]
|
|
36
|
+
packages = ["src/gate"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import urllib.request
|
|
3
|
+
import urllib.error
|
|
4
|
+
|
|
5
|
+
OSV_API = "https://api.osv.dev/v1/query"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def check_cve(name: str, version: str, ecosystem: str) -> list[dict]:
|
|
9
|
+
payload = json.dumps({
|
|
10
|
+
"package": {"name": name, "ecosystem": ecosystem},
|
|
11
|
+
"version": version,
|
|
12
|
+
}).encode()
|
|
13
|
+
|
|
14
|
+
req = urllib.request.Request(
|
|
15
|
+
OSV_API,
|
|
16
|
+
data=payload,
|
|
17
|
+
headers={"Content-Type": "application/json"},
|
|
18
|
+
method="POST",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
23
|
+
data = json.loads(resp.read())
|
|
24
|
+
except Exception:
|
|
25
|
+
return []
|
|
26
|
+
|
|
27
|
+
seen: set[str] = set()
|
|
28
|
+
vulns = []
|
|
29
|
+
for vuln in data.get("vulns", []):
|
|
30
|
+
cve_id = next(
|
|
31
|
+
(a for a in vuln.get("aliases", []) if a.startswith("CVE-")),
|
|
32
|
+
vuln.get("id", ""),
|
|
33
|
+
)
|
|
34
|
+
if cve_id in seen:
|
|
35
|
+
continue
|
|
36
|
+
seen.add(cve_id)
|
|
37
|
+
vulns.append({
|
|
38
|
+
"id": cve_id,
|
|
39
|
+
"summary": vuln.get("summary", "No description"),
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return vulns
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
def check_integrity(local: str, remote: str) -> dict:
|
|
2
|
+
"""
|
|
3
|
+
Compare a locally stored integrity hash against the value from the registry.
|
|
4
|
+
|
|
5
|
+
Both values are expected in standard format:
|
|
6
|
+
npm: "sha512-<base64>"
|
|
7
|
+
PyPI: "sha256:<hex>"
|
|
8
|
+
"""
|
|
9
|
+
if not local or not remote:
|
|
10
|
+
return {"ok": True, "skipped": True, "message": "No hash to compare"}
|
|
11
|
+
|
|
12
|
+
match = local.strip() == remote.strip()
|
|
13
|
+
return {
|
|
14
|
+
"ok": match,
|
|
15
|
+
"skipped": False,
|
|
16
|
+
"local": local,
|
|
17
|
+
"remote": remote,
|
|
18
|
+
"message": None if match else "Hash mismatch — package may have been tampered with",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_requirements_hashes(path) -> dict[str, dict[str, str]]:
|
|
23
|
+
"""
|
|
24
|
+
Parse hashes from a requirements.txt file that was generated with
|
|
25
|
+
pip-compile --generate-hashes or pip install --require-hashes.
|
|
26
|
+
|
|
27
|
+
Returns: {package_name_lower: {version: "sha256:<hex>"}}
|
|
28
|
+
|
|
29
|
+
Example line:
|
|
30
|
+
requests==2.28.0 \\
|
|
31
|
+
--hash=sha256:ae72a32d...
|
|
32
|
+
"""
|
|
33
|
+
import re
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
text = Path(path).read_text(encoding="utf-8")
|
|
37
|
+
# Join continuation lines
|
|
38
|
+
text = re.sub(r"\\\n\s*", " ", text)
|
|
39
|
+
|
|
40
|
+
result: dict[str, dict[str, str]] = {}
|
|
41
|
+
for line in text.splitlines():
|
|
42
|
+
line = line.strip()
|
|
43
|
+
if not line or line.startswith("#"):
|
|
44
|
+
continue
|
|
45
|
+
if "==" not in line:
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
# Extract name==version
|
|
49
|
+
spec_part = line.split()[0]
|
|
50
|
+
name, version = spec_part.split("==", 1)
|
|
51
|
+
name = name.strip().lower()
|
|
52
|
+
version = version.strip()
|
|
53
|
+
|
|
54
|
+
# Extract first sha256 hash
|
|
55
|
+
match = re.search(r"--hash=sha256:([a-f0-9]+)", line)
|
|
56
|
+
if match:
|
|
57
|
+
result.setdefault(name, {})[version] = f"sha256:{match.group(1)}"
|
|
58
|
+
|
|
59
|
+
return result
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
def check_maintainer_change(
|
|
2
|
+
current: list[str],
|
|
3
|
+
previous: list[str],
|
|
4
|
+
) -> dict:
|
|
5
|
+
"""
|
|
6
|
+
Compare maintainer lists between two versions.
|
|
7
|
+
Returns a dict describing any changes found.
|
|
8
|
+
"""
|
|
9
|
+
current_set = set(current)
|
|
10
|
+
previous_set = set(previous)
|
|
11
|
+
|
|
12
|
+
added = current_set - previous_set
|
|
13
|
+
removed = previous_set - current_set
|
|
14
|
+
|
|
15
|
+
if not added and not removed:
|
|
16
|
+
return {"ok": True, "added": [], "removed": []}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
"ok": False,
|
|
20
|
+
"added": sorted(added),
|
|
21
|
+
"removed": sorted(removed),
|
|
22
|
+
"message": _format_message(added, removed),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _format_message(added: set[str], removed: set[str]) -> str:
|
|
27
|
+
parts = []
|
|
28
|
+
if added:
|
|
29
|
+
parts.append(f"new maintainer(s): {', '.join(sorted(added))}")
|
|
30
|
+
if removed:
|
|
31
|
+
parts.append(f"removed maintainer(s): {', '.join(sorted(removed))}")
|
|
32
|
+
return "; ".join(parts)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def check_quarantine(published: datetime | None, quarantine_days: int = 7) -> dict:
|
|
5
|
+
if published is None:
|
|
6
|
+
return {"ok": True, "days_old": None, "message": None}
|
|
7
|
+
|
|
8
|
+
now = datetime.now(timezone.utc)
|
|
9
|
+
days_old = (now - published).days
|
|
10
|
+
|
|
11
|
+
if days_old < quarantine_days:
|
|
12
|
+
return {
|
|
13
|
+
"ok": False,
|
|
14
|
+
"days_old": days_old,
|
|
15
|
+
"message": f"Published {days_old} day(s) ago (quarantine window: {quarantine_days} days)",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return {"ok": True, "days_old": days_old, "message": None}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
# Patterns that indicate a script is doing something suspicious
|
|
4
|
+
_SUSPICIOUS: list[tuple[str, str]] = [
|
|
5
|
+
(r"\bcurl\b", "network fetch (curl)"),
|
|
6
|
+
(r"\bwget\b", "network fetch (wget)"),
|
|
7
|
+
(r"\bfetch\b", "network fetch (fetch)"),
|
|
8
|
+
(r"\bbase64\b", "base64 encoding"),
|
|
9
|
+
(r"atob\s*\(", "base64 decode (atob)"),
|
|
10
|
+
(r"Buffer\.from\b", "binary decoding (Buffer.from)"),
|
|
11
|
+
(r"\beval\s*\(", "eval execution"),
|
|
12
|
+
(r"Function\s*\(", "dynamic code execution (Function)"),
|
|
13
|
+
(r"\bexec\s*\(", "shell execution (exec)"),
|
|
14
|
+
(r"\bspawn\s*\(", "shell execution (spawn)"),
|
|
15
|
+
(r"\bchild_process\b", "child process usage"),
|
|
16
|
+
(r"https?://", "hardcoded URL"),
|
|
17
|
+
(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", "hardcoded IP address"),
|
|
18
|
+
(r"\bpowershell\b", "PowerShell execution"),
|
|
19
|
+
(r"\bchmod\b", "permission change"),
|
|
20
|
+
(r"process\.env\b", "environment variable access"),
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def analyze_script(script: str) -> list[str]:
|
|
25
|
+
"""Return a list of suspicious pattern descriptions found in the script."""
|
|
26
|
+
found = []
|
|
27
|
+
for pattern, description in _SUSPICIOUS:
|
|
28
|
+
if re.search(pattern, script, re.IGNORECASE):
|
|
29
|
+
found.append(description)
|
|
30
|
+
return found
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def check_install_scripts(install_scripts: dict[str, str]) -> dict:
|
|
34
|
+
"""
|
|
35
|
+
Analyze install scripts for suspicious patterns.
|
|
36
|
+
Returns a dict with 'ok', 'findings' (per script), and 'suspicious' flag.
|
|
37
|
+
"""
|
|
38
|
+
if not install_scripts:
|
|
39
|
+
return {"ok": True, "findings": {}}
|
|
40
|
+
|
|
41
|
+
findings: dict[str, list[str]] = {}
|
|
42
|
+
for script_name, script_body in install_scripts.items():
|
|
43
|
+
patterns = analyze_script(script_body)
|
|
44
|
+
if patterns:
|
|
45
|
+
findings[script_name] = patterns
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
"ok": len(findings) == 0,
|
|
49
|
+
"findings": findings,
|
|
50
|
+
"raw": install_scripts,
|
|
51
|
+
}
|