fullshield 0.3.0 → 1.0.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.
@@ -0,0 +1,356 @@
1
+ {
2
+ "fullshieldVersion": "1.0.0",
3
+ "entries": [
4
+ {
5
+ "package": "express",
6
+ "versionRange": "^4.0.0 || ^5.0.0",
7
+ "kind": "source",
8
+ "sinkClass": "sqli",
9
+ "memberPath": "req.get",
10
+ "argumentIndex": 0,
11
+ "reference": "https://expressjs.com/en/4x/api.html#req.get"
12
+ },
13
+ {
14
+ "package": "express",
15
+ "versionRange": "^4.0.0 || ^5.0.0",
16
+ "kind": "source",
17
+ "sinkClass": "sqli",
18
+ "memberPath": "req.body",
19
+ "argumentIndex": 0,
20
+ "reference": "https://expressjs.com/en/4x/api.html#req.body"
21
+ },
22
+ {
23
+ "package": "express",
24
+ "versionRange": "^4.0.0 || ^5.0.0",
25
+ "kind": "source",
26
+ "sinkClass": "sqli",
27
+ "memberPath": "req.query",
28
+ "argumentIndex": 0,
29
+ "reference": "https://expressjs.com/en/4x/api.html#req.query"
30
+ },
31
+ {
32
+ "package": "express",
33
+ "versionRange": "^4.0.0 || ^5.0.0",
34
+ "kind": "source",
35
+ "sinkClass": "sqli",
36
+ "memberPath": "req.params",
37
+ "argumentIndex": 0,
38
+ "reference": "https://expressjs.com/en/4x/api.html#req.params"
39
+ },
40
+ {
41
+ "package": "express-validator",
42
+ "versionRange": ">=6.0.0 <8.0.0",
43
+ "kind": "validator",
44
+ "sinkClass": "sqli",
45
+ "memberPath": "body().isInt",
46
+ "argumentIndex": 0,
47
+ "reference": "https://express-validator.github.io/docs/api/validation-chain/"
48
+ },
49
+ {
50
+ "package": "express-validator",
51
+ "versionRange": ">=6.0.0 <8.0.0",
52
+ "kind": "validator",
53
+ "sinkClass": "sqli",
54
+ "memberPath": "query().isInt",
55
+ "argumentIndex": 0,
56
+ "reference": "https://express-validator.github.io/docs/api/validation-chain/"
57
+ },
58
+ {
59
+ "package": "express-validator",
60
+ "versionRange": ">=6.0.0 <8.0.0",
61
+ "kind": "validator",
62
+ "sinkClass": "sqli",
63
+ "memberPath": "param().isInt",
64
+ "argumentIndex": 0,
65
+ "reference": "https://express-validator.github.io/docs/api/validation-chain/"
66
+ },
67
+ {
68
+ "package": "joi",
69
+ "versionRange": ">=17.0.0 <18.0.0",
70
+ "kind": "validator",
71
+ "sinkClass": "sqli",
72
+ "memberPath": "Joi.number().validate",
73
+ "argumentIndex": 0,
74
+ "reference": "https://joi.dev/api/?v=17.13.3#number"
75
+ },
76
+ {
77
+ "package": "yup",
78
+ "versionRange": ">=0.32.0 <2.0.0",
79
+ "kind": "validator",
80
+ "sinkClass": "sqli",
81
+ "memberPath": "yup.number().validate",
82
+ "argumentIndex": 0,
83
+ "reference": "https://github.com/jquense/yup#number"
84
+ },
85
+ {
86
+ "package": "zod",
87
+ "versionRange": ">=3.0.0 <5.0.0",
88
+ "kind": "validator",
89
+ "sinkClass": "sqli",
90
+ "memberPath": "z.number().parse",
91
+ "argumentIndex": 0,
92
+ "reference": "https://zod.dev/api"
93
+ },
94
+ {
95
+ "package": "validator",
96
+ "versionRange": ">=13.0.0 <14.0.0",
97
+ "kind": "validator",
98
+ "sinkClass": "sqli",
99
+ "memberPath": "validator.isInt",
100
+ "argumentIndex": 0,
101
+ "reference": "https://github.com/validatorjs/validator.js#validators"
102
+ },
103
+ {
104
+ "package": "dompurify",
105
+ "versionRange": ">=2.0.0 <4.0.0",
106
+ "kind": "sanitizer",
107
+ "sinkClass": "xss",
108
+ "memberPath": "DOMPurify.sanitize",
109
+ "argumentIndex": 0,
110
+ "reference": "https://github.com/cure53/DOMPurify#how-do-i-use-it"
111
+ },
112
+ {
113
+ "package": "sanitize-html",
114
+ "versionRange": ">=2.0.0 <3.0.0",
115
+ "kind": "sanitizer",
116
+ "sinkClass": "xss",
117
+ "memberPath": "sanitizeHtml",
118
+ "argumentIndex": 0,
119
+ "reference": "https://github.com/apostrophecms/sanitize-html#readme"
120
+ },
121
+ {
122
+ "package": "helmet",
123
+ "versionRange": ">=4.0.0 <8.0.0",
124
+ "kind": "sanitizer",
125
+ "sinkClass": "xss",
126
+ "memberPath": "helmet",
127
+ "argumentIndex": 0,
128
+ "reference": "https://helmetjs.github.io/"
129
+ },
130
+ {
131
+ "package": "pg",
132
+ "versionRange": ">=8.0.0 <9.0.0",
133
+ "kind": "sink",
134
+ "sinkClass": "sqli",
135
+ "memberPath": "query",
136
+ "argumentIndex": 0,
137
+ "reference": "https://node-postgres.com/apis/client"
138
+ },
139
+ {
140
+ "package": "mysql2",
141
+ "versionRange": ">=3.0.0 <4.0.0",
142
+ "kind": "sink",
143
+ "sinkClass": "sqli",
144
+ "memberPath": "query",
145
+ "argumentIndex": 0,
146
+ "reference": "https://sidorares.github.io/node-mysql2/docs"
147
+ },
148
+ {
149
+ "package": "sqlite3",
150
+ "versionRange": ">=5.0.0 <6.0.0",
151
+ "kind": "sink",
152
+ "sinkClass": "sqli",
153
+ "memberPath": "run",
154
+ "argumentIndex": 0,
155
+ "reference": "https://github.com/TryGhost/node-sqlite3/wiki/API"
156
+ },
157
+ {
158
+ "package": "better-sqlite3",
159
+ "versionRange": ">=9.0.0 <12.0.0",
160
+ "kind": "sink",
161
+ "sinkClass": "sqli",
162
+ "memberPath": "prepare",
163
+ "argumentIndex": 0,
164
+ "reference": "https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md"
165
+ },
166
+ {
167
+ "package": "knex",
168
+ "versionRange": ">=2.0.0 <4.0.0",
169
+ "kind": "sink",
170
+ "sinkClass": "sqli",
171
+ "memberPath": "raw",
172
+ "argumentIndex": 0,
173
+ "reference": "https://knexjs.org/guide/raw.html"
174
+ },
175
+ {
176
+ "package": "sequelize",
177
+ "versionRange": ">=6.0.0 <7.0.0",
178
+ "kind": "sink",
179
+ "sinkClass": "sqli",
180
+ "memberPath": "query",
181
+ "argumentIndex": 0,
182
+ "reference": "https://sequelize.org/docs/v6/core-concepts/raw-queries/"
183
+ },
184
+ {
185
+ "package": "typeorm",
186
+ "versionRange": ">=0.3.0 <0.4.0",
187
+ "kind": "sink",
188
+ "sinkClass": "sqli",
189
+ "memberPath": "query",
190
+ "argumentIndex": 0,
191
+ "reference": "https://typeorm.io/docs/query-builder/select-query-builder/"
192
+ },
193
+ {
194
+ "package": "prisma",
195
+ "versionRange": ">=5.0.0 <7.0.0",
196
+ "kind": "sink",
197
+ "sinkClass": "sqli",
198
+ "memberPath": "$queryRawUnsafe",
199
+ "argumentIndex": 0,
200
+ "reference": "https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/raw-queries"
201
+ },
202
+ {
203
+ "package": "mongoose",
204
+ "versionRange": ">=6.0.0 <9.0.0",
205
+ "kind": "sink",
206
+ "sinkClass": "nosqli",
207
+ "memberPath": "find",
208
+ "argumentIndex": 0,
209
+ "reference": "https://mongoosejs.com/docs/api/model.html#Model.find()"
210
+ },
211
+ {
212
+ "package": "mongodb",
213
+ "versionRange": ">=4.0.0 <7.0.0",
214
+ "kind": "sink",
215
+ "sinkClass": "nosqli",
216
+ "memberPath": "find",
217
+ "argumentIndex": 0,
218
+ "reference": "https://www.mongodb.com/docs/drivers/node/current/fundamentals/crud/read-operations/query-document/"
219
+ },
220
+ {
221
+ "package": "child_process",
222
+ "versionRange": ">=18.0.0",
223
+ "kind": "sink",
224
+ "sinkClass": "cmdi",
225
+ "memberPath": "exec",
226
+ "argumentIndex": 0,
227
+ "reference": "https://nodejs.org/api/child_process.html#child_processexeccommand-options-callback"
228
+ },
229
+ {
230
+ "package": "execa",
231
+ "versionRange": ">=8.0.0 <9.0.0",
232
+ "kind": "sink",
233
+ "sinkClass": "cmdi",
234
+ "memberPath": "execa",
235
+ "argumentIndex": 0,
236
+ "reference": "https://github.com/sindresorhus/execa#execafile-arguments-options"
237
+ },
238
+ {
239
+ "package": "axios",
240
+ "versionRange": ">=1.0.0 <2.0.0",
241
+ "kind": "sink",
242
+ "sinkClass": "ssrf",
243
+ "memberPath": "axios.get",
244
+ "argumentIndex": 0,
245
+ "reference": "https://axios-http.com/docs/api_intro"
246
+ },
247
+ {
248
+ "package": "node-fetch",
249
+ "versionRange": ">=3.0.0 <4.0.0",
250
+ "kind": "sink",
251
+ "sinkClass": "ssrf",
252
+ "memberPath": "fetch",
253
+ "argumentIndex": 0,
254
+ "reference": "https://github.com/node-fetch/node-fetch#fetchurl-options"
255
+ },
256
+ {
257
+ "package": "undici",
258
+ "versionRange": ">=6.0.0 <8.0.0",
259
+ "kind": "sink",
260
+ "sinkClass": "ssrf",
261
+ "memberPath": "undici.request",
262
+ "argumentIndex": 0,
263
+ "reference": "https://undici.nodejs.org/#/docs/api/Client"
264
+ },
265
+ {
266
+ "package": "got",
267
+ "versionRange": ">=14.0.0 <15.0.0",
268
+ "kind": "sink",
269
+ "sinkClass": "ssrf",
270
+ "memberPath": "got",
271
+ "argumentIndex": 0,
272
+ "reference": "https://github.com/sindresorhus/got#goturl-options"
273
+ },
274
+ {
275
+ "package": "fs",
276
+ "versionRange": ">=18.0.0",
277
+ "kind": "sink",
278
+ "sinkClass": "path",
279
+ "memberPath": "readFile",
280
+ "argumentIndex": 0,
281
+ "reference": "https://nodejs.org/api/fs.html#fsreadfilepath-options-callback"
282
+ },
283
+ {
284
+ "package": "fs-extra",
285
+ "versionRange": ">=11.0.0 <12.0.0",
286
+ "kind": "sink",
287
+ "sinkClass": "path",
288
+ "memberPath": "readFile",
289
+ "argumentIndex": 0,
290
+ "reference": "https://github.com/jprichardson/node-fs-extra"
291
+ },
292
+ {
293
+ "package": "path",
294
+ "versionRange": ">=18.0.0",
295
+ "kind": "sanitizer",
296
+ "sinkClass": "path",
297
+ "memberPath": "path.basename",
298
+ "argumentIndex": 0,
299
+ "reference": "https://nodejs.org/api/path.html#pathbasenamepath-suffix"
300
+ },
301
+ {
302
+ "package": "ejs",
303
+ "versionRange": ">=3.0.0 <4.0.0",
304
+ "kind": "sink",
305
+ "sinkClass": "xss",
306
+ "memberPath": "ejs.render",
307
+ "argumentIndex": 0,
308
+ "reference": "https://ejs.co/#docs"
309
+ },
310
+ {
311
+ "package": "pug",
312
+ "versionRange": ">=3.0.0 <4.0.0",
313
+ "kind": "sink",
314
+ "sinkClass": "xss",
315
+ "memberPath": "pug.render",
316
+ "argumentIndex": 0,
317
+ "reference": "https://pugjs.org/api/reference.html"
318
+ },
319
+ {
320
+ "package": "handlebars",
321
+ "versionRange": ">=4.0.0 <5.0.0",
322
+ "kind": "sink",
323
+ "sinkClass": "xss",
324
+ "memberPath": "Handlebars.compile",
325
+ "argumentIndex": 0,
326
+ "reference": "https://handlebarsjs.com/api-reference/compilation.html"
327
+ },
328
+ {
329
+ "package": "nunjucks",
330
+ "versionRange": ">=3.0.0 <4.0.0",
331
+ "kind": "sink",
332
+ "sinkClass": "xss",
333
+ "memberPath": "nunjucks.renderString",
334
+ "argumentIndex": 0,
335
+ "reference": "https://mozilla.github.io/nunjucks/api.html#renderstring"
336
+ },
337
+ {
338
+ "package": "jsonwebtoken",
339
+ "versionRange": ">=9.0.0 <10.0.0",
340
+ "kind": "sink",
341
+ "sinkClass": "jwt",
342
+ "memberPath": "jwt.verify",
343
+ "argumentIndex": 0,
344
+ "reference": "https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback"
345
+ },
346
+ {
347
+ "package": "bcrypt",
348
+ "versionRange": ">=5.0.0 <6.0.0",
349
+ "kind": "sanitizer",
350
+ "sinkClass": "crypto",
351
+ "memberPath": "bcrypt.hash",
352
+ "argumentIndex": 0,
353
+ "reference": "https://github.com/kelektiv/node.bcrypt.js#hash-info"
354
+ }
355
+ ]
356
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullshield",
3
- "version": "0.3.0",
3
+ "version": "1.0.0",
4
4
  "description": "Full-cycle security scanner for Node.js/TypeScript backends: detect, classify, report, notify, fix, prevent.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,7 @@
9
9
  "url": "git+https://github.com/kathanp/fullshield.git"
10
10
  },
