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.
Files changed (63) hide show
  1. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.pre-commit-hooks.yaml +1 -1
  2. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/CHANGELOG.md +35 -1
  3. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/CONTRIBUTING.md +3 -2
  4. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/PKG-INFO +8 -4
  5. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/README.en.md +7 -3
  6. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/README.md +8 -4
  7. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/action.yml +5 -1
  8. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/__init__.py +1 -1
  9. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/pyproject.toml +1 -1
  10. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  11. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  12. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/ISSUE_TEMPLATE/false-positive.yml +0 -0
  13. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/ISSUE_TEMPLATE/missed-package.yml +0 -0
  14. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/ghostpkg-ignore.json +0 -0
  15. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/workflows/ci.yml +0 -0
  16. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/workflows/field.yml +0 -0
  17. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.github/workflows/publish.yml +0 -0
  18. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/.gitignore +0 -0
  19. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/2.0 +0 -0
  20. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/LICENSE +0 -0
  21. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/SECURITY.md +0 -0
  22. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/assets/banner.html +0 -0
  23. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/assets/banner.png +0 -0
  24. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/assets/demo.gif +0 -0
  25. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/assets/make_demo.py +0 -0
  26. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/__main__.py +0 -0
  27. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/assess.py +0 -0
  28. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/cache.py +0 -0
  29. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/cli.py +0 -0
  30. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/data.py +0 -0
  31. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/discover.py +0 -0
  32. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/inspection.py +0 -0
  33. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/jslocks.py +0 -0
  34. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/manifests.py +0 -0
  35. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/policy.py +0 -0
  36. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/prose.py +0 -0
  37. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/registries.py +0 -0
  38. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/report.py +0 -0
  39. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/rules.py +0 -0
  40. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/ghostpkg/scanner.py +0 -0
  41. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/scripts/fieldtest.py +0 -0
  42. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_action.py +0 -0
  43. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_assess.py +0 -0
  44. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_cache.py +0 -0
  45. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_discover.py +0 -0
  46. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_field_findings.py +0 -0
  47. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_hostile_input.py +0 -0
  48. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_inspection.py +0 -0
  49. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_jslocks.py +0 -0
  50. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_lockfiles.py +0 -0
  51. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_manifests.py +0 -0
  52. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_mutation_gaps.py +0 -0
  53. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_not_a_package.py +0 -0
  54. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_policy.py +0 -0
  55. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_prose.py +0 -0
  56. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_registries.py +0 -0
  57. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_regressions.py +0 -0
  58. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_report.py +0 -0
  59. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_review_findings.py +0 -0
  60. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_stated_sources.py +0 -0
  61. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_trusted_scopes.py +0 -0
  62. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_versions.py +0 -0
  63. {ghostpkg-0.24.0 → ghostpkg-0.24.2}/tests/test_withdrawn.py +0 -0
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # repos:
4
4
  # - repo: https://github.com/M1rwana12/ghostpkg
5
- # rev: v0.19.0
5
+ # rev: v0.24.0
6
6
  # hooks:
7
7
  # - id: ghostpkg
8
8
  #
@@ -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.0...HEAD
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
- - npm coverage. The popular-name list used for typo distance is PyPI-derived, so
47
- npm typosquat detection is weaker than it should be.
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.0
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 held for only an hour**, because a free name can
282
- be registered at any moment and that is the entire attack. Young packages are
283
- held six hours, established ones a day.
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 held for only an hour**, because a free name can
234
- be registered at any moment and that is the entire attack. Young packages are
235
- held six hours, established ones a day.
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
- Результати запитів кешуються на диск: повторна перевірка манифесту зі 150
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: ghostpkg
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
 
@@ -1,6 +1,6 @@
1
1
  """ghostpkg -- catch package names that do not exist before you install them."""
2
2
 
3
- __version__ = "0.24.0"
3
+ __version__ = "0.24.2"
4
4
 
5
5
  from .assess import Finding, Verdict, assess
6
6
  from .registries import PackageFacts, RegistryError, fetch
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ghostpkg"
7
- version = "0.24.0"
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