ghostpkg 0.24.0__tar.gz → 0.24.2__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.
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.pre-commit-hooks.yaml +1 -1
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/CHANGELOG.md +35 -1
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/CONTRIBUTING.md +3 -2
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/PKG-INFO +8 -4
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/README.en.md +7 -3
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/README.md +8 -4
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/action.yml +5 -1
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/__init__.py +1 -1
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/pyproject.toml +1 -1
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/ISSUE_TEMPLATE/false-positive.yml +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/ISSUE_TEMPLATE/missed-package.yml +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/ghostpkg-ignore.json +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/workflows/ci.yml +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/workflows/field.yml +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/workflows/publish.yml +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.gitignore +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/2.0 +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/LICENSE +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/SECURITY.md +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/assets/banner.html +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/assets/banner.png +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/assets/demo.gif +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/assets/make_demo.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/__main__.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/assess.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/cache.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/cli.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/data.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/discover.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/inspection.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/jslocks.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/manifests.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/policy.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/prose.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/registries.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/report.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/rules.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/scanner.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/scripts/fieldtest.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_action.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_assess.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_cache.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_discover.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_field_findings.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_hostile_input.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_inspection.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_jslocks.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_lockfiles.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_manifests.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_mutation_gaps.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_not_a_package.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_policy.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_prose.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_registries.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_regressions.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_report.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_review_findings.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_stated_sources.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_trusted_scopes.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_versions.py +0 -0
- {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_withdrawn.py +0 -0
|
@@ -6,6 +6,38 @@ this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.24.2] - 2026-09-04
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- The action is named **ghostpkg - hallucinated package check** in its
|
|
13
|
+
`action.yml`. The GitHub Marketplace requires a name unique across every
|
|
14
|
+
action, user and organisation on GitHub, and `ghostpkg` alone is taken by an
|
|
15
|
+
existing account. This is the display name in the Marketplace only -- the
|
|
16
|
+
repository, the PyPI package and `uses: M1rwana12/ghostpkg@v0.24.2` are all
|
|
17
|
+
unchanged.
|
|
18
|
+
|
|
19
|
+
## [0.24.1] - 2026-09-04
|
|
20
|
+
|
|
21
|
+
### Documentation
|
|
22
|
+
Three claims that outlived the behaviour they described, found by re-reading
|
|
23
|
+
both READMEs against a live run before publishing the Action.
|
|
24
|
+
|
|
25
|
+
- **The cache paragraph still said "does not exist is held for only an hour"**
|
|
26
|
+
in both languages, and contradicted itself three hundred lines later with
|
|
27
|
+
"negatives are never cached". The second is true. The first described the
|
|
28
|
+
precise failure this tool exists to avoid, and an earlier patch had missed
|
|
29
|
+
the wording. It now also explains why the rule runs in both directions: a
|
|
30
|
+
free name can be registered at any moment, and a name published a moment ago
|
|
31
|
+
404s briefly because PyPI's feed announces it before the JSON API serves it.
|
|
32
|
+
- `CONTRIBUTING.md` still described the popular-name list as PyPI-derived and
|
|
33
|
+
npm typo detection as weak because of it. There have been 2,000 names per
|
|
34
|
+
ecosystem for some time.
|
|
35
|
+
- The usage comment in `.pre-commit-hooks.yaml` pinned `v0.19.0`.
|
|
36
|
+
|
|
37
|
+
Verified live rather than by reading: every flag in `--help` appears in both
|
|
38
|
+
options tables, the documented exit codes match, `--format github` emits the
|
|
39
|
+
annotation exactly as shown, and the `--json` envelope matches its example.
|
|
40
|
+
|
|
9
41
|
## [0.24.0] - 2026-09-04
|
|
10
42
|
|
|
11
43
|
The gate now runs in CI, and it scanned **88,904 packages across sixteen
|
|
@@ -983,7 +1015,9 @@ First release.
|
|
|
983
1015
|
- No corpus of hallucinated package names is shipped, following the decision of
|
|
984
1016
|
the USENIX'25 authors not to publish theirs.
|
|
985
1017
|
|
|
986
|
-
[Unreleased]: https://github.com/M1rwana12/ghostpkg/compare/v0.24.
|
|
1018
|
+
[Unreleased]: https://github.com/M1rwana12/ghostpkg/compare/v0.24.2...HEAD
|
|
1019
|
+
[0.24.2]: https://github.com/M1rwana12/ghostpkg/releases/tag/v0.24.2
|
|
1020
|
+
[0.24.1]: https://github.com/M1rwana12/ghostpkg/releases/tag/v0.24.1
|
|
987
1021
|
[0.24.0]: https://github.com/M1rwana12/ghostpkg/releases/tag/v0.24.0
|
|
988
1022
|
[0.23.0]: https://github.com/M1rwana12/ghostpkg/releases/tag/v0.23.0
|
|
989
1023
|
[0.22.0]: https://github.com/M1rwana12/ghostpkg/releases/tag/v0.22.0
|
|
@@ -43,8 +43,9 @@ new blocking signal, bring evidence that it does not fire on legitimate packages
|
|
|
43
43
|
- **The open problem:** detecting a hallucinated name that an attacker has
|
|
44
44
|
*already registered*. The existence check cannot see it. If you have an idea
|
|
45
45
|
that does not collapse into "block everything new", open an issue.
|
|
46
|
-
-
|
|
47
|
-
|
|
46
|
+
- Wider popular-name lists. There are 2,000 names per ecosystem now and each
|
|
47
|
+
name is compared against its own registry's list, so `recat`, `lodahs` and
|
|
48
|
+
`webpakc` are caught -- but a squat on anything outside the top 2,000 is not.
|
|
48
49
|
- More ecosystems: crates.io, RubyGems, Go modules.
|
|
49
50
|
|
|
50
51
|
## Please don't
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ghostpkg
|
|
3
|
-
Version: 0.24.
|
|
3
|
+
Version: 0.24.2
|
|
4
4
|
Summary: Catch package names that do not exist before you install them
|
|
5
5
|
Project-URL: Homepage, https://github.com/m1rwana12/ghostpkg
|
|
6
6
|
Project-URL: Issues, https://github.com/m1rwana12/ghostpkg/issues
|
|
@@ -278,9 +278,13 @@ Lookups are cached on disk, so re-scanning a 150-package manifest takes 0.4s
|
|
|
278
278
|
instead of 4.7s.
|
|
279
279
|
|
|
280
280
|
Time-to-live depends on the answer, and the negative case is the one that
|
|
281
|
-
matters: **"does not exist" is
|
|
282
|
-
|
|
283
|
-
|
|
281
|
+
matters: **"does not exist" is never cached at all.** That answer is the only
|
|
282
|
+
one that blocks, so it has to be fresh in both directions -- a free name can be
|
|
283
|
+
registered at any moment, which is the entire attack, and a name published a
|
|
284
|
+
moment ago can 404 briefly because PyPI's feed announces it before the JSON API
|
|
285
|
+
serves it. Caching that for an hour produced a real false block on a live
|
|
286
|
+
package. Young packages are held six hours, established ones a day, and a
|
|
287
|
+
pinned version is re-checked against a fresh list before it is ever blocked.
|
|
284
288
|
|
|
285
289
|
```bash
|
|
286
290
|
ghostpkg scan requirements.txt --no-cache # bypass it
|
|
@@ -230,9 +230,13 @@ Lookups are cached on disk, so re-scanning a 150-package manifest takes 0.4s
|
|
|
230
230
|
instead of 4.7s.
|
|
231
231
|
|
|
232
232
|
Time-to-live depends on the answer, and the negative case is the one that
|
|
233
|
-
matters: **"does not exist" is
|
|
234
|
-
|
|
235
|
-
|
|
233
|
+
matters: **"does not exist" is never cached at all.** That answer is the only
|
|
234
|
+
one that blocks, so it has to be fresh in both directions -- a free name can be
|
|
235
|
+
registered at any moment, which is the entire attack, and a name published a
|
|
236
|
+
moment ago can 404 briefly because PyPI's feed announces it before the JSON API
|
|
237
|
+
serves it. Caching that for an hour produced a real false block on a live
|
|
238
|
+
package. Young packages are held six hours, established ones a day, and a
|
|
239
|
+
pinned version is re-checked against a fresh list before it is ever blocked.
|
|
236
240
|
|
|
237
241
|
```bash
|
|
238
242
|
ghostpkg scan requirements.txt --no-cache # bypass it
|
|
@@ -91,10 +91,14 @@ ghostpkg check somepkg --json
|
|
|
91
91
|
ghostpkg clear-cache
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
Результати запитів кешуються на диск: повторна перевірка
|
|
95
|
-
залежностей займає 0,4 с замість 4,7 с. **Відповідь «пакета не існує»
|
|
96
|
-
|
|
97
|
-
миті, і саме в цьому
|
|
94
|
+
Результати запитів кешуються на диск: повторна перевірка маніфесту зі 150
|
|
95
|
+
залежностей займає 0,4 с замість 4,7 с. **Відповідь «пакета не існує» не
|
|
96
|
+
кешується взагалі** — це єдина відповідь, яка блокує, тож вона має бути свіжою
|
|
97
|
+
в обидва боки. Вільне ім'я можуть зареєструвати будь-якої миті, і саме в цьому
|
|
98
|
+
вся атака; а щойно опублікований пакет якийсь час віддає 404, бо стрічка PyPI
|
|
99
|
+
оголошує його раніше, ніж його починає віддавати JSON API. Кешування цієї
|
|
100
|
+
відповіді на годину вже дало справжнє хибне блокування живого пакета.
|
|
101
|
+
Закріплена версія теж перевіряється за свіжим списком, перш ніж заблокувати.
|
|
98
102
|
|
|
99
103
|
| Прапорець | Призначення |
|
|
100
104
|
|---|---|
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
name
|
|
1
|
+
# The Marketplace requires a name unique across every action, user and
|
|
2
|
+
# organisation on GitHub, and `ghostpkg` is taken by an existing account. This
|
|
3
|
+
# is the display name there only -- the repository, the PyPI package and
|
|
4
|
+
# `uses: M1rwana12/ghostpkg@v...` are all unchanged.
|
|
5
|
+
name: ghostpkg - hallucinated package check
|
|
2
6
|
description: Catch package names that do not exist before you install them
|
|
3
7
|
author: m1rwana12
|
|
4
8
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ghostpkg"
|
|
7
|
-
version = "0.24.
|
|
7
|
+
version = "0.24.2"
|
|
8
8
|
description = "Catch package names that do not exist before you install them"
|
|
9
9
|
readme = "README.en.md" # English page for PyPI; the repo main page is Ukrainian
|
|
10
10
|
requires-python = ">=3.9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|