11
11
  "engines": {
12
- "node": ">=20.11"
12
+ "node": ">=22.13"
13
13
  },
14
14
  "bin": {
15
15
  "fullshield": "dist/cli/index.js"
@@ -33,11 +33,13 @@
33
33
  "./package.json": "./package.json"
34
34
  },
35
35
  "peerDependencies": {
36
+ "@modelcontextprotocol/sdk": "^1.32.1",
36
37
  "express": "*",
37
38
  "fastify": "*",
38
- "koa": "*",
39
39
  "hono": "*",
40
- "@modelcontextprotocol/sdk": "^1.32.1",
40
+ "koa": "*",
41
+ "typescript": ">=5.0.0",
42
+ "yaml": "^2.8.0",
41
43
  "zod": "^4.6.7"
42
44
  },
43
45
  "peerDependenciesMeta": {
@@ -58,6 +60,12 @@
58
60
  },
59
61
  "zod": {
60
62
  "optional": true
63
+ },
64
+ "typescript": {
65
+ "optional": true
66
+ },
67
+ "yaml": {
68
+ "optional": true
61
69
  }
62
70
  },
63
71
  "files": [
@@ -67,13 +75,14 @@
67
75
  "LICENSE",
68
76
  "CHANGELOG.md",
69
77
  "templates",
70
- "rules"
78
+ "rules",
79
+ "models"
71
80
  ],
