slopsentry 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.
@@ -0,0 +1,235 @@
1
+ Metadata-Version: 2.5
2
+ Name: slopsentry
3
+ Version: 0.1.0
4
+ Summary: Check packages for slopsquatting before they are installed.
5
+ Project-URL: Homepage, https://github.com/Rajeshr-developer/slopsentry
6
+ Project-URL: Repository, https://github.com/Rajeshr-developer/slopsentry
7
+ Project-URL: Issues, https://github.com/Rajeshr-developer/slopsentry/issues
8
+ Author-email: Rajeshr-developer <rajeshr.developer@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: claude-code,pypi,security,slopsquatting,supply-chain,typosquatting
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Security
23
+ Classifier: Topic :: Software Development :: Build Tools
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: httpx>=0.27
26
+ Requires-Dist: packaging>=24
27
+ Requires-Dist: rich>=13
28
+ Requires-Dist: typer>=0.12
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=8; extra == 'dev'
31
+ Requires-Dist: ruff>=0.6; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # SlopSentry
35
+
36
+ SlopSentry checks packages for **slopsquatting** before you install them: AI-hallucinated
37
+ package names and malicious lookalikes that attackers register to catch them.
38
+
39
+ <!-- TODO: demo GIF of the Claude Code hook blocking a hallucinated package -->
40
+ <!-- ![SlopSentry blocking a hallucinated package in Claude Code](docs/demo.gif) -->
41
+
42
+ > **Alpha (0.1.0):** only Python package installs from PyPI are checked. npm and other
43
+ > ecosystems aren't supported yet.
44
+
45
+ ## Install
46
+
47
+ SlopSentry needs Python 3.10 or newer. Install it as a standalone tool with
48
+ [pipx](https://pipx.pypa.io/):
49
+
50
+ ```sh
51
+ pipx install slopsentry
52
+ ```
53
+
54
+ or run it without installing, using [uv](https://docs.astral.sh/uv/):
55
+
56
+ ```sh
57
+ uvx slopsentry check requests
58
+ ```
59
+
60
+ `pip install slopsentry` works too.
61
+
62
+ For development:
63
+
64
+ ```sh
65
+ python3 -m venv .venv
66
+ .venv/bin/pip install -e ".[dev]"
67
+ ```
68
+
69
+ ## Usage
70
+
71
+ ```sh
72
+ slopsentry check requests
73
+ ```
74
+
75
+ `check` runs every check, adds up the risk points, and gives a verdict:
76
+
77
+ | Check | Flags | Points |
78
+ | --------- | ----------------------------------------------------- | ------ |
79
+ | exists | package not found on PyPI | 100 |
80
+ | age | first release under 90 days ago, or no released files | 40 |
81
+ | age | two or fewer releases | 20 |
82
+ | age | no homepage or repository link | 20 |
83
+ | lookalike | resembles a popular package (see below) | 50 |
84
+ | lookalike | resembles one, but the age check passes cleanly | 15 |
85
+
86
+ | Verdict | Total points | Exit code |
87
+ | ------- | ------------ | --------- |
88
+ | PASS | under 30 | 0 |
89
+ | WARN | 30 to 69 | 0 |
90
+ | BLOCK | 70 or more | 1 |
91
+
92
+ If PyPI can't be reached, the package can't be verified: the verdict is at least WARN and
93
+ the exit code 0, or ERROR with exit code 2 under `--strict`. Empty or invalid input is an
94
+ ERROR with exit code 2. Thresholds live in `slopsentry/scoring.py`.
95
+
96
+ Without `--explain`, `check` prints one summary line plus the reasons for a WARN or
97
+ BLOCK. With `--explain` it prints a table of every check's status, detail and points:
98
+
99
+ ```sh
100
+ slopsentry check --explain requests
101
+ ```
102
+
103
+ ### Lookalike check
104
+
105
+ The lookalike check compares the name with the 5,000 most-downloaded PyPI packages,
106
+ bundled in `slopsentry/data/top_pypi.json`. Popular packages are never flagged, so
107
+ `requests` passes even though it's close to other names. A name is flagged if:
108
+
109
+ - it differs from a popular name only by separators (`django-rest-framework` vs
110
+ `djangorestframework`),
111
+ - it adds or removes `py-`, `python-`, `-py`, `-python` or `-lib` (`python-requests`,
112
+ `pandas-py`), or
113
+ - it's within Damerau-Levenshtein distance 1 of a popular name, or 2 for names longer
114
+ than 10 characters (`reqeusts`, `numpyy`).
115
+
116
+ Two guards cut false positives on legitimate packages:
117
+
118
+ 1. **Short names:** names under 5 characters get no edit-distance matching, since almost
119
+ every short name is one edit from another (`sax`/`six`, `od`/`id`).
120
+ 2. **Package families:** a distance-2 match is ignored when the names differ in exactly
121
+ one whole `-` segment, such as `mypy-boto3-emr` and `mypy-boto3-ec2`.
122
+
123
+ The reason names the package it resembles, e.g. "looks like requests". If the package is
124
+ established (the age check passes cleanly), the match adds only 15 points and reads
125
+ "looks like requests (established package)": shown in `--explain`, but not enough to warn
126
+ by itself. A new or thin lookalike adds 50, so one that is also brand new blocks.
127
+
128
+ To refresh the bundled list (it needs the network, or a downloaded copy of the dataset):
129
+
130
+ ```sh
131
+ .venv/bin/python scripts/update_top_packages.py
132
+ ```
133
+
134
+ `PACKAGE` can be a full requirement: `requests==2.31.0` and `requests[socks]` are
135
+ checked as `requests`. Invalid input is rejected before any request is made.
136
+
137
+ Names are normalised per PEP 503, so `Requests` and `typing_extensions` match
138
+ `requests` and `typing-extensions`.
139
+
140
+ Use `--strict` to treat an unreachable registry as a failure:
141
+
142
+ ```sh
143
+ slopsentry check --strict requests
144
+ ```
145
+
146
+ ## Claude Code hook
147
+
148
+ `slopsentry hook` is a Claude Code `PreToolUse` hook. It reads the hook event on stdin,
149
+ finds install commands in Bash tool calls, scores each package, and answers on stdout:
150
+
151
+ | Worst verdict | Decision |
152
+ | ------------- | ------------------------------------------------ |
153
+ | BLOCK | `deny`, with the package, score and reasons |
154
+ | WARN | `ask`, so you confirm before it runs |
155
+ | PASS | allowed silently |
156
+
157
+ It recognises `pip install` (also `pip3`, `python -m pip`, `python3 -m pip`), `uv add`,
158
+ `uv pip install` and `poetry add`, including in chained commands (`&&`, `||`, `;`, `|`)
159
+ and after `sudo`, `env` or `VAR=value` prefixes. Flags, local paths, URLs, `-e` and `-r`
160
+ arguments are skipped. Commands that use a custom index (`--index-url`, `-i`,
161
+ `--extra-index-url`, `-f`/`--find-links`, uv's `--index`/`--default-index`, poetry's
162
+ `--source`) aren't checked, since their packages may not be on PyPI. In 0.1.0 only
163
+ Python installs are checked; `npm install`, `yarn add` and other ecosystems pass through
164
+ unchecked.
165
+
166
+ The hook fails open: it always exits 0. Commands it can't parse, registry errors (5 second
167
+ timeout) and its own crashes all allow the command. Crashes are appended with a timestamp
168
+ to `~/.slopsentry/hook.log`; add `--debug` to also print them to stderr.
169
+
170
+ ### Setting up the hook
171
+
172
+ 1. Install SlopSentry so `slopsentry` is on your `PATH`:
173
+
174
+ ```sh
175
+ pipx install slopsentry
176
+ slopsentry --help
177
+ ```
178
+
179
+ 2. Add the hook to `.claude/settings.json` in a project, or to `~/.claude/settings.json`
180
+ to cover every project:
181
+
182
+ ```json
183
+ {
184
+ "hooks": {
185
+ "PreToolUse": [
186
+ {
187
+ "matcher": "Bash",
188
+ "hooks": [
189
+ {
190
+ "type": "command",
191
+ "command": "slopsentry hook || true",
192
+ "timeout": 30
193
+ }
194
+ ]
195
+ }
196
+ ]
197
+ }
198
+ }
199
+ ```
200
+
201
+ `|| true` keeps Claude Code working if `slopsentry` isn't installed on a machine
202
+ where the settings file is shared.
203
+
204
+ 3. Restart Claude Code, then check it in `/hooks`. Asking Claude to run
205
+ `pip install reqeusts` should be blocked.
206
+
207
+ If you use uv and don't want to install anything, use `"command": "uvx slopsentry hook || true"`
208
+ instead. The first run downloads SlopSentry, so it's slower.
209
+
210
+ When working on SlopSentry itself, point the hook at your checkout instead:
211
+ `"\"$CLAUDE_PROJECT_DIR\"/.venv/bin/slopsentry hook || true"`.
212
+
213
+ ### Known gaps
214
+
215
+ The hook reads the command text; it doesn't run a shell. These installs get past it:
216
+
217
+ - **`bash -c "pip install x"`** (and `sh -c`, `eval`): the install is inside a string
218
+ argument that isn't parsed.
219
+ - **`$(...)` and backtick subshells**, e.g. `pip install $(cat pkgs.txt)`: the package
220
+ names only exist once the shell runs.
221
+ - **`xargs`**, e.g. `cat pkgs.txt | xargs pip install`: the names arrive on stdin.
222
+ - **`-r requirements.txt`**: requirement files aren't read, so their packages aren't
223
+ checked.
224
+ - **`--extra-index-url` dependency confusion**: commands with an extra index are skipped
225
+ entirely. pip picks the highest version across all indexes, so an attacker can publish
226
+ your private package's name on PyPI with a higher version, and it wins.
227
+
228
+ ## Data attribution
229
+
230
+ `slopsentry/data/top_pypi.json` is derived from
231
+ [Top PyPI Packages](https://hugovk.dev/top-pypi-packages/) by Hugo van Kemenade and
232
+ contributors ([DOI 10.5281/zenodo.23082845](https://doi.org/10.5281/zenodo.23082845)),
233
+ release 2026.10.1, licensed under
234
+ [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). Changes: kept the top 5,000
235
+ names in download order, normalised them per PEP 503, and removed the download counts.
@@ -0,0 +1,17 @@
1
+ slopsentry/__init__.py,sha256=CtawsIAF3GUfWb3VoF9zo_f_XWbX8zhI_EIfptuNBak,98
2
+ slopsentry/cli.py,sha256=0WKdSxu5Ke1oWy1uvtkkniFOGvFRf7ZqCBhRFQnaafE,4145
3
+ slopsentry/hook.py,sha256=mts4T3wSz0UZRT0OHNhOzpP0Sjhn4Du3S3eu5_PvaIU,11671
4
+ slopsentry/registry.py,sha256=WDfqu62KV9TgD15N1tzoOC0cAteF4V8hHcPRhhHc4Kg,2196
5
+ slopsentry/scoring.py,sha256=SYaSzo3jr_eCkAaiKK483eX8s-wnUGQr0YAoYr4OJSY,2220
6
+ slopsentry/spec.py,sha256=4fmdv-yYZb30XVaYQXOYLvweFy-Z-tQx0QOGrCRDMEA,703
7
+ slopsentry/checks/__init__.py,sha256=_0RZrNZFECsKNO6JwP-rVnn8igGtFfJV62ONzDBbX-g,89
8
+ slopsentry/checks/age.py,sha256=s-onWUNtT9C6XBx9PtU7pjKdz486-GCHXesgjBBJXZI,2454
9
+ slopsentry/checks/exists.py,sha256=gIDZB-76Weq_bKwHdjHhDzchR4_KJ5Tn7GXG0INvWtk,493
10
+ slopsentry/checks/lookalike.py,sha256=P9BcLQAdwrMOtOYoiKJMohw64vCHlYclOQrtloZJ7wM,4922
11
+ slopsentry/data/__init__.py,sha256=1ngRWhLp3cYxZ3_p2EYHHimayoCtAoE9sdfywZLBE6o,26
12
+ slopsentry/data/top_pypi.json,sha256=RPEyyXenUSbe3dgS80rnvxwqiufuSKyBqQpxiA2830s,94708
13
+ slopsentry-0.1.0.dist-info/METADATA,sha256=8gFYzFlHLp3x4Z37JuMbFQzY1hhFJrIO35uvI5qDr2I,9269
14
+ slopsentry-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
15
+ slopsentry-0.1.0.dist-info/entry_points.txt,sha256=-KcJn4Zi-Ygc9VUhF4-oCL2VUsAhpKIymCz28RvTqNw,50
16
+ slopsentry-0.1.0.dist-info/licenses/LICENSE,sha256=bz1w0UQuFAWPHdtciZCmm1lKvuvXk3O_rCEGqPlWlXs,1074
17
+ slopsentry-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ slopsentry = slopsentry.cli:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rajeshr-developer
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.