ghostpkg 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.
- ghostpkg/__init__.py +16 -0
- ghostpkg/__main__.py +5 -0
- ghostpkg/assess.py +144 -0
- ghostpkg/cli.py +204 -0
- ghostpkg/data.py +399 -0
- ghostpkg/registries.py +130 -0
- ghostpkg-0.1.0.dist-info/METADATA +392 -0
- ghostpkg-0.1.0.dist-info/RECORD +11 -0
- ghostpkg-0.1.0.dist-info/WHEEL +4 -0
- ghostpkg-0.1.0.dist-info/entry_points.txt +2 -0
- ghostpkg-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ghostpkg
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Catch package names that do not exist before you install them
|
|
5
|
+
Project-URL: Homepage, https://github.com/m1rwana12/ghostpkg
|
|
6
|
+
Project-URL: Issues, https://github.com/m1rwana12/ghostpkg/issues
|
|
7
|
+
Author-email: m1rwana12 <senja3209@icloud.com>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 m1rwana12
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: ai,dependencies,hallucination,llm,npm,pypi,security,slopsquatting,supply-chain,typosquatting
|
|
31
|
+
Classifier: Development Status :: 4 - Beta
|
|
32
|
+
Classifier: Environment :: Console
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
+
Classifier: Topic :: Security
|
|
42
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
43
|
+
Requires-Python: >=3.9
|
|
44
|
+
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
|
|
48
|
+
<div align="center">
|
|
49
|
+
|
|
50
|
+
<img src="https://raw.githubusercontent.com/M1rwana12/ghostpkg/main/assets/banner.png" alt="ghostpkg" width="100%">
|
|
51
|
+
|
|
52
|
+
<h1>ghostpkg</h1>
|
|
53
|
+
|
|
54
|
+
**Stops you installing packages that don't exist.**
|
|
55
|
+
|
|
56
|
+
Language models invent library names. Attackers register those names in advance.
|
|
57
|
+
`ghostpkg` checks the name against the real registry — before `pip` or `npm` downloads anything.
|
|
58
|
+
|
|
59
|
+
**English** · [Українська](https://github.com/M1rwana12/ghostpkg/blob/main/README.md)
|
|
60
|
+
|
|
61
|
+
[](https://github.com/M1rwana12/ghostpkg/actions/workflows/ci.yml)
|
|
62
|
+
[](https://pypi.org/project/ghostpkg/)
|
|
63
|
+
[](https://pypi.org/project/ghostpkg/)
|
|
64
|
+
[](https://github.com/M1rwana12/ghostpkg/blob/main/pyproject.toml)
|
|
65
|
+
[](https://github.com/M1rwana12/ghostpkg/blob/main/LICENSE)
|
|
66
|
+
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Contents
|
|
72
|
+
|
|
73
|
+
- [The problem](#the-problem)
|
|
74
|
+
- [What ghostpkg does](#what-ghostpkg-does)
|
|
75
|
+
- [Install](#install)
|
|
76
|
+
- [Usage](#usage)
|
|
77
|
+
- [Every signal it checks](#every-signal-it-checks)
|
|
78
|
+
- [Why it doesn't block on "suspicious"](#why-it-doesnt-block-on-suspicious)
|
|
79
|
+
- [Where it fits in your workflow](#where-it-fits-in-your-workflow)
|
|
80
|
+
- [How it works internally](#how-it-works-internally)
|
|
81
|
+
- [Comparison](#comparison)
|
|
82
|
+
- [Honest limitations](#honest-limitations)
|
|
83
|
+
- [Prior art and credit](#prior-art-and-credit)
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## The problem
|
|
88
|
+
|
|
89
|
+
A study of ~200,000 code-generation prompts found **205,474 unique hallucinated
|
|
90
|
+
package names**. The dangerous part isn't the count — it's the consistency:
|
|
91
|
+
|
|
92
|
+
> **43% of those hallucinations reappeared in all ten reruns of the same prompt.**
|
|
93
|
+
|
|
94
|
+
That makes them **predictable**. An attacker can work out in advance which name a
|
|
95
|
+
model will invent, register it on PyPI or npm, and wait. By the time your agent
|
|
96
|
+
confidently writes `pip install fastapi-middleware`, the package **is already there**.
|
|
97
|
+
|
|
98
|
+
The attack has a name: **slopsquatting**.
|
|
99
|
+
|
|
100
|
+
This isn't theoretical. Malicious packages exploiting exactly this pattern have
|
|
101
|
+
been found with tens of thousands of downloads, and the Cloud Security Alliance
|
|
102
|
+
published a formal research note on it in April 2026.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## What ghostpkg does
|
|
107
|
+
|
|
108
|
+
It answers one question, precisely: **does this package actually exist, and if so,
|
|
109
|
+
does anything about it look wrong?**
|
|
110
|
+
|
|
111
|
+
<div align="center">
|
|
112
|
+
<img src="https://raw.githubusercontent.com/M1rwana12/ghostpkg/main/assets/demo.gif" alt="ghostpkg blocking a package that does not exist" width="100%">
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
The exit code is `1` when anything is blocked, so it drops straight into CI or a
|
|
116
|
+
pre-install hook with no glue code.
|
|
117
|
+
|
|
118
|
+
**What it is:** a fast, narrow, dependency-free gate you can put in front of every
|
|
119
|
+
install.
|
|
120
|
+
|
|
121
|
+
**What it is not:** a code scanner, a malware detector, or a replacement for an SCA
|
|
122
|
+
product. It does one job.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Install
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pip install ghostpkg
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Other ways:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
uvx ghostpkg check requests # run without installing
|
|
136
|
+
pipx install ghostpkg # isolated, global command
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
> [!NOTE]
|
|
140
|
+
> **Zero dependencies.** Python standard library only.
|
|
141
|
+
> A supply-chain security tool that drags in a dependency tree of its own is a
|
|
142
|
+
> questionable proposition. This one has none.
|
|
143
|
+
|
|
144
|
+
Requires Python 3.9+. Tested on Linux, macOS and Windows.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Usage
|
|
149
|
+
|
|
150
|
+
### Check names directly
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
ghostpkg check fastapi-middleware pandas-utils
|
|
154
|
+
ghostpkg check react-router-dom-utils -e npm
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Scan a manifest
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
ghostpkg scan requirements.txt
|
|
161
|
+
ghostpkg scan package.json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`requirements.txt` parsing skips comments, flags, VCS URLs and direct links, and
|
|
165
|
+
takes the bare project name off each requirement line. `package.json` reads
|
|
166
|
+
`dependencies`, `devDependencies` and `optionalDependencies`.
|
|
167
|
+
|
|
168
|
+
### Options
|
|
169
|
+
|
|
170
|
+
| Flag | Purpose |
|
|
171
|
+
|---|---|
|
|
172
|
+
| `-e`, `--ecosystem` | `pypi` (default) or `npm` |
|
|
173
|
+
| `--strict` | Promote warnings to blocks |
|
|
174
|
+
| `--json` | Machine-readable output for scripts and CI |
|
|
175
|
+
| `-q`, `--quiet` | Hide packages that passed |
|
|
176
|
+
| `--version` | Print the version |
|
|
177
|
+
|
|
178
|
+
### Exit codes
|
|
179
|
+
|
|
180
|
+
| Code | Meaning |
|
|
181
|
+
|---|---|
|
|
182
|
+
| `0` | Nothing blocked (warnings may still be present) |
|
|
183
|
+
| `1` | At least one package blocked |
|
|
184
|
+
| `2` | Usage error, unreadable manifest, or the registry was unreachable |
|
|
185
|
+
|
|
186
|
+
### JSON output
|
|
187
|
+
|
|
188
|
+
```console
|
|
189
|
+
$ ghostpkg check somepkgthatisnotreal9911 --json
|
|
190
|
+
[
|
|
191
|
+
{
|
|
192
|
+
"name": "somepkgthatisnotreal9911",
|
|
193
|
+
"ecosystem": "pypi",
|
|
194
|
+
"verdict": "BLOCK",
|
|
195
|
+
"reasons": [
|
|
196
|
+
"does not exist on pypi"
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Every signal it checks
|
|
205
|
+
|
|
206
|
+
| Signal | Verdict | Why |
|
|
207
|
+
|---|---|---|
|
|
208
|
+
| Not in the registry | 🔴 **Blocked** | The name is a ghost. There is nothing to install, and this is exactly what a hallucination looks like. |
|
|
209
|
+
| First published < 90 days ago | 🟡 Warning | Attackers register fast. So do honest authors — hence a warning, not a block. |
|
|
210
|
+
| First published < 1 year ago | 🟡 Warning | Weaker version of the same signal. |
|
|
211
|
+
| Only one release | 🟡 Warning | Squats are usually published once and abandoned. |
|
|
212
|
+
| No repository or homepage link | 🟡 Warning | Real projects almost always link to source. |
|
|
213
|
+
| 1–2 characters from a popular name, **and** recently published | 🟡 Warning | Classic typosquat shape. Age matters: an old lookalike is just a package with a similar name. |
|
|
214
|
+
|
|
215
|
+
Warnings are advisory by default. Nothing but non-existence blocks unless you pass
|
|
216
|
+
`--strict`.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Why it doesn't block on "suspicious"
|
|
221
|
+
|
|
222
|
+
The obvious design is to score packages and block anything that looks shady. I built
|
|
223
|
+
that version first, then measured it against the **live feed of newly published PyPI
|
|
224
|
+
packages**.
|
|
225
|
+
|
|
226
|
+
> It flagged **100% of legitimate packages published that day.**
|
|
227
|
+
|
|
228
|
+
That is not a threshold-tuning problem — it's the shape of the data. A malicious
|
|
229
|
+
slopsquat registered three days ago and an honest new library published three days
|
|
230
|
+
ago are **the same package from the outside**. Both are young. Both have one release.
|
|
231
|
+
Both often lack a repository link.
|
|
232
|
+
|
|
233
|
+
So `ghostpkg` blocks on exactly one signal — **the package does not exist** — because
|
|
234
|
+
that signal is precise, and it's the one that actually corresponds to a hallucination.
|
|
235
|
+
Everything softer is reported for a human to read.
|
|
236
|
+
|
|
237
|
+
```console
|
|
238
|
+
$ ghostpkg check react-router-dom-utils -e npm
|
|
239
|
+
|
|
240
|
+
WARNING react-router-dom-utils
|
|
241
|
+
- first published 176 days ago
|
|
242
|
+
- only one release
|
|
243
|
+
- no repository or homepage link
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
There is a second lesson baked into the code. A naive typosquat check using a flat
|
|
247
|
+
edit-distance budget flagged `flask`, `click` and `black` as typos **of each other** —
|
|
248
|
+
short popular names sit inherently close together. The budget now scales with name
|
|
249
|
+
length, and only applies to packages young enough to plausibly be a squat.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Where it fits in your workflow
|
|
254
|
+
|
|
255
|
+
### In CI
|
|
256
|
+
|
|
257
|
+
```yaml
|
|
258
|
+
- name: Check dependencies exist
|
|
259
|
+
run: |
|
|
260
|
+
pip install ghostpkg
|
|
261
|
+
ghostpkg scan requirements.txt
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### As a pre-commit hook
|
|
265
|
+
|
|
266
|
+
```yaml
|
|
267
|
+
repos:
|
|
268
|
+
- repo: local
|
|
269
|
+
hooks:
|
|
270
|
+
- id: ghostpkg
|
|
271
|
+
name: ghostpkg
|
|
272
|
+
entry: ghostpkg scan requirements.txt
|
|
273
|
+
language: system
|
|
274
|
+
files: requirements\.txt$
|
|
275
|
+
pass_filenames: false
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### In front of a coding agent
|
|
279
|
+
|
|
280
|
+
Point your agent's shell hook at `ghostpkg check` before it is allowed to run an
|
|
281
|
+
install command. The non-zero exit code stops the install, and `--json` gives the
|
|
282
|
+
agent a structured reason it can act on.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## How it works internally
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
name ──▶ registry lookup ──▶ 404? ──yes──▶ BLOCK ("does not exist")
|
|
290
|
+
│
|
|
291
|
+
no
|
|
292
|
+
▼
|
|
293
|
+
collect facts:
|
|
294
|
+
age · release count · repo link
|
|
295
|
+
│
|
|
296
|
+
▼
|
|
297
|
+
young enough to be a squat?
|
|
298
|
+
│
|
|
299
|
+
yes ────┴──── no ──▶ OK
|
|
300
|
+
│
|
|
301
|
+
▼
|
|
302
|
+
typo distance to top 2,000 names
|
|
303
|
+
(budget scales with name length)
|
|
304
|
+
│
|
|
305
|
+
▼
|
|
306
|
+
reasons found? ──no──▶ OK
|
|
307
|
+
│yes
|
|
308
|
+
▼
|
|
309
|
+
WARN (BLOCK if --strict)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
| Module | Responsibility |
|
|
313
|
+
|---|---|
|
|
314
|
+
| `registries.py` | PyPI and npm clients over `urllib`. Returns a `PackageFacts` record. |
|
|
315
|
+
| `assess.py` | The policy. Turns facts into a verdict plus human-readable reasons. |
|
|
316
|
+
| `data.py` | The 2,000 most-downloaded PyPI names, used only for typo distance. |
|
|
317
|
+
| `cli.py` | Subcommands, manifest parsing, colour output, exit codes. |
|
|
318
|
+
|
|
319
|
+
Lookups run concurrently across a small thread pool, so scanning a manifest costs
|
|
320
|
+
roughly one round trip rather than one per dependency.
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Comparison
|
|
325
|
+
|
|
326
|
+
| | `ghostpkg` | SCA scanners (Snyk, Socket) | `pip install` alone |
|
|
327
|
+
|---|:---:|:---:|:---:|
|
|
328
|
+
| Catches a name that doesn't exist | ✅ **before install** | after install / in a PR | ❌ |
|
|
329
|
+
| Runs without an account | ✅ | ❌ | — |
|
|
330
|
+
| Runtime dependencies | **0** | many | — |
|
|
331
|
+
| Blocks legitimate new packages | ❌ **no** | varies | — |
|
|
332
|
+
| PyPI + npm in one tool | ✅ | ✅ | ❌ |
|
|
333
|
+
| Detects known malware | ❌ | ✅ | ❌ |
|
|
334
|
+
| Licence / CVE analysis | ❌ | ✅ | ❌ |
|
|
335
|
+
|
|
336
|
+
`ghostpkg` is deliberately narrow, and the last two rows are where a real SCA product
|
|
337
|
+
earns its keep. Use both.
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Honest limitations
|
|
342
|
+
|
|
343
|
+
> [!WARNING]
|
|
344
|
+
> **The hard case is out of scope today.** A hallucinated name that an attacker has
|
|
345
|
+
> **already registered** will pass the existence check. The warning signals are all
|
|
346
|
+
> that stand between you and it, and they are advisory. Improving this is the main
|
|
347
|
+
> open problem — see [issues](https://github.com/M1rwana12/ghostpkg/issues).
|
|
348
|
+
|
|
349
|
+
- Typo detection compares against the 2,000 most-downloaded PyPI projects, so a squat
|
|
350
|
+
on a less popular package won't be flagged as a lookalike.
|
|
351
|
+
- npm scoped packages (`@scope/name`) are checked, but the popular-name list is
|
|
352
|
+
PyPI-derived, so npm typosquat detection is weaker.
|
|
353
|
+
- Every check is a live registry request. There is no caching yet.
|
|
354
|
+
- Registry outages surface as exit code `2` rather than a silent pass — deliberately,
|
|
355
|
+
but it does mean a flaky network fails your build.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Prior art and credit
|
|
360
|
+
|
|
361
|
+
The scale of the problem was established by Spracklen et al., *"We Have a Package for
|
|
362
|
+
You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs"*
|
|
363
|
+
([USENIX Security 2025](https://www.usenix.org/conference/usenixsecurity25)).
|
|
364
|
+
|
|
365
|
+
Those authors **deliberately did not publish** their list of hallucinated package
|
|
366
|
+
names, because such a list is a ready-made target list for attackers. `ghostpkg`
|
|
367
|
+
follows that decision and **ships no corpus of hallucinated names** — it checks names
|
|
368
|
+
live instead.
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Contributing
|
|
373
|
+
|
|
374
|
+
Issues and pull requests welcome — see [CONTRIBUTING.md](https://github.com/M1rwana12/ghostpkg/blob/main/CONTRIBUTING.md).
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
git clone https://github.com/M1rwana12/ghostpkg
|
|
378
|
+
cd ghostpkg
|
|
379
|
+
pip install -e ".[dev]"
|
|
380
|
+
pytest
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**The most valuable contribution is a false positive report.** If `ghostpkg` flagged
|
|
384
|
+
a real package, that's a bug — a tool that cries wolf on legitimate packages gets
|
|
385
|
+
turned off, and then it protects nothing.
|
|
386
|
+
|
|
387
|
+
The threat model, and an explicit list of what this tool does **not** catch, is in
|
|
388
|
+
[SECURITY.md](https://github.com/M1rwana12/ghostpkg/blob/main/SECURITY.md).
|
|
389
|
+
|
|
390
|
+
## License
|
|
391
|
+
|
|
392
|
+
[MIT](https://github.com/M1rwana12/ghostpkg/blob/main/LICENSE)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
ghostpkg/__init__.py,sha256=GBjBGiCZNmiQy3iytFWWcykOBUpKpwrv7XyaRyxTpqI,341
|
|
2
|
+
ghostpkg/__main__.py,sha256=E6Gls0DNz8GQK2K-kOUIx8cYhgANW_CH54VKrfCfs14,52
|
|
3
|
+
ghostpkg/assess.py,sha256=cz-1oU82xphtMvPntzflSjpWbeB1AFE-tdvYnS04piw,4475
|
|
4
|
+
ghostpkg/cli.py,sha256=hzMrJoAkvosH9JDgfRAWdPsEQ5X-t7wWK2KFZRuUMYw,6380
|
|
5
|
+
ghostpkg/data.py,sha256=w74RhS4AYa0j4GpK4gYXTw0T85DJlGaLgxKAEdkUUJk,34584
|
|
6
|
+
ghostpkg/registries.py,sha256=HKsq7pW5uMQ87cA83BVq_oBE4LHvpI3ofZhnu61vmOs,4094
|
|
7
|
+
ghostpkg-0.1.0.dist-info/METADATA,sha256=8_uy5RFfGrs0JHhvWjDMNSIZ9-MA9nOyYvSy7cDKBWE,14108
|
|
8
|
+
ghostpkg-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
9
|
+
ghostpkg-0.1.0.dist-info/entry_points.txt,sha256=g-u1xTPhIrFDG-Gd65jJsq-NrFKbwUFtsRRs5wcEOhk,47
|
|
10
|
+
ghostpkg-0.1.0.dist-info/licenses/LICENSE,sha256=jcLAaLeXAwKtyIMBuKwKApCG41yu6qdk4Cqzcn-thU4,1066
|
|
11
|
+
ghostpkg-0.1.0.dist-info/RECORD,,
|
|
@@ -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.
|