72
81
  "publishConfig": {
73
82
  "access": "public"
74
83
  },
75
84
  "scripts": {
76
- "build": "tsup",
85
+ "build": "node scripts/atomic-build.mjs",
77
86
  "typecheck": "tsc --noEmit",
78
87
  "lint": "eslint . --max-warnings 0",
79
88
  "test": "vitest run",
@@ -84,7 +93,8 @@
84
93
  "bench": "node scripts/bench.mjs",
85
94
  "bench:public": "node scripts/bench-public.mjs",
86
95
  "verify": "node scripts/verify.mjs",
87
- "verify:phase": "node scripts/verify.mjs --phase"
96
+ "verify:phase": "node scripts/verify.mjs --phase",
97
+ "quality": "node scripts/quality.mjs"
88
98
  },
89
99
  "dependencies": {
90
100
  "@typescript-eslint/typescript-estree": "^8.71.2",
@@ -93,17 +103,22 @@
93
103
  "picocolors": "^1.1.1"
94
104
  },
95
105
  "devDependencies": {
96
- "@modelcontextprotocol/sdk": "^1.32.1",
97
106
  "@eslint/js": "^9.14.0",
107
+ "@modelcontextprotocol/sdk": "^1.32.1",
98
108
  "@types/node": "^22.9.0",
99
109
  "@vitest/coverage-v8": "^2.1.4",
100
110
  "eslint": "^9.14.0",
101
111
  "express": "^4.22.3",
102
112
  "globals": "^15.12.0",
113
+ "knip": "^6.42.1",
103
114
  "tsup": "^8.3.5",
104
115
  "typescript": "^5.6.3",
105
116
  "typescript-eslint": "^8.13.0",
106
117
  "vitest": "^2.1.4",
118
+ "yaml": "^2.8.0",
107
119
  "zod": "^4.6.7"
120
+ },
121
+ "knip": {
122
+ "ignore": ["test/fixtures/**"]
108
123
  }
