slopguard-cli 0.1.0__py3-none-any.whl
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.
- slopguard/__init__.py +7 -0
- slopguard/__main__.py +13 -0
- slopguard/cli.py +321 -0
- slopguard/config.py +139 -0
- slopguard/data/__init__.py +40 -0
- slopguard/data/hallucinations_seed.json +5603 -0
- slopguard/data/popular_packages.json +2007 -0
- slopguard/models.py +133 -0
- slopguard/parsers/__init__.py +9 -0
- slopguard/parsers/base.py +28 -0
- slopguard/parsers/npm.py +146 -0
- slopguard/parsers/python.py +269 -0
- slopguard/registry/__init__.py +14 -0
- slopguard/registry/base.py +107 -0
- slopguard/registry/npm.py +78 -0
- slopguard/registry/pypi.py +99 -0
- slopguard/report/__init__.py +8 -0
- slopguard/report/json.py +17 -0
- slopguard/report/terminal.py +87 -0
- slopguard/scoring/__init__.py +7 -0
- slopguard/scoring/engine.py +235 -0
- slopguard/scoring/signals.py +183 -0
- slopguard/update.py +15 -0
- slopguard_cli-0.1.0.dist-info/METADATA +197 -0
- slopguard_cli-0.1.0.dist-info/RECORD +28 -0
- slopguard_cli-0.1.0.dist-info/WHEEL +4 -0
- slopguard_cli-0.1.0.dist-info/entry_points.txt +2 -0
- slopguard_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Individual risk-signal functions.
|
|
2
|
+
|
|
3
|
+
Each function consumes the dependency, optional registry metadata, and optional
|
|
4
|
+
context (popularity sets, hallucination DB hits). They return a :class:`Signal`
|
|
5
|
+
or ``None``. The engine composes them.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from datetime import UTC, datetime, timedelta
|
|
12
|
+
|
|
13
|
+
from slopguard.models import Dependency, HallucinationEntry, Signal
|
|
14
|
+
from slopguard.registry.base import PackageMetadata
|
|
15
|
+
|
|
16
|
+
# Weights mirror the spec section 9 table.
|
|
17
|
+
W_DB_HIT = 0.90
|
|
18
|
+
W_NOT_FOUND = 0.85
|
|
19
|
+
W_RECENT_30 = 0.20
|
|
20
|
+
W_RECENT_7 = 0.35
|
|
21
|
+
W_LOW_DOWNLOADS = 0.15
|
|
22
|
+
W_NEW_PUBLISHER = 0.20
|
|
23
|
+
W_SOLO_NEW = 0.30
|
|
24
|
+
W_LEVENSHTEIN = 0.25
|
|
25
|
+
W_NAME_PATTERN = 0.10
|
|
26
|
+
|
|
27
|
+
_NAME_PATTERN_RE = re.compile(
|
|
28
|
+
r"^(?:[a-z0-9]+[-_])+(helpers?|utils?|async|pro|kit|sdk|tools?|extras?|plus|next|core)$",
|
|
29
|
+
re.IGNORECASE,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _now() -> datetime:
|
|
34
|
+
return datetime.now(UTC)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _ensure_aware(dt: datetime) -> datetime:
|
|
38
|
+
if dt.tzinfo is None:
|
|
39
|
+
return dt.replace(tzinfo=UTC)
|
|
40
|
+
return dt
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def hallucination_db_hit(dep: Dependency, entry: HallucinationEntry | None) -> Signal | None:
|
|
44
|
+
if entry is None:
|
|
45
|
+
return None
|
|
46
|
+
models = ", ".join(entry.models_observed) if entry.models_observed else "unspecified models"
|
|
47
|
+
return Signal(
|
|
48
|
+
type="hallucination_db_hit",
|
|
49
|
+
weight=W_DB_HIT,
|
|
50
|
+
detail=(f"Matched seed DB entry; recurrence {entry.recurrence_rate:.2f} across {models}."),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def registry_not_found(meta: PackageMetadata) -> Signal | None:
|
|
55
|
+
if meta.exists:
|
|
56
|
+
return None
|
|
57
|
+
return Signal(
|
|
58
|
+
type="registry_not_found",
|
|
59
|
+
weight=W_NOT_FOUND,
|
|
60
|
+
detail="Registry returned 404 — package name is not currently published.",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def recently_published(meta: PackageMetadata) -> tuple[Signal | None, Signal | None]:
|
|
65
|
+
"""Return (very_recent, recent) signals — at most one will be non-None."""
|
|
66
|
+
if not meta.exists or meta.first_release is None:
|
|
67
|
+
return (None, None)
|
|
68
|
+
age = _now() - _ensure_aware(meta.first_release)
|
|
69
|
+
if age < timedelta(days=7):
|
|
70
|
+
return (
|
|
71
|
+
Signal(
|
|
72
|
+
type="very_recently_published",
|
|
73
|
+
weight=W_RECENT_7,
|
|
74
|
+
detail=f"First release {age.days} day(s) ago.",
|
|
75
|
+
),
|
|
76
|
+
None,
|
|
77
|
+
)
|
|
78
|
+
if age < timedelta(days=30):
|
|
79
|
+
return (
|
|
80
|
+
None,
|
|
81
|
+
Signal(
|
|
82
|
+
type="recently_published",
|
|
83
|
+
weight=W_RECENT_30,
|
|
84
|
+
detail=f"First release {age.days} day(s) ago.",
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
return (None, None)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def low_downloads(meta: PackageMetadata) -> Signal | None:
|
|
91
|
+
if not meta.exists or meta.downloads_recent is None:
|
|
92
|
+
return None
|
|
93
|
+
if meta.downloads_recent >= 100:
|
|
94
|
+
return None
|
|
95
|
+
return Signal(
|
|
96
|
+
type="low_downloads",
|
|
97
|
+
weight=W_LOW_DOWNLOADS,
|
|
98
|
+
detail=f"{meta.downloads_recent} recent downloads.",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def new_publisher(meta: PackageMetadata) -> Signal | None:
|
|
103
|
+
if not meta.exists or meta.publisher_created is None:
|
|
104
|
+
return None
|
|
105
|
+
age = _now() - _ensure_aware(meta.publisher_created)
|
|
106
|
+
if age < timedelta(days=30):
|
|
107
|
+
return Signal(
|
|
108
|
+
type="new_publisher",
|
|
109
|
+
weight=W_NEW_PUBLISHER,
|
|
110
|
+
detail=f"Publisher account created {age.days} day(s) ago.",
|
|
111
|
+
)
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def single_release_new_account(meta: PackageMetadata) -> Signal | None:
|
|
116
|
+
if not meta.exists or meta.publisher_created is None or meta.publisher_package_count is None:
|
|
117
|
+
return None
|
|
118
|
+
age = _now() - _ensure_aware(meta.publisher_created)
|
|
119
|
+
if meta.publisher_package_count == 1 and age < timedelta(days=60):
|
|
120
|
+
return Signal(
|
|
121
|
+
type="single_release_new_account",
|
|
122
|
+
weight=W_SOLO_NEW,
|
|
123
|
+
detail=(f"Publisher's only release; account {age.days} day(s) old."),
|
|
124
|
+
)
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def levenshtein(a: str, b: str) -> int:
|
|
129
|
+
"""Iterative Levenshtein distance. Small inputs — no heuristic shortcuts."""
|
|
130
|
+
if a == b:
|
|
131
|
+
return 0
|
|
132
|
+
if not a:
|
|
133
|
+
return len(b)
|
|
134
|
+
if not b:
|
|
135
|
+
return len(a)
|
|
136
|
+
prev = list(range(len(b) + 1))
|
|
137
|
+
for i, ca in enumerate(a, start=1):
|
|
138
|
+
curr = [i] + [0] * len(b)
|
|
139
|
+
for j, cb in enumerate(b, start=1):
|
|
140
|
+
cost = 0 if ca == cb else 1
|
|
141
|
+
curr[j] = min(
|
|
142
|
+
curr[j - 1] + 1, # insertion
|
|
143
|
+
prev[j] + 1, # deletion
|
|
144
|
+
prev[j - 1] + cost, # substitution
|
|
145
|
+
)
|
|
146
|
+
prev = curr
|
|
147
|
+
return prev[-1]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def levenshtein_typo(dep: Dependency, popular: frozenset[str]) -> Signal | None:
|
|
151
|
+
"""Flag when the dep name is Levenshtein 1 or 2 from a popular package (and not that package)."""
|
|
152
|
+
lower = dep.name.lower()
|
|
153
|
+
if lower in popular:
|
|
154
|
+
return None
|
|
155
|
+
best: tuple[int, str] | None = None
|
|
156
|
+
for candidate in popular:
|
|
157
|
+
# Quick length-based prune.
|
|
158
|
+
if abs(len(candidate) - len(lower)) > 2:
|
|
159
|
+
continue
|
|
160
|
+
d = levenshtein(lower, candidate)
|
|
161
|
+
if d <= 2 and (best is None or d < best[0]):
|
|
162
|
+
best = (d, candidate)
|
|
163
|
+
if d == 1:
|
|
164
|
+
break
|
|
165
|
+
if best is None:
|
|
166
|
+
return None
|
|
167
|
+
d, candidate = best
|
|
168
|
+
return Signal(
|
|
169
|
+
type="levenshtein_typo",
|
|
170
|
+
weight=W_LEVENSHTEIN,
|
|
171
|
+
detail=f"Levenshtein {d} from popular package '{candidate}'.",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def name_pattern_suspicious(dep: Dependency) -> Signal | None:
|
|
176
|
+
"""Flag classic hallucination shapes like `<stem>-helpers`, `<stem>-utils`."""
|
|
177
|
+
if not _NAME_PATTERN_RE.match(dep.name):
|
|
178
|
+
return None
|
|
179
|
+
return Signal(
|
|
180
|
+
type="name_pattern_suspicious",
|
|
181
|
+
weight=W_NAME_PATTERN,
|
|
182
|
+
detail="Name matches a known hallucination shape (e.g. <stem>-helpers / -utils / -async / -pro).",
|
|
183
|
+
)
|
slopguard/update.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Stub for the future ``slopguard update`` remote DB refresh.
|
|
2
|
+
|
|
3
|
+
# TODO(v0.2): implement a signed-bundle download from a trusted host.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run() -> int:
|
|
10
|
+
"""Print a not-yet-implemented message and exit 0."""
|
|
11
|
+
print(
|
|
12
|
+
"slopguard update is not implemented in v0.1. The embedded seed database "
|
|
13
|
+
"ships with the package and is refreshed by upgrading slopguard itself."
|
|
14
|
+
)
|
|
15
|
+
return 0
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: slopguard-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Defend developers and AI coding agents against slopsquatting (hallucinated package names).
|
|
5
|
+
Project-URL: Homepage, https://github.com/hariomunknownslab/slopguard
|
|
6
|
+
Project-URL: Repository, https://github.com/hariomunknownslab/slopguard
|
|
7
|
+
Project-URL: Issues, https://github.com/hariomunknownslab/slopguard/issues
|
|
8
|
+
Author-email: SlopGuard <contact@unknownslab.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai,llm,package-hallucination,security,slopsquatting,supply-chain
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Security
|
|
20
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: httpx>=0.27.0
|
|
23
|
+
Requires-Dist: pydantic>=2.6.0
|
|
24
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
25
|
+
Requires-Dist: rich>=13.7.0
|
|
26
|
+
Requires-Dist: tomli>=2.0.1; python_version < '3.11'
|
|
27
|
+
Requires-Dist: typer>=0.12.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: build>=1.2.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: jsonschema>=4.21.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: respx>=0.21.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: types-pyyaml>=6.0.12; extra == 'dev'
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
|
|
40
|
+
# SlopGuard
|
|
41
|
+
|
|
42
|
+
[](https://github.com/hariomunknownslab/slopguard/actions/workflows/ci.yml)
|
|
43
|
+
[](https://pypi.org/project/slopguard/)
|
|
44
|
+
[](https://opensource.org/licenses/MIT)
|
|
45
|
+
|
|
46
|
+
**Slopsquatting** is what happens when an LLM hallucinates a plausible-sounding
|
|
47
|
+
package name that does not exist on the public registry — and then an attacker
|
|
48
|
+
registers that exact name with malware so the *next* developer (or AI agent)
|
|
49
|
+
who follows the suggestion installs it. SlopGuard scans your project's
|
|
50
|
+
dependencies, flags entries that are either known LLM hallucinations or that
|
|
51
|
+
show the behavioural fingerprint of a slopsquat, and exits non-zero so CI
|
|
52
|
+
fails the build before the malware reaches `node_modules` or `site-packages`.
|
|
53
|
+
|
|
54
|
+
> SlopGuard stops AI coding agents from installing packages that LLMs hallucinated.
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install slopguard-cli
|
|
60
|
+
# Homebrew formula ships in a later release:
|
|
61
|
+
# brew install slopguard
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
> The PyPI **distribution** name is `slopguard-cli` (the name `slopguard`
|
|
65
|
+
> overlapped with an unrelated existing package on PyPI). The installed
|
|
66
|
+
> command, the Python import, and everything else stays `slopguard`.
|
|
67
|
+
|
|
68
|
+
Python 3.11+ is required.
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
### 1. Scan the current directory
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
slopguard scan
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
SlopGuard auto-discovers `package.json`, `package-lock.json`,
|
|
79
|
+
`requirements.txt`, `pyproject.toml`, and `Pipfile` (up to two levels deep),
|
|
80
|
+
probes each name against the public registry, and prints a Rich table:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
SlopGuard v0.1.0 — scanning /home/dev/myproj
|
|
84
|
+
|
|
85
|
+
Detected manifests:
|
|
86
|
+
• package.json (npm, 32 deps)
|
|
87
|
+
• requirements.txt (pypi, 15 deps)
|
|
88
|
+
|
|
89
|
+
Scanned 47 dependencies in 3.1s.
|
|
90
|
+
|
|
91
|
+
┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
92
|
+
┃ Package ┃ Risk ┃ Reason ┃
|
|
93
|
+
┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
|
94
|
+
│ react-codeshift │ HALLUCIN. │ Matched seed DB entry; recurrence 0.71. │
|
|
95
|
+
│ langchain-helpers │ SUSPICIOUS │ Created 14 days ago, 48 downloads, new auth. │
|
|
96
|
+
│ openai-utils │ SUSPICIOUS │ Levenshtein 2 from popular package 'openai'. │
|
|
97
|
+
│ requests │ CLEAN │ Established package. │
|
|
98
|
+
└────────────────────┴────────────┴──────────────────────────────────────────────┘
|
|
99
|
+
|
|
100
|
+
Summary: 1 hallucinated, 2 suspicious, 44 clean, 0 error(s).
|
|
101
|
+
Exit code: 1
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 2. Scan a specific path
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
slopguard scan ./mono/services/api
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 3. CI mode — JSON output, strict failure threshold
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
slopguard scan --format json --output report.json --fail-on hallucinated
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
See [`.github/workflows/slopguard.yml.example`](.github/workflows/slopguard.yml.example)
|
|
117
|
+
for a drop-in GitHub Actions workflow and [`docs/ci-integration.md`](docs/ci-integration.md)
|
|
118
|
+
for details on other CI providers.
|
|
119
|
+
|
|
120
|
+
## How it works
|
|
121
|
+
|
|
122
|
+
For every dependency, SlopGuard computes a small set of independent signals
|
|
123
|
+
and combines them into a single risk score in `[0.0, 1.0]`:
|
|
124
|
+
|
|
125
|
+
- **Hallucination-DB hit** (weight 0.90) — exact match in an embedded seed
|
|
126
|
+
database of names known to be hallucinated by major LLMs.
|
|
127
|
+
- **Registry not found** (0.85) — the registry returns 404 for the name. The
|
|
128
|
+
most common slopsquat shape: a name that doesn't exist *yet*.
|
|
129
|
+
- **Very recently / recently published** (0.35 / 0.20) — first release < 7
|
|
130
|
+
days / < 30 days old.
|
|
131
|
+
- **Low downloads** (0.15) — < 100 downloads in the last month (npm) or last
|
|
132
|
+
week (PyPI).
|
|
133
|
+
- **New publisher** (0.20) and **single-release new account** (0.30) — a
|
|
134
|
+
brand-new account whose only release is the package you're about to install.
|
|
135
|
+
- **Levenshtein typo** (0.25) — name is 1–2 edits away from a top-1000
|
|
136
|
+
popular package (likely a typosquat).
|
|
137
|
+
- **Suspicious name pattern** (0.10) — matches a classic LLM-hallucination
|
|
138
|
+
shape like `<stem>-helpers`, `<stem>-utils`, `<stem>-async`, `<stem>-pro`.
|
|
139
|
+
|
|
140
|
+
The default cutoffs map scores `≥ 0.85` → **hallucinated**, `≥ 0.40` →
|
|
141
|
+
**suspicious**, else **clean**. Both thresholds are tunable in
|
|
142
|
+
`.slopguard.yaml`. See [`docs/detection.md`](docs/detection.md) for the full
|
|
143
|
+
table, the order of operations, and edge cases.
|
|
144
|
+
|
|
145
|
+
## Configuration
|
|
146
|
+
|
|
147
|
+
`.slopguard.yaml`, picked up automatically from the scan target or any
|
|
148
|
+
ancestor (up to 3 levels):
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
ignore:
|
|
152
|
+
packages: ["internal-tool"]
|
|
153
|
+
patterns: ["^@mycompany/"]
|
|
154
|
+
|
|
155
|
+
fail_on: suspicious # any | hallucinated | suspicious | none
|
|
156
|
+
|
|
157
|
+
network:
|
|
158
|
+
enabled: true
|
|
159
|
+
timeout_seconds: 5
|
|
160
|
+
concurrency: 16
|
|
161
|
+
|
|
162
|
+
scoring:
|
|
163
|
+
suspicious_min_score: 0.4
|
|
164
|
+
hallucinated_min_score: 0.85
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
CLI flags override the file. See [`docs/usage.md`](docs/usage.md) for the full
|
|
168
|
+
reference.
|
|
169
|
+
|
|
170
|
+
## What it does NOT do (v0.1)
|
|
171
|
+
|
|
172
|
+
- No live LLM probing — the hallucination database is a static seed for v0.1.
|
|
173
|
+
- No SaaS dashboard, no auth, no billing, no telemetry to any remote server.
|
|
174
|
+
- No tarpit registry, no defensive package registration.
|
|
175
|
+
- No Cursor / Claude Code / Copilot IDE plugins.
|
|
176
|
+
- No support for crates.io, pkg.go.dev, Maven Central, RubyGems, NuGet —
|
|
177
|
+
Python and JavaScript only.
|
|
178
|
+
- No license scanning, no CVE matching, no SBOM generation.
|
|
179
|
+
- No remote configuration, no SaaS API client.
|
|
180
|
+
|
|
181
|
+
The full v0.2+ roadmap is tracked in the build spec, section 14.
|
|
182
|
+
|
|
183
|
+
## Privacy & trust
|
|
184
|
+
|
|
185
|
+
SlopGuard makes **only** the network calls you opt into (the public registry
|
|
186
|
+
probes against `registry.npmjs.org` and `pypi.org`). No analytics, no
|
|
187
|
+
ping-home, no telemetry. The trust model is the moat: run `--no-network` if
|
|
188
|
+
you want to be sure.
|
|
189
|
+
|
|
190
|
+
## Contributing
|
|
191
|
+
|
|
192
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). PRs welcome — especially curated
|
|
193
|
+
additions to the hallucination database.
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT. Copyright © 2026 SlopGuard. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
slopguard/__init__.py,sha256=N7XKzw5SjJe5DhhJFB7TyZBBbtCWs98fKxuKuKhFE94,169
|
|
2
|
+
slopguard/__main__.py,sha256=-G9dcipk-3ulFo3WkJ3XEedb9dNQwG-gCpOmjzxiYcs,178
|
|
3
|
+
slopguard/cli.py,sha256=jod0Oo-66G6P5GMKVA8G1JN7t54uzxtmFU_R-SMqMVk,10676
|
|
4
|
+
slopguard/config.py,sha256=ImXQe4yiaJEu74rG3kNPqT9RPkeFJvvUpJ9irpjhzLs,4437
|
|
5
|
+
slopguard/models.py,sha256=mr8dhFabHFSFrkd5ykPkQZpRLyN8j85OVhPkPqP-zjM,3126
|
|
6
|
+
slopguard/update.py,sha256=68_ziw1EBw3uxw69ejgxpBnMZxajtCNea2CoApA4bKA,446
|
|
7
|
+
slopguard/data/__init__.py,sha256=YeEejiB_6P1XOQLAvc2cK7bZt6NZRHxS4jLuGmG-WMo,1632
|
|
8
|
+
slopguard/parsers/__init__.py,sha256=HS-tbUIOz3z0mCgDCTz8Du3dv4ncD2V1ao7njVc_wNo,306
|
|
9
|
+
slopguard/parsers/base.py,sha256=Iz4Y5vAXvnK-tdSGGANeJOxgXr5HcaV4D03v5HsIdS4,700
|
|
10
|
+
slopguard/parsers/npm.py,sha256=YgIaHUachzFQOGFqJW1Kc2sA2W_8D7NgbkmN7hpxeEI,5616
|
|
11
|
+
slopguard/parsers/python.py,sha256=Ve-EAJNuoozpVstes_RkNRE9QMyPED9frgm6_kOXKHA,10635
|
|
12
|
+
slopguard/registry/__init__.py,sha256=b-ucqaPqh5G9E14C5hEsyZD7MTchAzeSrysZkVXxnJI,361
|
|
13
|
+
slopguard/registry/base.py,sha256=EHXL0EB5RYIZU5DxaUlxI4crVDnojdzpER3_erTd_3Q,3870
|
|
14
|
+
slopguard/registry/npm.py,sha256=_dOJ4KDvIg3TKQ-thjpwkAu3MubFZilaAHlvFfnZeB8,2857
|
|
15
|
+
slopguard/registry/pypi.py,sha256=txgNcdycePL_RO-6BxddGAPPi0kqsA7bQ71UUeLPWBs,3499
|
|
16
|
+
slopguard/report/__init__.py,sha256=g7l1qlNFip6ZYb4VDGA-ioHwZnQfAfPWoBfIdOwo_U8,234
|
|
17
|
+
slopguard/report/json.py,sha256=MwgXj7-7Ao1RRv-7JL6yaa51ixSJLeTPUiXHzWUOido,522
|
|
18
|
+
slopguard/report/terminal.py,sha256=80SPLI8xhyyBcSJp6xHsgB350qw9RRwFmYAoh0HhU_w,2866
|
|
19
|
+
slopguard/scoring/__init__.py,sha256=1kfQQ_QATivy1lDcdPmeZqbPX2WSlRfUQGJpn1C3g7A,144
|
|
20
|
+
slopguard/scoring/engine.py,sha256=cPfKRlL4y4D74jAxnDcR5xutK5I1khw9mKn_2AUAS0I,8595
|
|
21
|
+
slopguard/scoring/signals.py,sha256=wTwSFDto7l_BIs17780jxLsO9EbK0R3V0BIfSbNwZ3Y,5732
|
|
22
|
+
slopguard/data/hallucinations_seed.json,sha256=5zqSAqAFe6xZhwMl1GYs5EsSKg4DdfLSt2rPBcr81HE,162120
|
|
23
|
+
slopguard/data/popular_packages.json,sha256=kLzd3KX5W2QBd_FM9ZZ03pPx-sB7yUmqykiykbla-wU,35020
|
|
24
|
+
slopguard_cli-0.1.0.dist-info/METADATA,sha256=Uh97auOxGIPtALJ8ukxfnM5Uxe5f-JZ_pDZk2fVyZMo,8070
|
|
25
|
+
slopguard_cli-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
26
|
+
slopguard_cli-0.1.0.dist-info/entry_points.txt,sha256=h_zIuznebScv0IDoby-nTcA2mBiS0rWJnDSKzlwfzTI,48
|
|
27
|
+
slopguard_cli-0.1.0.dist-info/licenses/LICENSE,sha256=C7LCX7SDI6sNelsCL7-nRLejbHbLa4OOJwJcSTO6kL4,1066
|
|
28
|
+
slopguard_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SlopGuard
|
|
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.
|