ghostpkg 0.1.0__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.1.0/.github/ISSUE_TEMPLATE/bug.yml +24 -0
- ghostpkg-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- ghostpkg-0.1.0/.github/ISSUE_TEMPLATE/false-positive.yml +40 -0
- ghostpkg-0.1.0/.github/ISSUE_TEMPLATE/missed-package.yml +30 -0
- ghostpkg-0.1.0/.github/workflows/ci.yml +26 -0
- ghostpkg-0.1.0/.gitignore +69 -0
- ghostpkg-0.1.0/CHANGELOG.md +39 -0
- ghostpkg-0.1.0/CONTRIBUTING.md +69 -0
- ghostpkg-0.1.0/LICENSE +21 -0
- ghostpkg-0.1.0/PKG-INFO +392 -0
- ghostpkg-0.1.0/README.en.md +345 -0
- ghostpkg-0.1.0/README.md +201 -0
- ghostpkg-0.1.0/SECURITY.md +47 -0
- ghostpkg-0.1.0/assets/banner.html +53 -0
- ghostpkg-0.1.0/assets/banner.png +0 -0
- ghostpkg-0.1.0/assets/demo.gif +0 -0
- ghostpkg-0.1.0/assets/make_demo.py +129 -0
- ghostpkg-0.1.0/ghostpkg/__init__.py +16 -0
- ghostpkg-0.1.0/ghostpkg/__main__.py +5 -0
- ghostpkg-0.1.0/ghostpkg/assess.py +144 -0
- ghostpkg-0.1.0/ghostpkg/cli.py +204 -0
- ghostpkg-0.1.0/ghostpkg/data.py +399 -0
- ghostpkg-0.1.0/ghostpkg/registries.py +130 -0
- ghostpkg-0.1.0/pyproject.toml +58 -0
- ghostpkg-0.1.0/tests/test_assess.py +101 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Bug
|
|
2
|
+
description: Something crashed or behaved incorrectly
|
|
3
|
+
labels: ["bug"]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: what
|
|
7
|
+
attributes:
|
|
8
|
+
label: What happened
|
|
9
|
+
validations:
|
|
10
|
+
required: true
|
|
11
|
+
- type: textarea
|
|
12
|
+
id: repro
|
|
13
|
+
attributes:
|
|
14
|
+
label: Command you ran
|
|
15
|
+
render: shell
|
|
16
|
+
validations:
|
|
17
|
+
required: true
|
|
18
|
+
- type: input
|
|
19
|
+
id: version
|
|
20
|
+
attributes:
|
|
21
|
+
label: ghostpkg version and OS
|
|
22
|
+
placeholder: "0.1.0, Windows 11, Python 3.12"
|
|
23
|
+
validations:
|
|
24
|
+
required: true
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: False positive
|
|
2
|
+
description: ghostpkg flagged a package that is actually fine
|
|
3
|
+
labels: ["false-positive", "bug"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
This is the most valuable report you can file. A tool that cries wolf on
|
|
9
|
+
real packages gets turned off, and then it protects nothing.
|
|
10
|
+
- type: input
|
|
11
|
+
id: package
|
|
12
|
+
attributes:
|
|
13
|
+
label: Package name
|
|
14
|
+
placeholder: some-real-package
|
|
15
|
+
validations:
|
|
16
|
+
required: true
|
|
17
|
+
- type: dropdown
|
|
18
|
+
id: ecosystem
|
|
19
|
+
attributes:
|
|
20
|
+
label: Ecosystem
|
|
21
|
+
options: [pypi, npm]
|
|
22
|
+
validations:
|
|
23
|
+
required: true
|
|
24
|
+
- type: textarea
|
|
25
|
+
id: output
|
|
26
|
+
attributes:
|
|
27
|
+
label: Output
|
|
28
|
+
description: Paste the full output, ideally from `ghostpkg check <name> --json`.
|
|
29
|
+
render: shell
|
|
30
|
+
validations:
|
|
31
|
+
required: true
|
|
32
|
+
- type: checkboxes
|
|
33
|
+
id: strict
|
|
34
|
+
attributes:
|
|
35
|
+
label: Were you using --strict?
|
|
36
|
+
description: >
|
|
37
|
+
--strict promotes warnings to blocks and is documented as flagging
|
|
38
|
+
legitimate new packages. That behaviour is expected.
|
|
39
|
+
options:
|
|
40
|
+
- label: I was NOT using --strict
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Missed detection
|
|
2
|
+
description: ghostpkg passed a package it should have caught
|
|
3
|
+
labels: ["missed-detection"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Note the known limitation first: a hallucinated name that an attacker has
|
|
9
|
+
**already registered** passes the existence check by design. That case is
|
|
10
|
+
tracked as the project's main open problem — but concrete examples are
|
|
11
|
+
still very useful, so please file it.
|
|
12
|
+
- type: input
|
|
13
|
+
id: package
|
|
14
|
+
attributes:
|
|
15
|
+
label: Package name
|
|
16
|
+
validations:
|
|
17
|
+
required: true
|
|
18
|
+
- type: dropdown
|
|
19
|
+
id: ecosystem
|
|
20
|
+
attributes:
|
|
21
|
+
label: Ecosystem
|
|
22
|
+
options: [pypi, npm]
|
|
23
|
+
validations:
|
|
24
|
+
required: true
|
|
25
|
+
- type: textarea
|
|
26
|
+
id: why
|
|
27
|
+
attributes:
|
|
28
|
+
label: Why should it have been caught?
|
|
29
|
+
validations:
|
|
30
|
+
required: true
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ${{ matrix.os }}
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
15
|
+
python-version: ["3.9", "3.12", "3.13"]
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
- uses: actions/setup-python@v7
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
- run: pip install -e ".[dev]"
|
|
22
|
+
- run: pytest -q
|
|
23
|
+
- name: CLI smoke test
|
|
24
|
+
run: |
|
|
25
|
+
ghostpkg --version
|
|
26
|
+
ghostpkg check requests
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# --- Secrets: never commit credentials ---
|
|
2
|
+
# Token/credential files
|
|
3
|
+
*.token
|
|
4
|
+
*.secret
|
|
5
|
+
*.key
|
|
6
|
+
*.pem
|
|
7
|
+
*.pfx
|
|
8
|
+
*.p12
|
|
9
|
+
.env
|
|
10
|
+
.env.*
|
|
11
|
+
!.env.example
|
|
12
|
+
secrets.*
|
|
13
|
+
credentials.*
|
|
14
|
+
auth.json
|
|
15
|
+
hosts.yml
|
|
16
|
+
.netrc
|
|
17
|
+
_netrc
|
|
18
|
+
|
|
19
|
+
# gh / git credential stores (should never be here, blocked defensively)
|
|
20
|
+
.config/gh/
|
|
21
|
+
.gh/
|
|
22
|
+
.git-credentials
|
|
23
|
+
|
|
24
|
+
# --- Windows ---
|
|
25
|
+
Thumbs.db
|
|
26
|
+
ehthumbs.db
|
|
27
|
+
Desktop.ini
|
|
28
|
+
$RECYCLE.BIN/
|
|
29
|
+
*.lnk
|
|
30
|
+
|
|
31
|
+
# --- Editors / IDE ---
|
|
32
|
+
.vscode/
|
|
33
|
+
.idea/
|
|
34
|
+
*.swp
|
|
35
|
+
*.swo
|
|
36
|
+
*~
|
|
37
|
+
|
|
38
|
+
# --- Build / temp ---
|
|
39
|
+
dist/
|
|
40
|
+
build/
|
|
41
|
+
out/
|
|
42
|
+
tmp/
|
|
43
|
+
temp/
|
|
44
|
+
*.log
|
|
45
|
+
*.tmp
|
|
46
|
+
*.bak
|
|
47
|
+
|
|
48
|
+
# --- Language ecosystems ---
|
|
49
|
+
node_modules/
|
|
50
|
+
__pycache__/
|
|
51
|
+
*.py[cod]
|
|
52
|
+
.venv/
|
|
53
|
+
venv/
|
|
54
|
+
.pytest_cache/
|
|
55
|
+
target/
|
|
56
|
+
|
|
57
|
+
# --- Local scratch (never published) ---
|
|
58
|
+
scratch/
|
|
59
|
+
.local/
|
|
60
|
+
|
|
61
|
+
# Internal planning document -- deliberately not published.
|
|
62
|
+
# It is the portfolio strategy doc, not part of this tool.
|
|
63
|
+
PLAN.md
|
|
64
|
+
|
|
65
|
+
# Playwright MCP scratch output
|
|
66
|
+
.playwright-mcp/
|
|
67
|
+
|
|
68
|
+
# Banner art-direction options (working files, not shipped)
|
|
69
|
+
banner-options/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
|
|
5
|
+
this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-09-01
|
|
10
|
+
|
|
11
|
+
First release.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- `ghostpkg check` — check one or more package names against PyPI or npm.
|
|
15
|
+
- `ghostpkg scan` — check every dependency in `requirements.txt` or `package.json`.
|
|
16
|
+
- Verdicts: `BLOCK` for names absent from the registry, `WARN` for weak signals
|
|
17
|
+
(recent publication, single release, missing repository link, small edit
|
|
18
|
+
distance to a popular name), `OK` otherwise.
|
|
19
|
+
- `--strict` to promote warnings to blocks, `--json` for machine-readable output,
|
|
20
|
+
`-q/--quiet` to hide passing packages.
|
|
21
|
+
- Exit codes: `0` nothing blocked, `1` something blocked, `2` usage or registry
|
|
22
|
+
error.
|
|
23
|
+
- Bundled list of the 2,000 most-downloaded PyPI projects, used only for typo
|
|
24
|
+
distance.
|
|
25
|
+
|
|
26
|
+
### Notes on the design
|
|
27
|
+
- The default profile blocks on **non-existence only**. An earlier version scored
|
|
28
|
+
packages and blocked anything suspicious; measured against the live feed of
|
|
29
|
+
newly published PyPI packages, it flagged 100% of legitimate same-day
|
|
30
|
+
publications. Age cannot separate a malicious registration from an honest new
|
|
31
|
+
release, so softer signals are warnings.
|
|
32
|
+
- The typo-distance budget scales with name length. A flat budget flagged `flask`,
|
|
33
|
+
`click` and `black` as typos of one another.
|
|
34
|
+
- Zero runtime dependencies, by policy.
|
|
35
|
+
- No corpus of hallucinated package names is shipped, following the decision of
|
|
36
|
+
the USENIX'25 authors not to publish theirs.
|
|
37
|
+
|
|
38
|
+
[Unreleased]: https://github.com/M1rwana12/ghostpkg/compare/v0.1.0...HEAD
|
|
39
|
+
[0.1.0]: https://github.com/M1rwana12/ghostpkg/releases/tag/v0.1.0
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Contributing to ghostpkg
|
|
2
|
+
|
|
3
|
+
Thanks for looking. This is a small, deliberately narrow tool, and the bar for
|
|
4
|
+
changes is mostly about keeping it that way.
|
|
5
|
+
|
|
6
|
+
## Getting set up
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
git clone https://github.com/M1rwana12/ghostpkg
|
|
10
|
+
cd ghostpkg
|
|
11
|
+
pip install -e ".[dev]"
|
|
12
|
+
pytest
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The test suite constructs `PackageFacts` directly and never touches the network,
|
|
16
|
+
so it runs offline and cannot break because a real package changed.
|
|
17
|
+
|
|
18
|
+
## The two rules that shape this project
|
|
19
|
+
|
|
20
|
+
**1. No runtime dependencies.** A supply-chain security tool that installs a
|
|
21
|
+
dependency tree of its own undermines its own argument. Standard library only.
|
|
22
|
+
A pull request that adds a runtime dependency will be declined regardless of how
|
|
23
|
+
useful the dependency is.
|
|
24
|
+
|
|
25
|
+
**2. A false positive is worse than a miss.** People run this in front of every
|
|
26
|
+
install. If it cries wolf on a legitimate package, they turn it off, and then it
|
|
27
|
+
protects nothing.
|
|
28
|
+
|
|
29
|
+
That second rule is not a preference, it's a measurement. The first version of
|
|
30
|
+
this tool scored packages on age, release count and missing repository links,
|
|
31
|
+
then blocked anything suspicious. Tested against the live feed of newly published
|
|
32
|
+
PyPI packages, it flagged **100% of them**. A malicious slopsquat registered
|
|
33
|
+
three days ago and an honest new library published three days ago are the same
|
|
34
|
+
package from the outside.
|
|
35
|
+
|
|
36
|
+
So: the default profile blocks on non-existence only. If you want to propose a
|
|
37
|
+
new blocking signal, bring evidence that it does not fire on legitimate packages.
|
|
38
|
+
|
|
39
|
+
## Especially welcome
|
|
40
|
+
|
|
41
|
+
- **False positive reports.** If `ghostpkg` flagged something real, that's a bug
|
|
42
|
+
and it's the most valuable report you can file.
|
|
43
|
+
- **The open problem:** detecting a hallucinated name that an attacker has
|
|
44
|
+
*already registered*. The existence check cannot see it. If you have an idea
|
|
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.
|
|
48
|
+
- More ecosystems: crates.io, RubyGems, Go modules.
|
|
49
|
+
|
|
50
|
+
## Please don't
|
|
51
|
+
|
|
52
|
+
- **Do not add a corpus of hallucinated package names to this repository.** The
|
|
53
|
+
authors of the USENIX'25 study withheld theirs deliberately, because such a
|
|
54
|
+
list is a ready-made target list for attackers pre-registering those names.
|
|
55
|
+
That reasoning applies here. This project checks names live and ships no corpus.
|
|
56
|
+
- Don't broaden the scope into code scanning or malware detection. Other tools do
|
|
57
|
+
that well.
|
|
58
|
+
|
|
59
|
+
## Style
|
|
60
|
+
|
|
61
|
+
Match the surrounding code. Type hints on public functions, `from __future__
|
|
62
|
+
import annotations` at the top of each module (the package supports Python 3.9).
|
|
63
|
+
Comments explain *why*, not *what* — several comments in `assess.py` record the
|
|
64
|
+
measurement behind a decision, and that is the kind worth writing.
|
|
65
|
+
|
|
66
|
+
## Pull requests
|
|
67
|
+
|
|
68
|
+
Keep them focused. Add a test. CI runs on Linux, macOS and Windows across Python
|
|
69
|
+
3.9, 3.12 and 3.13, and it needs to be green.
|
ghostpkg-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 m1rwana12
|
|
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.
|