109
124
  }
@@ -0,0 +1,7 @@
1
+ # DEPS-LOCKMISMATCH-001
2
+
3
+ A locked version does not satisfy the range in `package.json`.
4
+
5
+ ## Remediation
6
+
7
+ Regenerate the lockfile so the locked version satisfies the declared range. Do not guess a version.
@@ -0,0 +1,7 @@
1
+ # DEPS-PHANTOM-001
2
+
3
+ An import names a package that is not in `dependencies`, `devDependencies`, `optionalDependencies`, or `peerDependencies`. Node built-ins and relative imports are not packages. An import under `test/`, `tests/`, `__tests__/`, `scripts/`, `seeds/`, or a `*.test.*` / `*.spec.*` file is severity low. Other paths stay medium. A package declared in `devDependencies` is not reported on those paths.
4
+
5
+ ## Remediation
6
+
7
+ Add the package to `package.json`, or remove the import.
package/rules/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # FullShield rules
2
2
 
3
+ New work is `SECURITY_SCANNER_SPEC.md` Section 32. These pages describe rules that already exist. They are not the Phase 46–59 designs.
4
+
3
5
  Each Core rule has a positive fixture in `test/fixtures/vulnerable-app` and a
4
6
  negative counterpart in `test/fixtures/clean-app`.
