slopsquat-guard 0.4.1__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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Somesh Ghaturle
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,138 @@
1
+ Metadata-Version: 2.4
2
+ Name: slopsquat-guard
3
+ Version: 0.4.1
4
+ Summary: Block hallucinated & typosquatted package installs by AI coding agents
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/somesh-ghaturle/pkg-guard
7
+ Project-URL: Issues, https://github.com/somesh-ghaturle/pkg-guard/issues
8
+ Requires-Python: >=3.9
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Dynamic: license-file
12
+
13
+ # pkg-guard
14
+
15
+ AI coding agents sometimes install packages that don't exist. Attackers register
16
+ those hallucinated names, plus one-letter typos of popular packages, and wait
17
+ ("slopsquatting"). **pkg-guard** is a hook that checks every install command your
18
+ agent runs and blocks the suspicious ones before they execute.
19
+
20
+ ```
21
+ $ pkg-guard check "pip install reqeusts"
22
+ BLOCK reqeusts: does not exist on pypi (hallucinated name?). Did you mean 'requests'?
23
+
24
+ $ pkg-guard check "npm i lodahs"
25
+ BLOCK lodahs: looks like a typosquat of popular package 'lodash'.
26
+ ```
27
+
28
+ The agent sees the reason and fixes its own command.
29
+
30
+ - **Works with** Claude Code, Codex, and Gemini CLI. One script speaks all three hook protocols.
31
+ - **Covers** npm, pnpm, yarn, bun, npx, pip, uv, uvx, poetry, pipx, cargo, gem, and go (`go get`, `go install`, `go run mod@v`).
32
+ - **Single file, Python stdlib only.** The guard adds no dependencies of its own. Read it in five minutes.
33
+
34
+ ## What gets blocked
35
+
36
+ | Signal | Rule |
37
+ |---|---|
38
+ | Doesn't exist | Registry returns 404. Suggests the popular name it resembles. |
39
+ | Too new | First published less than 30 days ago (`PKG_GUARD_MIN_AGE_DAYS`) |
40
+ | Too few users | npm: fewer than 100 weekly downloads. crates: fewer than 100 in 90 days. gems: fewer than 1,000 total. Go (GitHub/GitLab/Bitbucket): fewer than 10 repo stars, since Go publish times come from git commits and can be backdated. |
41
+ | Typosquat | One edit away from a popular package, and young or little-used |
42
+ | Install script (npm) | Latest version runs `preinstall`/`install`/`postinstall` code, and the package is young (< 180 days) or little-used (< 10,000 weekly downloads) |
43
+
44
+ It also catches the usual ways around a naive regex: `bash -lc "..."`, `$(...)`,
45
+ `sudo`/`env` wrappers, `npm isntall`, `npm i x@npm:evil`, `npx -p evil`,
46
+ `uvx --with evil`, and `pip install -r requirements.txt` (nested `-r` included).
47
+
48
+ It also checks file edits (Claude Code `Write`/`Edit`, Codex `apply_patch`, Gemini CLI `write_file`/`replace`): dependencies the agent adds to
49
+ `package.json`, `requirements*.txt`, `pyproject.toml`, `Cargo.toml`, `Gemfile`, or `go.mod`
50
+ are checked before the edit is written, so a later bare `npm install` can't sneak them in.
51
+
52
+ ## Install
53
+
54
+ ### Claude Code (plugin: hook + skill)
55
+
56
+ ```
57
+ /plugin marketplace add somesh-ghaturle/pkg-guard
58
+ /plugin install pkg-guard@pkg-guard
59
+ ```
60
+
61
+ ### Codex
62
+
63
+ ```bash
64
+ git clone https://github.com/somesh-ghaturle/pkg-guard ~/.pkg-guard
65
+ mkdir -p ~/.codex/skills && cp -r ~/.pkg-guard/skills/pkg-guard ~/.codex/skills/
66
+ ```
67
+
68
+ Then add the hook to `~/.codex/config.toml`, or use [`examples/codex-hooks.json`](examples/codex-hooks.json) as `~/.codex/hooks.json`:
69
+
70
+ ```toml
71
+ [[hooks.PreToolUse]]
72
+ matcher = "Bash|apply_patch"
73
+
74
+ [[hooks.PreToolUse.hooks]]
75
+ type = "command"
76
+ command = "python3 ~/.pkg-guard/pkg_guard.py hook"
77
+ timeout = 20
78
+ ```
79
+
80
+ ### Gemini CLI
81
+
82
+ ```bash
83
+ git clone https://github.com/somesh-ghaturle/pkg-guard ~/.pkg-guard
84
+ ```
85
+
86
+ Then merge [`examples/gemini-settings.json`](examples/gemini-settings.json) into `~/.gemini/settings.json`, with the command set to `python3 ~/.pkg-guard/pkg_guard.py hook`. It checks `run_shell_command` plus `write_file`/`replace` edits to manifests.
87
+
88
+ ### Claude Code without the plugin
89
+
90
+ Add [`examples/claude-settings.json`](examples/claude-settings.json) to `~/.claude/settings.json`, using your own path to the script.
91
+
92
+ ### As a CLI
93
+
94
+ ```bash
95
+ pipx install slopsquat-guard # the PyPI name; the command is pkg-guard
96
+ pkg-guard check "npm install some-lib" # exit 1 if blocked
97
+ pkg-guard scan # check every package in ./package-lock.json, uv.lock, ...
98
+ ```
99
+
100
+ The PyPI package is **`slopsquat-guard`**. `pkg-guard` on PyPI is a different, unrelated project.
101
+
102
+ `scan` reads `package-lock.json`, `npm-shrinkwrap.json`, `yarn.lock` (classic and Berry),
103
+ `pnpm-lock.yaml`, `uv.lock`, `poetry.lock`, `Cargo.lock` (the TOML ones need Python 3.11+),
104
+ `Gemfile.lock` and `go.sum`, skips git/path/workspace entries, and
105
+ applies the same rules to every registry package, transitive ones included. Use it once
106
+ when you add pkg-guard to an existing repo, or in CI.
107
+
108
+ ## Configuration
109
+
110
+ | Env var | Effect |
111
+ |---|---|
112
+ | `PKG_GUARD_ALLOW=a,b` | Always allow these packages (internal or private names) |
113
+ | `PKG_GUARD_MIN_AGE_DAYS=30` | Minimum package age |
114
+ | `GOPRIVATE` / `GONOPROXY` | Go modules matching these globs are skipped, like `go` does (env vars only, not `go env -w`) |
115
+ | `XDG_CACHE_HOME` | Packages that pass are cached for 24h in `$XDG_CACHE_HOME/pkg-guard/pkg-guard-ok.json` (default `~/.cache`). Blocks are never cached. Delete the file to recheck everything. |
116
+ | `PKG_GUARD_FAIL_CLOSED=1` | Block when the registry can't be reached. The default is to allow and warn, so offline work keeps going. |
117
+
118
+ ## Limits
119
+
120
+ pkg-guard catches agent **mistakes**: hallucinated names, typos, packages too new
121
+ to trust. It is not a sandbox against an agent that is trying to get around it. A
122
+ prompt-injected agent can still install through a variable (`P=npm; $P i x`), a script
123
+ file, or `python -c "import subprocess..."`. The command parser follows bash's quoting,
124
+ comments, heredocs and `$(...)` rules so ordinary commands can't hide an install by
125
+ accident, but no static check can see every way a shell can run code.
126
+
127
+ It is a heuristic guard, not a sandbox. It checks install commands and manifest edits made through the agent's edit tools. It does not check imports, and it does not see manifests changed by shell commands (`echo evil >> requirements.txt`). `pyproject.toml` and `Cargo.toml` edits are
128
+ only checked on Python 3.11+ (needs `tomllib`). Go modules on vanity domains (`go.uber.org/...`) get no star signal, and their age can be backdated. The pass cache is a convenience, not a security boundary: the hook refuses agent writes to it, but a determined agent could still reach it indirectly. Packages from custom indexes are
129
+ still checked against the public registry. Tarball and git URLs are not checked. Codex `apply_patch` patches are applied with a port of Codex's own matching rules; a patch Codex would reject is denied.
130
+
131
+ ## Development
132
+
133
+ ```bash
134
+ python3 -m unittest -v
135
+ python3 scripts/gen_popular.py # refresh the top-1000 npm/PyPI/crates lists in pkg_guard.py
136
+ ```
137
+
138
+ MIT licensed.
@@ -0,0 +1,126 @@
1
+ # pkg-guard
2
+
3
+ AI coding agents sometimes install packages that don't exist. Attackers register
4
+ those hallucinated names, plus one-letter typos of popular packages, and wait
5
+ ("slopsquatting"). **pkg-guard** is a hook that checks every install command your
6
+ agent runs and blocks the suspicious ones before they execute.
7
+
8
+ ```
9
+ $ pkg-guard check "pip install reqeusts"
10
+ BLOCK reqeusts: does not exist on pypi (hallucinated name?). Did you mean 'requests'?
11
+
12
+ $ pkg-guard check "npm i lodahs"
13
+ BLOCK lodahs: looks like a typosquat of popular package 'lodash'.
14
+ ```
15
+
16
+ The agent sees the reason and fixes its own command.
17
+
18
+ - **Works with** Claude Code, Codex, and Gemini CLI. One script speaks all three hook protocols.
19
+ - **Covers** npm, pnpm, yarn, bun, npx, pip, uv, uvx, poetry, pipx, cargo, gem, and go (`go get`, `go install`, `go run mod@v`).
20
+ - **Single file, Python stdlib only.** The guard adds no dependencies of its own. Read it in five minutes.
21
+
22
+ ## What gets blocked
23
+
24
+ | Signal | Rule |
25
+ |---|---|
26
+ | Doesn't exist | Registry returns 404. Suggests the popular name it resembles. |
27
+ | Too new | First published less than 30 days ago (`PKG_GUARD_MIN_AGE_DAYS`) |
28
+ | Too few users | npm: fewer than 100 weekly downloads. crates: fewer than 100 in 90 days. gems: fewer than 1,000 total. Go (GitHub/GitLab/Bitbucket): fewer than 10 repo stars, since Go publish times come from git commits and can be backdated. |
29
+ | Typosquat | One edit away from a popular package, and young or little-used |
30
+ | Install script (npm) | Latest version runs `preinstall`/`install`/`postinstall` code, and the package is young (< 180 days) or little-used (< 10,000 weekly downloads) |
31
+
32
+ It also catches the usual ways around a naive regex: `bash -lc "..."`, `$(...)`,
33
+ `sudo`/`env` wrappers, `npm isntall`, `npm i x@npm:evil`, `npx -p evil`,
34
+ `uvx --with evil`, and `pip install -r requirements.txt` (nested `-r` included).
35
+
36
+ It also checks file edits (Claude Code `Write`/`Edit`, Codex `apply_patch`, Gemini CLI `write_file`/`replace`): dependencies the agent adds to
37
+ `package.json`, `requirements*.txt`, `pyproject.toml`, `Cargo.toml`, `Gemfile`, or `go.mod`
38
+ are checked before the edit is written, so a later bare `npm install` can't sneak them in.
39
+
40
+ ## Install
41
+
42
+ ### Claude Code (plugin: hook + skill)
43
+
44
+ ```
45
+ /plugin marketplace add somesh-ghaturle/pkg-guard
46
+ /plugin install pkg-guard@pkg-guard
47
+ ```
48
+
49
+ ### Codex
50
+
51
+ ```bash
52
+ git clone https://github.com/somesh-ghaturle/pkg-guard ~/.pkg-guard
53
+ mkdir -p ~/.codex/skills && cp -r ~/.pkg-guard/skills/pkg-guard ~/.codex/skills/
54
+ ```
55
+
56
+ Then add the hook to `~/.codex/config.toml`, or use [`examples/codex-hooks.json`](examples/codex-hooks.json) as `~/.codex/hooks.json`:
57
+
58
+ ```toml
59
+ [[hooks.PreToolUse]]
60
+ matcher = "Bash|apply_patch"
61
+
62
+ [[hooks.PreToolUse.hooks]]
63
+ type = "command"
64
+ command = "python3 ~/.pkg-guard/pkg_guard.py hook"
65
+ timeout = 20
66
+ ```
67
+
68
+ ### Gemini CLI
69
+
70
+ ```bash
71
+ git clone https://github.com/somesh-ghaturle/pkg-guard ~/.pkg-guard
72
+ ```
73
+
74
+ Then merge [`examples/gemini-settings.json`](examples/gemini-settings.json) into `~/.gemini/settings.json`, with the command set to `python3 ~/.pkg-guard/pkg_guard.py hook`. It checks `run_shell_command` plus `write_file`/`replace` edits to manifests.
75
+
76
+ ### Claude Code without the plugin
77
+
78
+ Add [`examples/claude-settings.json`](examples/claude-settings.json) to `~/.claude/settings.json`, using your own path to the script.
79
+
80
+ ### As a CLI
81
+
82
+ ```bash
83
+ pipx install slopsquat-guard # the PyPI name; the command is pkg-guard
84
+ pkg-guard check "npm install some-lib" # exit 1 if blocked
85
+ pkg-guard scan # check every package in ./package-lock.json, uv.lock, ...
86
+ ```
87
+
88
+ The PyPI package is **`slopsquat-guard`**. `pkg-guard` on PyPI is a different, unrelated project.
89
+
90
+ `scan` reads `package-lock.json`, `npm-shrinkwrap.json`, `yarn.lock` (classic and Berry),
91
+ `pnpm-lock.yaml`, `uv.lock`, `poetry.lock`, `Cargo.lock` (the TOML ones need Python 3.11+),
92
+ `Gemfile.lock` and `go.sum`, skips git/path/workspace entries, and
93
+ applies the same rules to every registry package, transitive ones included. Use it once
94
+ when you add pkg-guard to an existing repo, or in CI.
95
+
96
+ ## Configuration
97
+
98
+ | Env var | Effect |
99
+ |---|---|
100
+ | `PKG_GUARD_ALLOW=a,b` | Always allow these packages (internal or private names) |
101
+ | `PKG_GUARD_MIN_AGE_DAYS=30` | Minimum package age |
102
+ | `GOPRIVATE` / `GONOPROXY` | Go modules matching these globs are skipped, like `go` does (env vars only, not `go env -w`) |
103
+ | `XDG_CACHE_HOME` | Packages that pass are cached for 24h in `$XDG_CACHE_HOME/pkg-guard/pkg-guard-ok.json` (default `~/.cache`). Blocks are never cached. Delete the file to recheck everything. |
104
+ | `PKG_GUARD_FAIL_CLOSED=1` | Block when the registry can't be reached. The default is to allow and warn, so offline work keeps going. |
105
+
106
+ ## Limits
107
+
108
+ pkg-guard catches agent **mistakes**: hallucinated names, typos, packages too new
109
+ to trust. It is not a sandbox against an agent that is trying to get around it. A
110
+ prompt-injected agent can still install through a variable (`P=npm; $P i x`), a script
111
+ file, or `python -c "import subprocess..."`. The command parser follows bash's quoting,
112
+ comments, heredocs and `$(...)` rules so ordinary commands can't hide an install by
113
+ accident, but no static check can see every way a shell can run code.
114
+
115
+ It is a heuristic guard, not a sandbox. It checks install commands and manifest edits made through the agent's edit tools. It does not check imports, and it does not see manifests changed by shell commands (`echo evil >> requirements.txt`). `pyproject.toml` and `Cargo.toml` edits are
116
+ only checked on Python 3.11+ (needs `tomllib`). Go modules on vanity domains (`go.uber.org/...`) get no star signal, and their age can be backdated. The pass cache is a convenience, not a security boundary: the hook refuses agent writes to it, but a determined agent could still reach it indirectly. Packages from custom indexes are
117
+ still checked against the public registry. Tarball and git URLs are not checked. Codex `apply_patch` patches are applied with a port of Codex's own matching rules; a patch Codex would reject is denied.
118
+
119
+ ## Development
120
+
121
+ ```bash
122
+ python3 -m unittest -v
123
+ python3 scripts/gen_popular.py # refresh the top-1000 npm/PyPI/crates lists in pkg_guard.py
124
+ ```
125
+
126
+ MIT licensed.