ghostimport 0.1.0
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.
- package/README.md +240 -0
- package/dist/cache.d.ts +4 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +587 -0
- package/dist/config.d.ts +3 -0
- package/dist/files.d.ts +3 -0
- package/dist/imports.d.ts +1 -0
- package/dist/index.cjs +476 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +431 -0
- package/dist/npm.d.ts +3 -0
- package/dist/scan.d.ts +2 -0
- package/dist/types.d.ts +70 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# ghostimport
|
|
4
|
+
|
|
5
|
+
**Detects ghost imports — npm packages that don't exist, hallucinated by AI coding tools like Cursor, Copilot, and Claude**
|
|
6
|
+
|
|
7
|
+
AI coding tools sometimes generate `import` statements for packages that don't exist on npm. `ghostimport` scans your codebase and flags them before they cause a build failure — or worse, before an attacker registers the name with a malicious payload.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
$ npx ghostimport
|
|
11
|
+
|
|
12
|
+
ghostimport v0.1.0
|
|
13
|
+
Scanning /my-project
|
|
14
|
+
|
|
15
|
+
Scanned 142 files · 38 unique packages checked
|
|
16
|
+
|
|
17
|
+
✗ 2 hallucinated packages (do not exist on npm):
|
|
18
|
+
|
|
19
|
+
● @openai/functions-runtime
|
|
20
|
+
↳ src/agents/runner.ts
|
|
21
|
+
↳ src/agents/tools.ts
|
|
22
|
+
|
|
23
|
+
● react-server-fetch
|
|
24
|
+
↳ src/data/loader.ts
|
|
25
|
+
|
|
26
|
+
Found 2 issues.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Why this exists
|
|
32
|
+
|
|
33
|
+
When an LLM generates code, it predicts the most likely next token — not the most accurate one. It will confidently write `import { createAgent } from '@langchain/agent-runtime'` even if that exact package doesn't exist.
|
|
34
|
+
|
|
35
|
+
The result: your build fails, or your CI breaks at 2am, or — in the worst case — an attacker who monitors public GitHub repos for unregistered package names [registers it with a `postinstall` script that exfiltrates your `.env`](https://vibedoctor.io/blog/hallucinated-imports-ai-packages-dont-exist).
|
|
36
|
+
|
|
37
|
+
`ghostimport` catches this in seconds.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Run once (no install needed)
|
|
45
|
+
npx ghostimport
|
|
46
|
+
|
|
47
|
+
# Or install globally
|
|
48
|
+
npm install -g ghostimport
|
|
49
|
+
|
|
50
|
+
# Or as a dev dependency
|
|
51
|
+
npm install --save-dev ghostimport
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Scan current directory
|
|
60
|
+
ghostimport
|
|
61
|
+
|
|
62
|
+
# Scan a specific folder
|
|
63
|
+
ghostimport ./src
|
|
64
|
+
|
|
65
|
+
# Only show problems (great for CI)
|
|
66
|
+
ghostimport --quiet
|
|
67
|
+
|
|
68
|
+
# JSON output (pipe to other tools)
|
|
69
|
+
ghostimport --json
|
|
70
|
+
|
|
71
|
+
# Skip "missing from package.json" warnings
|
|
72
|
+
ghostimport --no-undeclared
|
|
73
|
+
|
|
74
|
+
# Help
|
|
75
|
+
ghostimport --help
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Add to CI (GitHub Actions)
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
- name: Check for hallucinated packages
|
|
82
|
+
run: npx ghostimport --quiet
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This step will fail (exit code 1) if any hallucinated packages are found.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Programmatic API
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { scan, extractImports, checkNpm } from 'ghostimport'
|
|
93
|
+
|
|
94
|
+
// Scan a directory
|
|
95
|
+
const results = await scan('./src')
|
|
96
|
+
|
|
97
|
+
console.log(results.hallucinated)
|
|
98
|
+
// [{ pkg: '@openai/functions-runtime', files: ['src/agents/runner.ts'] }]
|
|
99
|
+
|
|
100
|
+
console.log(results.notInPackageJson)
|
|
101
|
+
// [{ pkg: 'zod', files: ['src/validate.ts'] }]
|
|
102
|
+
|
|
103
|
+
// Check a single package
|
|
104
|
+
const { exists } = await checkNpm('some-package-name')
|
|
105
|
+
// exists: true | false | null (null = network error)
|
|
106
|
+
|
|
107
|
+
// Extract imports from a string
|
|
108
|
+
const imports = extractImports(`
|
|
109
|
+
import React from 'react'
|
|
110
|
+
import { createAgent } from '@fake/pkg'
|
|
111
|
+
`)
|
|
112
|
+
// ['react', '@fake/pkg']
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### TypeScript types
|
|
116
|
+
|
|
117
|
+
The package ships with full TypeScript declarations. Key types:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import type { ScanResult, ScanOptions, ScaryEntry, NpmCheckResult } from 'ghostimport'
|
|
121
|
+
|
|
122
|
+
interface ScanResult {
|
|
123
|
+
scanned: number // total files scanned
|
|
124
|
+
packages: number // unique packages found
|
|
125
|
+
hallucinated: { // packages that DON'T exist on npm
|
|
126
|
+
pkg: string
|
|
127
|
+
files: string[]
|
|
128
|
+
}[]
|
|
129
|
+
notInPackageJson: { // packages that exist but aren't declared
|
|
130
|
+
pkg: string
|
|
131
|
+
files: string[]
|
|
132
|
+
}[]
|
|
133
|
+
errors: { // packages that couldn't be checked (network)
|
|
134
|
+
pkg: string
|
|
135
|
+
error: string
|
|
136
|
+
files: string[]
|
|
137
|
+
}[]
|
|
138
|
+
scary: ScaryEntry[] // supply chain risk entries (--scary mode)
|
|
139
|
+
cacheHits: number
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface ScanOptions {
|
|
143
|
+
onProgress?: (p: { pkg: string; exists: boolean | null; done: number; total: number }) => void
|
|
144
|
+
useCache?: boolean // default: true
|
|
145
|
+
scary?: boolean // default: false
|
|
146
|
+
config?: Config
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## What it scans
|
|
153
|
+
|
|
154
|
+
- `import x from 'pkg'`
|
|
155
|
+
- `import { x } from 'pkg'`
|
|
156
|
+
- `import * as x from 'pkg'`
|
|
157
|
+
- `require('pkg')`
|
|
158
|
+
- `await import('pkg')`
|
|
159
|
+
- `export { x } from 'pkg'`
|
|
160
|
+
- Scoped packages: `@scope/name`
|
|
161
|
+
- Subpath imports: `pkg/utils` → checks `pkg`
|
|
162
|
+
|
|
163
|
+
**Automatically ignores:**
|
|
164
|
+
- Node.js built-ins (`fs`, `path`, `crypto`, `node:*`, ...)
|
|
165
|
+
- Relative imports (`./`, `../`)
|
|
166
|
+
- `node_modules/`, `dist/`, `.git/`, `build/`
|
|
167
|
+
|
|
168
|
+
**Supported file types:** `.js` `.jsx` `.ts` `.tsx` `.mjs` `.cjs`
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Supply chain risk (`--scary`)
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
ghostimport --scary
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
In scary mode, `ghostimport` also checks whether hallucinated package names are available for malicious registration, and flags recently-published packages with suspicious signals (new, few downloads, single version).
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Config file
|
|
183
|
+
|
|
184
|
+
Create `.ghostimportrc.json` in your project root:
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"ignore": ["@company/*", "internal-lib"],
|
|
189
|
+
"includeUndeclared": true
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
| Field | Default | Description |
|
|
194
|
+
|---|---|---|
|
|
195
|
+
| `ignore` | `[]` | Packages or scope patterns to skip (`@scope/*` supported) |
|
|
196
|
+
| `includeUndeclared` | `true` | Warn on packages that exist on npm but aren't in `package.json` |
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Zero dependencies
|
|
201
|
+
|
|
202
|
+
`ghostimport` has **no runtime dependencies**. The published package uses only Node.js built-ins. This means:
|
|
203
|
+
|
|
204
|
+
- No supply chain risk from the tool itself
|
|
205
|
+
- Fast install
|
|
206
|
+
- Works offline for the scan phase (network only needed to check npm)
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Contributing
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
git clone https://github.com/FGuerreir0/ghostimport
|
|
214
|
+
cd ghostimport
|
|
215
|
+
npm install
|
|
216
|
+
|
|
217
|
+
npm run build # type-check + emit .d.ts + bundle with esbuild
|
|
218
|
+
npm test # run test suite
|
|
219
|
+
npm run dev # run CLI from source (no build needed)
|
|
220
|
+
npm run typecheck # type-check only (no output)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Source layout:
|
|
224
|
+
|
|
225
|
+
| File | Purpose |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `src/types.ts` | All exported TypeScript interfaces |
|
|
228
|
+
| `src/imports.ts` | Import extraction (regex) |
|
|
229
|
+
| `src/cache.ts` | Local registry cache (`~/.ghostimport/`) |
|
|
230
|
+
| `src/config.ts` | `.ghostimportrc.json` loading |
|
|
231
|
+
| `src/npm.ts` | npm registry checks, supply chain heuristics |
|
|
232
|
+
| `src/files.ts` | File walker, `package.json` deps, monorepo support |
|
|
233
|
+
| `src/scan.ts` | Main `scan()` orchestrator |
|
|
234
|
+
| `src/cli.ts` | CLI interface |
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
MIT
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CacheEntry } from './types';
|
|
2
|
+
export declare function loadCache(): Record<string, CacheEntry>;
|
|
3
|
+
export declare function saveCache(cache: Record<string, CacheEntry>): void;
|
|
4
|
+
export declare function getCached(cache: Record<string, CacheEntry>, pkgName: string): CacheEntry | null;
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|