5
7
 
@@ -29,6 +31,7 @@ negative counterpart in `test/fixtures/clean-app`.
29
31
  | [DEPS-TYPOSQUAT-001](DEPS-TYPOSQUAT-001.md) | Direct dep 1 edit from a popular name |
30
32
  | [DEPS-DEPRECATED-001](DEPS-DEPRECATED-001.md) | Lockfile `deprecated` field |
31
33
  | [DEPS-NOLOCK-001](DEPS-NOLOCK-001.md) | package.json without a lockfile |
34
+ | [SUPPLY-PROV-001](SUPPLY-PROV-001.md) | Experimental: provenance regression, publisher change, or new release |
32
35
  | [SUPPLY-WORM-001](SUPPLY-WORM-001.md) | Experimental: token read + publish/registry/GitHub/workflow write |
33
36
  | [SUPPLY-WORKFLOW-001](SUPPLY-WORKFLOW-001.md) | Experimental: dep writes `.github/` / git hooks / CI config |
34
37
 
@@ -2,6 +2,12 @@
2
2
 
3
3
  **AWS access key.** CWE-798.
4
4
 
5
+ ## Placeholder
6
+
7
+ `FULLSHIELD_EXPERIMENTAL_SECRET_CHECK` treats the documented example access key ID `AKIAIOSFODNN7EXAMPLE` as a placeholder. Confidence is lowered. The finding stays, and severity is unchanged.
8
+
9
+ https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html
10
+
5
11
  ## Remediation
6
12
 
7
13
  Rotate the key. Load it from the environment. Removing it from the file is not enough — it stays in git history.
@@ -2,6 +2,12 @@
2
2
 
3
3
  **GitHub token.** CWE-798.
4
4
 
5
+ ## Checksum
6
+
7
+ `FULLSHIELD_EXPERIMENTAL_SECRET_CHECK` validates the public CRC-32 suffix for `ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_`, and `github_pat_` tokens. The checksum is CRC-32/IEEE of the token body excluding the last six characters, written as six base62 characters (`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`). A failed check lowers confidence only. It does not raise severity or remove the finding. Other issuers have no checksum logic here.
8
+
9
+ https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats/
10
+
5
11
  ## Remediation
6
12
 
7
13
  Revoke the token and store a new one in env/CI secrets.
