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.
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .eggs/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ *.egg
10
+ MANIFEST
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.
@@ -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
@@ -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
+ }