@@ -0,0 +1,17 @@
1
+ # SUPPLY-CAP-001
2
+
3
+ **Dependency capability denied by policy (static).** CWE-829.
4
+
5
+ Experimental (`FULLSHIELD_EXPERIMENTAL_CAPABILITIES=1`). A `deps.capabilityPolicy` `deny` rule matched a capability read from already-installed package files. High. Never executes package code and never runs install scripts. Absent from the default scan.
6
+
7
+ Capabilities: `child_process`, `net` (`net` / `http` / `https` / `dgram` / `fetch`), `fs_write`, `env` (`process.env`), `eval` (`eval`, `new Function`, `vm`), `native` (`.node`, `binding.gyp`), `dynamic_require`, `install_script` (`preinstall` / `install` / `postinstall`), `obfuscation` (Phase 70 `eval` of a decoded string, a 64+ hex run, an 80+ base64 run, or eight `\xNN` escapes).
8
+
9
+ `fullshield deps --capabilities` prints each direct dependency's own capabilities plus those of its transitive packages, with the package version that introduces each one. `deps-diff` adds `newCapabilities` only when this flag is also set. A bump is reported only when the previous integrity is already in the capability cache.
10
+
11
+ ## False-positive risk
12
+
13
+ The check is textual. A comment, string, or example that mentions `process.env`, `writeFile`, or `require('net')` is counted. The negative fixture is a package that only calls `fs.readFileSync` and whose `scripts.test` is not an install script. `warn` policy does not emit this rule. A deny glob that names a capability the package does not have does not emit it.
14
+
15
+ ## Remediation
16
+
17
+ Remove the package, or change `deps.capabilityPolicy` if the capability is expected. Do not run the package's install script to investigate it.
@@ -0,0 +1,3 @@
1
+ # SUPPLY-CHAINC2-001
2
+
3
+ A package file names a public blockchain RPC method (`getSignaturesForAddress` or `eth_getLogs`) and also fetches or evaluates a value. Either signal alone is not a finding. Experimental: `FULLSHIELD_EXPERIMENTAL_SUPPLY=1`. Static only. Fixtures use `example.invalid`.
@@ -0,0 +1,3 @@
1
+ # SUPPLY-PLATFORMGATE-001
2
+
3
+ A `process.platform === 'win32'` or `os.platform()` check, a download, and a process spawn or executable write all appear in the same file. One of those signals alone is not a finding. Experimental: `FULLSHIELD_EXPERIMENTAL_SUPPLY=1`. Fixtures use `example.invalid`.
@@ -0,0 +1,15 @@
1
+ # SUPPLY-PROV-001
2
+
3
+ Experimental (`FULLSHIELD_EXPERIMENTAL_PROVENANCE=1`). Informational registry signal for an installed package version. Severity is medium. The finding does not say the package is malicious. `DEPS-MALICIOUS-001` still comes only from an OSV `MAL-` advisory.
4
+
5
+ The registry host is `registry.npmjs.org` only. One packument request per package, at most 8 at a time, and `Retry-After` is honored. Results are stored in the Phase 62 registry cache (`<cache>/registry/<name>.json`). `--offline` reads that cache only.
6
+
7
+ Signals, when the packument lists the installed version:
8
+
9
+ - **provenance-regression** — an earlier version has `dist.attestations` and the installed version does not.
10
+ - **publisher-change** — the publishing user (`_npmUser` or `npmUser`) published none of the previous 10 versions.
11
+ - **new-release** — the same age check as Phase 62 (`releaseAgeLabel`, default 72 hours).
12
+
13
+ Missing cache data, a missing version, or an offline miss stays `unknown` and produces no finding.
14
+
15
+ False-positive risk: a maintainer can publish a young release, rotate the npm user, or drop attestations without anything being wrong. The negative fixture is an aged version that still has attestations and the same publisher as the previous versions; it produces no finding.
@@ -0,0 +1,3 @@
1
+ # SUPPLY-SECHOLDER-001
2
+
3
+ The registry `latest` dist-tag for a locked package is npm's removed-for-security placeholder `0.0.1-security`, and the locked version is not that placeholder. Missing registry data produces no finding. `--offline` reads the cache only. Critical. Not downgraded by reachability. Remove the package, delete `node_modules`, reinstall from a clean lockfile, and rotate credentials the machine could read.
@@ -0,0 +1,3 @@
1
+ # SUPPLY-STEALER-001
2
+
3
+ A package file names a browser, Discord, or wallet store path and also makes a network call. A path alone, or a network call alone, is not a finding. Experimental: `FULLSHIELD_EXPERIMENTAL_SUPPLY=1`. Critical. Not downgraded by reachability.