@reneturcios/npmpreflight 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +150 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +69 -0
- package/dist/cli.js.map +1 -0
- package/dist/core.d.ts +10 -0
- package/dist/core.js +580 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +19 -0
- package/dist/mcp.js.map +1 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +39 -4
- package/server.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rene Turcios
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,151 @@
|
|
|
1
|
-
#
|
|
1
|
+
# npmpreflight
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Audit a package directory before `npm publish`, while a bad publish is
|
|
4
|
+
still cheap to undo.
|
|
5
|
+
|
|
6
|
+
npmpreflight resolves the file set your package would actually ship,
|
|
7
|
+
scans the shipped files for secret patterns, totals the size, and checks
|
|
8
|
+
`package.json` for the fields and entry points a publish depends on. It
|
|
9
|
+
answers the questions that are embarrassing to discover after a release:
|
|
10
|
+
is a credential sitting in a shipped file, does the `bin` target exist
|
|
11
|
+
in the tarball, is the license field set, and is a scoped package about
|
|
12
|
+
to publish as restricted.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
Run it without installing:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npx @reneturcios/npmpreflight check .
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or install it globally:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm install -g @reneturcios/npmpreflight
|
|
26
|
+
npmpreflight check .
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requires Node 20 or newer.
|
|
30
|
+
|
|
31
|
+
## CLI usage
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npmpreflight check [path] [--json]
|
|
35
|
+
npmpreflight mcp
|
|
36
|
+
npmpreflight --version
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- `check [path]` audits the package directory at `path` (default: the
|
|
40
|
+
current directory) and prints a human readable report. Add `--json`
|
|
41
|
+
for the machine readable report.
|
|
42
|
+
- `mcp` starts the MCP server over stdio (see below).
|
|
43
|
+
- `--version` prints the version.
|
|
44
|
+
|
|
45
|
+
Exit codes: `0` when the verdict is READY, `1` for any other verdict,
|
|
46
|
+
`2` for a usage error or an unreadable or invalid `package.json`, and
|
|
47
|
+
`141` when the reader closed the output pipe early.
|
|
48
|
+
|
|
49
|
+
Example output:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
npmpreflight check .: FIX-FIRST
|
|
53
|
+
files: 4, bytes: 603, findings: 2 (high: 2, medium: 0, low: 0, info: 0)
|
|
54
|
+
summary: 4 shipped file(s), 603 byte(s), 2 finding(s) (2 high, 0 medium, 0 low, 0 info). High-severity findings must be fixed before publishing.
|
|
55
|
+
[HIGH] SECRET-LEAK src/index.js:2 Shipped file matches the secret pattern private-key-block. Remove the credential from the package and rotate it if it was ever real. (evidence: private-key-block)
|
|
56
|
+
[HIGH] SECRET-LEAK src/index.js:3 Shipped file matches the secret pattern github-pat-classic. Remove the credential from the package and rotate it if it was ever real. (evidence: github-pat-classic)
|
|
57
|
+
FIX-FIRST: resolve the high-severity findings before publishing.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Findings never include the matched secret text. The evidence for a
|
|
61
|
+
secret finding is the pattern id only, plus the file and line, so a
|
|
62
|
+
report is safe to paste into an issue or a CI log.
|
|
63
|
+
|
|
64
|
+
## MCP
|
|
65
|
+
|
|
66
|
+
The same audit is available as an MCP tool over stdio:
|
|
67
|
+
|
|
68
|
+
- Tool: `preflight_package`
|
|
69
|
+
- Input: `path`, the package directory to check
|
|
70
|
+
- Output: the JSON report, identical to `check --json`
|
|
71
|
+
|
|
72
|
+
Start it with `npmpreflight mcp`, or configure an MCP client to run
|
|
73
|
+
`npx @reneturcios/npmpreflight mcp`. The server is also published as an
|
|
74
|
+
OCI image and listed in `server.json` under the name
|
|
75
|
+
`io.github.CloudCorpRecords/npmpreflight`.
|
|
76
|
+
|
|
77
|
+
## Checks
|
|
78
|
+
|
|
79
|
+
| Rule | Severity | What it flags |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| MISSING-FIELD | high | `package.json` is missing one of `name`, `version`, `license`, or `description` (one finding per missing field). |
|
|
82
|
+
| BIN-MISSING | high | A `bin` path in `package.json` is not in the shipped file set. |
|
|
83
|
+
| MAIN-MISSING | high | The `main` entry point is not in the shipped file set. |
|
|
84
|
+
| SECRET-LEAK | high | A shipped text file matches a secret pattern: a private key block header, an AWS access key id shape (`AKIA` plus 16 characters), a classic GitHub token shape (`ghp_` plus 36 or more characters), a fine grained GitHub token shape (`github_pat_` prefix), an `sk-` API key shape, or a generic `api_key` style assignment with a long quoted value. Evidence is the pattern id only. |
|
|
85
|
+
| OVERSIZE | medium | Total shipped size over 10 MiB, or any single shipped file over 1 MiB (one finding per oversize file). |
|
|
86
|
+
| SCOPED-NO-PUBLISHCONFIG | medium | The package name is scoped (starts with `@`) and `publishConfig.access` is not `public`. |
|
|
87
|
+
| NO-README | low | No README file is in the shipped set. |
|
|
88
|
+
| NO-ENGINES | info | `package.json` declares no `engines` range. |
|
|
89
|
+
|
|
90
|
+
Every report counts the shipped files and their total bytes, plus the
|
|
91
|
+
finding totals by severity.
|
|
92
|
+
|
|
93
|
+
## Verdict
|
|
94
|
+
|
|
95
|
+
- `READY` when there are no high severity findings. Medium, low, and
|
|
96
|
+
info findings are reported but do not block the verdict.
|
|
97
|
+
- `FIX-FIRST` when at least one high severity finding exists.
|
|
98
|
+
|
|
99
|
+
## How the shipped file set is resolved
|
|
100
|
+
|
|
101
|
+
- If `package.json` has a `files` array, those paths are included:
|
|
102
|
+
files are added directly and directories are walked recursively.
|
|
103
|
+
`package.json`, `README`, and `LICENSE` are always included when
|
|
104
|
+
present, whether or not they are listed, matching npm.
|
|
105
|
+
- If there is no `files` array, everything in the directory is included
|
|
106
|
+
except `node_modules`, `.git`, and paths matched by `.npmignore`
|
|
107
|
+
when one is present. The always included files above are still
|
|
108
|
+
included even if an ignore rule would match them.
|
|
109
|
+
- The walk is capped at 5000 shipped files. When the cap is reached
|
|
110
|
+
the report records it in its counts (`truncated: 1`, with
|
|
111
|
+
`filesSeen` and `maxFiles`) and the render notes that the remaining
|
|
112
|
+
files were not scanned.
|
|
113
|
+
|
|
114
|
+
## Limits
|
|
115
|
+
|
|
116
|
+
Read these before trusting a clean verdict:
|
|
117
|
+
|
|
118
|
+
- The file set is a static resolution, not a byte-exact `npm pack`.
|
|
119
|
+
npm applies more rules (its own always excluded files, lifecycle
|
|
120
|
+
generated files, and publish time behaviour) that a static pass does
|
|
121
|
+
not reproduce. Treat npmpreflight as a preflight check, then confirm
|
|
122
|
+
a release with `npm pack --dry-run`.
|
|
123
|
+
- `.npmignore` support is a simple subset: blank lines, `#` comments,
|
|
124
|
+
plain lines (a file name at any depth, or an exact relative path),
|
|
125
|
+
and directory patterns ending in `/`. Globs, negations, and
|
|
126
|
+
character classes in `.npmignore` are not supported and such lines
|
|
127
|
+
will not match the way npm would match them.
|
|
128
|
+
- `files` entries support literal files, literal directories, simple
|
|
129
|
+
`*` and `**` globs, and `!` negations of those. Anything more exotic
|
|
130
|
+
is resolved literally and may differ from npm.
|
|
131
|
+
- Secret scanning reads text files only. A file whose scanned head
|
|
132
|
+
contains a NUL byte is treated as binary and skipped. Only the first
|
|
133
|
+
512 KiB of each file is scanned, so a secret past that offset in a
|
|
134
|
+
large file will not be found. Patterns are shapes, not proof: a
|
|
135
|
+
match can be a test fixture or an example, and a real credential in
|
|
136
|
+
an unrecognized shape will not match.
|
|
137
|
+
- Symlinks are not followed and are not shipped.
|
|
138
|
+
- Output is deterministic: findings are sorted, counts are stable,
|
|
139
|
+
and no timestamps are emitted.
|
|
140
|
+
|
|
141
|
+
## Development
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
npm install
|
|
145
|
+
npm run build
|
|
146
|
+
npm test
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* npmpreflight CLI.
|
|
4
|
+
*
|
|
5
|
+
* npmpreflight check [target] [--json]
|
|
6
|
+
* npmpreflight mcp
|
|
7
|
+
* npmpreflight --version
|
|
8
|
+
*
|
|
9
|
+
* Exit codes: 0 when the verdict is READY, 1 for any other verdict,
|
|
10
|
+
* 2 usage error, 141 when the reader closed the output pipe early
|
|
11
|
+
* (the shell-visible equivalent of death by SIGPIPE). The emit contract
|
|
12
|
+
* follows FACTORY-NOTES.md: one flush-aware write, exitCode never
|
|
13
|
+
* process.exit on report paths.
|
|
14
|
+
*/
|
|
15
|
+
import { runCheck, renderReport, CLEAN_VERDICT, VERSION } from "./core.js";
|
|
16
|
+
function exitBrokenPipe() {
|
|
17
|
+
process.exit(141);
|
|
18
|
+
}
|
|
19
|
+
process.stdout.on("error", (err) => {
|
|
20
|
+
if (err.code === "EPIPE")
|
|
21
|
+
exitBrokenPipe();
|
|
22
|
+
throw err;
|
|
23
|
+
});
|
|
24
|
+
async function emit(text, code) {
|
|
25
|
+
process.exitCode = code;
|
|
26
|
+
await new Promise((resolve, reject) => {
|
|
27
|
+
process.stdout.write(`${text}\n`, (err) => {
|
|
28
|
+
if (!err) {
|
|
29
|
+
resolve();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (err.code === "EPIPE")
|
|
33
|
+
exitBrokenPipe();
|
|
34
|
+
reject(err);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function usage() {
|
|
39
|
+
console.error(`Usage:
|
|
40
|
+
npmpreflight check [target] [--json]
|
|
41
|
+
npmpreflight mcp
|
|
42
|
+
npmpreflight --version`);
|
|
43
|
+
process.exit(2);
|
|
44
|
+
}
|
|
45
|
+
async function main() {
|
|
46
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
47
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
48
|
+
console.log(VERSION);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (cmd === "mcp") {
|
|
52
|
+
await import("./mcp.js");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (cmd !== "check")
|
|
56
|
+
usage();
|
|
57
|
+
const target = rest.find((a) => !a.startsWith("--")) ?? ".";
|
|
58
|
+
try {
|
|
59
|
+
const report = runCheck(target);
|
|
60
|
+
const text = rest.includes("--json") ? JSON.stringify(report, null, 2) : renderReport(report);
|
|
61
|
+
await emit(text, report.verdict === CLEAN_VERDICT ? 0 : 1);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
65
|
+
process.exitCode = 2;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
void main();
|
|
69
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE3E,SAAS,cAAc;IACrB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACpB,CAAC;AAED,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAA0B,EAAE,EAAE;IACxD,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;QAAE,cAAc,EAAE,CAAC;IAC3C,MAAM,GAAG,CAAC;AACZ,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI,CAAC,IAAY,EAAE,IAAY;IAC5C,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IACxB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE;YACxC,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAO,EAAE,CAAC;gBACV,OAAO;YACT,CAAC;YACD,IAAK,GAA6B,CAAC,IAAI,KAAK,OAAO;gBAAE,cAAc,EAAE,CAAC;YACtE,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,KAAK;IACZ,OAAO,CAAC,KAAK,CAAC;;;yBAGS,CAAC,CAAC;IACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7C,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QAC7D,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IACD,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;QAClB,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;QACzB,OAAO;IACT,CAAC;IACD,IAAI,GAAG,KAAK,OAAO;QAAE,KAAK,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC;IAC5D,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAC9F,MAAM,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5E,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,KAAK,IAAI,EAAE,CAAC"}
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Report } from "./types.js";
|
|
2
|
+
export declare const VERSION = "0.1.0";
|
|
3
|
+
export declare const CLEAN_VERDICT = "READY";
|
|
4
|
+
export declare const FIX_VERDICT = "FIX-FIRST";
|
|
5
|
+
export declare const MAX_FILES = 5000;
|
|
6
|
+
export declare const MAX_SCAN_BYTES: number;
|
|
7
|
+
export declare const MAX_FILE_BYTES: number;
|
|
8
|
+
export declare const MAX_TOTAL_BYTES: number;
|
|
9
|
+
export declare function runCheck(target: string): Report;
|
|
10
|
+
export declare function renderReport(report: Report): string;
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npmpreflight core: audit a package directory before npm publish.
|
|
3
|
+
*
|
|
4
|
+
* The shipped file set is resolved statically: the files field when one
|
|
5
|
+
* is present (literal files, directories walked recursively, and a
|
|
6
|
+
* simple glob subset), otherwise everything in the directory except
|
|
7
|
+
* node_modules, .git, and paths matched by a simple .npmignore subset.
|
|
8
|
+
* package.json, README, and LICENSE are always included when present,
|
|
9
|
+
* matching npm behaviour. This is a static approximation of npm pack,
|
|
10
|
+
* not a byte-exact reproduction of it.
|
|
11
|
+
*
|
|
12
|
+
* Secret scanning never records the matched text. A finding carries the
|
|
13
|
+
* rule id, the file, the line, and the pattern id as its only evidence.
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, openSync, readSync, closeSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
16
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
17
|
+
export const VERSION = "0.1.0";
|
|
18
|
+
export const CLEAN_VERDICT = "READY";
|
|
19
|
+
export const FIX_VERDICT = "FIX-FIRST";
|
|
20
|
+
export const MAX_FILES = 5000;
|
|
21
|
+
export const MAX_SCAN_BYTES = 512 * 1024;
|
|
22
|
+
export const MAX_FILE_BYTES = 1024 * 1024;
|
|
23
|
+
export const MAX_TOTAL_BYTES = 10 * 1024 * 1024;
|
|
24
|
+
const SEVERITY_ORDER = { high: 0, medium: 1, low: 2, info: 3 };
|
|
25
|
+
const REQUIRED_FIELDS = ["name", "version", "license", "description"];
|
|
26
|
+
const ALWAYS_INCLUDED_RE = /^(?:package\.json|readme(?:\.[^/]*)?|licen[cs]e(?:\.[^/]*)?)$/i;
|
|
27
|
+
/* ------------------------------------------------------------------ */
|
|
28
|
+
/* Small helpers */
|
|
29
|
+
/* ------------------------------------------------------------------ */
|
|
30
|
+
function stripDotSlash(p) {
|
|
31
|
+
let out = p.trim();
|
|
32
|
+
while (out.startsWith("./"))
|
|
33
|
+
out = out.slice(2);
|
|
34
|
+
return out.replace(/^\/+/, "");
|
|
35
|
+
}
|
|
36
|
+
function isRecord(v) {
|
|
37
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
38
|
+
}
|
|
39
|
+
function isPresent(v) {
|
|
40
|
+
if (v === undefined || v === null)
|
|
41
|
+
return false;
|
|
42
|
+
if (typeof v === "string")
|
|
43
|
+
return v.trim().length > 0;
|
|
44
|
+
if (isRecord(v))
|
|
45
|
+
return Object.keys(v).length > 0;
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse the supported .npmignore subset: blank lines and # comments are
|
|
50
|
+
* skipped, plain lines match a file name at any depth or an exact
|
|
51
|
+
* relative path, and lines ending in / match a directory. Globs,
|
|
52
|
+
* negations, and character classes are not supported.
|
|
53
|
+
*/
|
|
54
|
+
function parseNpmignore(root) {
|
|
55
|
+
const path = join(root, ".npmignore");
|
|
56
|
+
if (!existsSync(path))
|
|
57
|
+
return [];
|
|
58
|
+
let text;
|
|
59
|
+
try {
|
|
60
|
+
text = readFileSync(path, "utf8");
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
const rules = [];
|
|
66
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
67
|
+
const line = raw.trim();
|
|
68
|
+
if (!line || line.startsWith("#"))
|
|
69
|
+
continue;
|
|
70
|
+
const cleaned = line.replace(/^\/+/, "");
|
|
71
|
+
const isDir = cleaned.endsWith("/");
|
|
72
|
+
const pattern = isDir ? cleaned.replace(/\/+$/, "") : cleaned;
|
|
73
|
+
if (!pattern)
|
|
74
|
+
continue;
|
|
75
|
+
rules.push({ pattern, isDir, hasSlash: pattern.includes("/") });
|
|
76
|
+
}
|
|
77
|
+
return rules;
|
|
78
|
+
}
|
|
79
|
+
function matchesIgnore(rel, rules) {
|
|
80
|
+
const segments = rel.split("/");
|
|
81
|
+
for (const rule of rules) {
|
|
82
|
+
if (rule.isDir) {
|
|
83
|
+
if (rule.hasSlash) {
|
|
84
|
+
if (rel === rule.pattern || rel.startsWith(`${rule.pattern}/`))
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
else if (segments.includes(rule.pattern)) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (rule.hasSlash) {
|
|
93
|
+
if (rel === rule.pattern || rel.startsWith(`${rule.pattern}/`))
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
else if (basename(rel) === rule.pattern) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
/* ------------------------------------------------------------------ */
|
|
103
|
+
/* Simple glob subset for files entries */
|
|
104
|
+
/* ------------------------------------------------------------------ */
|
|
105
|
+
function globToRegExp(glob) {
|
|
106
|
+
let out = "^";
|
|
107
|
+
let i = 0;
|
|
108
|
+
while (i < glob.length) {
|
|
109
|
+
const ch = glob[i];
|
|
110
|
+
if (ch === "*") {
|
|
111
|
+
if (glob[i + 1] === "*") {
|
|
112
|
+
if (glob[i + 2] === "/") {
|
|
113
|
+
out += "(?:.*/)?";
|
|
114
|
+
i += 3;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
out += ".*";
|
|
118
|
+
i += 2;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
out += "[^/]*";
|
|
123
|
+
i += 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else if (ch === "?") {
|
|
127
|
+
out += "[^/]";
|
|
128
|
+
i += 1;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
out += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
132
|
+
i += 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
out += "$";
|
|
136
|
+
return new RegExp(out);
|
|
137
|
+
}
|
|
138
|
+
class Collector {
|
|
139
|
+
byRel = new Map();
|
|
140
|
+
seen = new Set();
|
|
141
|
+
truncated = false;
|
|
142
|
+
get filesSeen() {
|
|
143
|
+
return this.seen.size;
|
|
144
|
+
}
|
|
145
|
+
get files() {
|
|
146
|
+
return [...this.byRel.values()].sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0));
|
|
147
|
+
}
|
|
148
|
+
has(rel) {
|
|
149
|
+
return this.byRel.has(rel);
|
|
150
|
+
}
|
|
151
|
+
remove(rel) {
|
|
152
|
+
this.byRel.delete(rel);
|
|
153
|
+
}
|
|
154
|
+
removeUnder(dirRel) {
|
|
155
|
+
for (const rel of [...this.byRel.keys()]) {
|
|
156
|
+
if (rel === dirRel || rel.startsWith(`${dirRel}/`))
|
|
157
|
+
this.byRel.delete(rel);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** Count a distinct candidate file, then ship it unless the cap hit. */
|
|
161
|
+
consider(rel, abs) {
|
|
162
|
+
if (this.seen.has(rel))
|
|
163
|
+
return;
|
|
164
|
+
this.seen.add(rel);
|
|
165
|
+
if (this.byRel.size >= MAX_FILES) {
|
|
166
|
+
this.truncated = true;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
let size = 0;
|
|
170
|
+
try {
|
|
171
|
+
size = statSync(abs).size;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
this.byRel.set(rel, { rel, abs, size });
|
|
177
|
+
}
|
|
178
|
+
/** Walk a directory recursively in sorted order. */
|
|
179
|
+
walk(dirAbs, dirRel, opts = {}) {
|
|
180
|
+
let entries;
|
|
181
|
+
try {
|
|
182
|
+
entries = readdirSync(dirAbs, { withFileTypes: true });
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
188
|
+
for (const entry of entries) {
|
|
189
|
+
if (entry.name === "node_modules" || entry.name === ".git")
|
|
190
|
+
continue;
|
|
191
|
+
const abs = join(dirAbs, entry.name);
|
|
192
|
+
const rel = dirRel ? `${dirRel}/${entry.name}` : entry.name;
|
|
193
|
+
if (entry.isDirectory()) {
|
|
194
|
+
this.walk(abs, rel, opts);
|
|
195
|
+
}
|
|
196
|
+
else if (entry.isFile()) {
|
|
197
|
+
const always = dirRel === "" && ALWAYS_INCLUDED_RE.test(entry.name);
|
|
198
|
+
if (!always && opts.ignore && matchesIgnore(rel, opts.ignore))
|
|
199
|
+
continue;
|
|
200
|
+
this.consider(rel, abs);
|
|
201
|
+
}
|
|
202
|
+
// Symlinks and special files are not followed or shipped.
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function collectShipped(root, pkg) {
|
|
207
|
+
const collector = new Collector();
|
|
208
|
+
const filesField = Array.isArray(pkg["files"]) ? pkg["files"] : undefined;
|
|
209
|
+
if (filesField) {
|
|
210
|
+
for (const rawEntry of filesField) {
|
|
211
|
+
if (typeof rawEntry !== "string")
|
|
212
|
+
continue;
|
|
213
|
+
let entry = stripDotSlash(rawEntry);
|
|
214
|
+
if (!entry)
|
|
215
|
+
continue;
|
|
216
|
+
const negated = entry.startsWith("!");
|
|
217
|
+
if (negated)
|
|
218
|
+
entry = stripDotSlash(entry.slice(1));
|
|
219
|
+
if (!entry)
|
|
220
|
+
continue;
|
|
221
|
+
if (entry.includes("*") || entry.includes("?")) {
|
|
222
|
+
const re = globToRegExp(entry);
|
|
223
|
+
if (negated) {
|
|
224
|
+
for (const f of collector.files) {
|
|
225
|
+
if (re.test(f.rel))
|
|
226
|
+
collector.remove(f.rel);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
// Walk the whole tree once per glob entry, matching paths.
|
|
231
|
+
const before = new Set(collector.files.map((f) => f.rel));
|
|
232
|
+
const walker = new Collector();
|
|
233
|
+
walker.walk(root, "");
|
|
234
|
+
for (const f of walker.files) {
|
|
235
|
+
if (re.test(f.rel))
|
|
236
|
+
collector.consider(f.rel, f.abs);
|
|
237
|
+
}
|
|
238
|
+
void before;
|
|
239
|
+
}
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const clean = entry.replace(/\/+$/, "");
|
|
243
|
+
const abs = join(root, clean);
|
|
244
|
+
if (negated) {
|
|
245
|
+
collector.remove(clean);
|
|
246
|
+
collector.removeUnder(clean);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
let st;
|
|
250
|
+
try {
|
|
251
|
+
st = statSync(abs);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (st.isDirectory()) {
|
|
257
|
+
collector.walk(abs, clean);
|
|
258
|
+
}
|
|
259
|
+
else if (st.isFile()) {
|
|
260
|
+
collector.consider(clean, abs);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
const ignore = parseNpmignore(root);
|
|
266
|
+
collector.walk(root, "", { ignore });
|
|
267
|
+
}
|
|
268
|
+
// Always included, even when the files field or .npmignore omits them.
|
|
269
|
+
let rootEntries = [];
|
|
270
|
+
try {
|
|
271
|
+
rootEntries = readdirSync(root).sort();
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
rootEntries = [];
|
|
275
|
+
}
|
|
276
|
+
for (const name of rootEntries) {
|
|
277
|
+
if (!ALWAYS_INCLUDED_RE.test(name))
|
|
278
|
+
continue;
|
|
279
|
+
const abs = join(root, name);
|
|
280
|
+
try {
|
|
281
|
+
if (statSync(abs).isFile())
|
|
282
|
+
collector.consider(name, abs);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return { files: collector.files, filesSeen: collector.filesSeen, truncated: collector.truncated };
|
|
289
|
+
}
|
|
290
|
+
const SECRET_PATTERNS = [
|
|
291
|
+
{ id: "private-key-block", make: () => /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/g },
|
|
292
|
+
{ id: "aws-access-key-id", make: () => /\bAKIA[0-9A-Z]{16}\b/g },
|
|
293
|
+
{ id: "github-pat-classic", make: () => /\bghp_[A-Za-z0-9]{36,}\b/g },
|
|
294
|
+
{ id: "github-pat-fine-grained", make: () => /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g },
|
|
295
|
+
{ id: "api-key-sk", make: () => /\bsk-(?:proj-|ant-)?[A-Za-z0-9_-]{20,}\b/g },
|
|
296
|
+
];
|
|
297
|
+
const GENERIC_ASSIGNMENT = {
|
|
298
|
+
id: "generic-api-key-assignment",
|
|
299
|
+
make: () => /(?:\bapi[_-]?key\b|\bapikey\b|\bsecret[_-]?key\b|\baccess[_-]?token\b|\bauth[_-]?token\b)\s*[:=]\s*["'][^"'\r\n\s]{16,}["']/gi,
|
|
300
|
+
};
|
|
301
|
+
/** Read at most MAX_SCAN_BYTES from the head of a file. */
|
|
302
|
+
function readHead(abs, size) {
|
|
303
|
+
const cap = Math.min(size, MAX_SCAN_BYTES);
|
|
304
|
+
if (cap <= 0)
|
|
305
|
+
return Buffer.alloc(0);
|
|
306
|
+
const buf = Buffer.alloc(cap);
|
|
307
|
+
let fd;
|
|
308
|
+
try {
|
|
309
|
+
fd = openSync(abs, "r");
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return Buffer.alloc(0);
|
|
313
|
+
}
|
|
314
|
+
try {
|
|
315
|
+
readSync(fd, buf, 0, cap, 0);
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return Buffer.alloc(0);
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
closeSync(fd);
|
|
322
|
+
}
|
|
323
|
+
return buf;
|
|
324
|
+
}
|
|
325
|
+
function lineOf(text, index) {
|
|
326
|
+
let line = 1;
|
|
327
|
+
for (let i = 0; i < index && i < text.length; i++) {
|
|
328
|
+
if (text[i] === "\n")
|
|
329
|
+
line += 1;
|
|
330
|
+
}
|
|
331
|
+
return line;
|
|
332
|
+
}
|
|
333
|
+
function scanFile(file, findings) {
|
|
334
|
+
const head = readHead(file.abs, file.size);
|
|
335
|
+
if (head.length === 0)
|
|
336
|
+
return;
|
|
337
|
+
if (head.includes(0))
|
|
338
|
+
return; // Binary file: not scanned as text.
|
|
339
|
+
const text = head.toString("utf8");
|
|
340
|
+
const seen = new Set();
|
|
341
|
+
const specificLines = new Set();
|
|
342
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
343
|
+
const re = pattern.make();
|
|
344
|
+
for (const match of text.matchAll(re)) {
|
|
345
|
+
const line = lineOf(text, match.index ?? 0);
|
|
346
|
+
specificLines.add(line);
|
|
347
|
+
const key = `${pattern.id}:${line}`;
|
|
348
|
+
if (seen.has(key))
|
|
349
|
+
continue;
|
|
350
|
+
seen.add(key);
|
|
351
|
+
findings.push({
|
|
352
|
+
rule: "SECRET-LEAK",
|
|
353
|
+
severity: "high",
|
|
354
|
+
file: file.rel,
|
|
355
|
+
line,
|
|
356
|
+
message: `Shipped file matches the secret pattern ${pattern.id}. Remove the credential from the package and rotate it if it was ever real.`,
|
|
357
|
+
evidence: pattern.id,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// A generic assignment on a line already flagged by a specific
|
|
362
|
+
// pattern would only duplicate the same leak, so it is skipped there.
|
|
363
|
+
const genericRe = GENERIC_ASSIGNMENT.make();
|
|
364
|
+
for (const match of text.matchAll(genericRe)) {
|
|
365
|
+
const line = lineOf(text, match.index ?? 0);
|
|
366
|
+
if (specificLines.has(line))
|
|
367
|
+
continue;
|
|
368
|
+
const key = `${GENERIC_ASSIGNMENT.id}:${line}`;
|
|
369
|
+
if (seen.has(key))
|
|
370
|
+
continue;
|
|
371
|
+
seen.add(key);
|
|
372
|
+
findings.push({
|
|
373
|
+
rule: "SECRET-LEAK",
|
|
374
|
+
severity: "high",
|
|
375
|
+
file: file.rel,
|
|
376
|
+
line,
|
|
377
|
+
message: `Shipped file matches the secret pattern ${GENERIC_ASSIGNMENT.id}. Remove the credential from the package and rotate it if it was ever real.`,
|
|
378
|
+
evidence: GENERIC_ASSIGNMENT.id,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/* ------------------------------------------------------------------ */
|
|
383
|
+
/* Main check */
|
|
384
|
+
/* ------------------------------------------------------------------ */
|
|
385
|
+
export function runCheck(target) {
|
|
386
|
+
const root = resolve(target);
|
|
387
|
+
const pkgPath = join(root, "package.json");
|
|
388
|
+
let pkgText;
|
|
389
|
+
try {
|
|
390
|
+
pkgText = readFileSync(pkgPath, "utf8");
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
// A target that is itself a package.json file is accepted too.
|
|
394
|
+
try {
|
|
395
|
+
if (basename(root) === "package.json") {
|
|
396
|
+
return runCheck(dirname(root));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
catch {
|
|
400
|
+
// Fall through to the clear error below.
|
|
401
|
+
}
|
|
402
|
+
throw new Error(`package.json not found at ${pkgPath}: ${target} is not a package directory with a readable package.json`);
|
|
403
|
+
}
|
|
404
|
+
let pkg;
|
|
405
|
+
try {
|
|
406
|
+
const parsed = JSON.parse(pkgText);
|
|
407
|
+
if (!isRecord(parsed))
|
|
408
|
+
throw new Error("top level is not an object");
|
|
409
|
+
pkg = parsed;
|
|
410
|
+
}
|
|
411
|
+
catch (err) {
|
|
412
|
+
throw new Error(`package.json at ${pkgPath} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
413
|
+
}
|
|
414
|
+
const { files, filesSeen, truncated } = collectShipped(root, pkg);
|
|
415
|
+
const shippedRels = new Set(files.map((f) => f.rel));
|
|
416
|
+
const totalBytes = files.reduce((sum, f) => sum + f.size, 0);
|
|
417
|
+
const findings = [];
|
|
418
|
+
// Required fields.
|
|
419
|
+
for (const field of REQUIRED_FIELDS) {
|
|
420
|
+
if (!isPresent(pkg[field])) {
|
|
421
|
+
findings.push({
|
|
422
|
+
rule: "MISSING-FIELD",
|
|
423
|
+
severity: "high",
|
|
424
|
+
file: "package.json",
|
|
425
|
+
message: `package.json is missing the ${field} field. npm publish needs it, and registries and tooling read it as the package identity.`,
|
|
426
|
+
evidence: field,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// Bin targets must be in the shipped set.
|
|
431
|
+
const binPaths = [];
|
|
432
|
+
const bin = pkg["bin"];
|
|
433
|
+
if (typeof bin === "string")
|
|
434
|
+
binPaths.push(bin);
|
|
435
|
+
else if (isRecord(bin)) {
|
|
436
|
+
for (const v of Object.values(bin))
|
|
437
|
+
if (typeof v === "string")
|
|
438
|
+
binPaths.push(v);
|
|
439
|
+
}
|
|
440
|
+
for (const binPath of [...binPaths].sort()) {
|
|
441
|
+
const rel = stripDotSlash(binPath);
|
|
442
|
+
if (!shippedRels.has(rel)) {
|
|
443
|
+
findings.push({
|
|
444
|
+
rule: "BIN-MISSING",
|
|
445
|
+
severity: "high",
|
|
446
|
+
file: "package.json",
|
|
447
|
+
message: `bin target ${rel} is not in the shipped file set. The published package would install a command that does not exist.`,
|
|
448
|
+
evidence: rel,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// The main entry point must be in the shipped set.
|
|
453
|
+
if (typeof pkg["main"] === "string" && pkg["main"].trim()) {
|
|
454
|
+
const rel = stripDotSlash(pkg["main"]);
|
|
455
|
+
if (!shippedRels.has(rel)) {
|
|
456
|
+
findings.push({
|
|
457
|
+
rule: "MAIN-MISSING",
|
|
458
|
+
severity: "high",
|
|
459
|
+
file: "package.json",
|
|
460
|
+
message: `main entry ${rel} is not in the shipped file set. Consumers would fail to load the published package.`,
|
|
461
|
+
evidence: rel,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// Secret scan over the shipped set.
|
|
466
|
+
for (const file of files)
|
|
467
|
+
scanFile(file, findings);
|
|
468
|
+
// Size checks.
|
|
469
|
+
for (const file of files) {
|
|
470
|
+
if (file.size > MAX_FILE_BYTES) {
|
|
471
|
+
findings.push({
|
|
472
|
+
rule: "OVERSIZE",
|
|
473
|
+
severity: "medium",
|
|
474
|
+
file: file.rel,
|
|
475
|
+
message: `Shipped file is ${file.size} bytes, over the ${MAX_FILE_BYTES} byte per-file limit. Large payloads usually belong in a release asset, not the npm tarball.`,
|
|
476
|
+
evidence: `${file.size} bytes`,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (totalBytes > MAX_TOTAL_BYTES) {
|
|
481
|
+
findings.push({
|
|
482
|
+
rule: "OVERSIZE",
|
|
483
|
+
severity: "medium",
|
|
484
|
+
message: `Total shipped size is ${totalBytes} bytes, over the ${MAX_TOTAL_BYTES} byte limit. Trim the files field or split the payload out of the package.`,
|
|
485
|
+
evidence: `${totalBytes} bytes`,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
// Scoped packages need an explicit public publishConfig.
|
|
489
|
+
const name = typeof pkg["name"] === "string" ? pkg["name"] : "";
|
|
490
|
+
if (name.startsWith("@")) {
|
|
491
|
+
const pc = isRecord(pkg["publishConfig"]) ? pkg["publishConfig"] : undefined;
|
|
492
|
+
if (pc?.["access"] !== "public") {
|
|
493
|
+
findings.push({
|
|
494
|
+
rule: "SCOPED-NO-PUBLISHCONFIG",
|
|
495
|
+
severity: "medium",
|
|
496
|
+
file: "package.json",
|
|
497
|
+
message: `Scoped package ${name} does not set publishConfig.access to public. A first publish would default to restricted access and fail or stay private.`,
|
|
498
|
+
evidence: name,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// README presence.
|
|
503
|
+
const hasReadme = files.some((f) => /^readme(?:\.[^/]*)?$/i.test(basename(f.rel)));
|
|
504
|
+
if (!hasReadme) {
|
|
505
|
+
findings.push({
|
|
506
|
+
rule: "NO-README",
|
|
507
|
+
severity: "low",
|
|
508
|
+
message: "No README file is in the shipped set. The registry page and the installed package would have no usage documentation.",
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
// Engines declaration.
|
|
512
|
+
if (!isPresent(pkg["engines"])) {
|
|
513
|
+
findings.push({
|
|
514
|
+
rule: "NO-ENGINES",
|
|
515
|
+
severity: "info",
|
|
516
|
+
file: "package.json",
|
|
517
|
+
message: "package.json declares no engines range. Consumers and CI cannot tell which Node versions the package supports.",
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
findings.sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity] ||
|
|
521
|
+
(a.file ?? "").localeCompare(b.file ?? "") ||
|
|
522
|
+
(a.line ?? 0) - (b.line ?? 0) ||
|
|
523
|
+
(a.rule < b.rule ? -1 : a.rule > b.rule ? 1 : 0) ||
|
|
524
|
+
(a.message < b.message ? -1 : a.message > b.message ? 1 : 0));
|
|
525
|
+
const bySeverity = { high: 0, medium: 0, low: 0, info: 0 };
|
|
526
|
+
for (const f of findings)
|
|
527
|
+
bySeverity[f.severity] += 1;
|
|
528
|
+
const verdict = bySeverity.high > 0 ? FIX_VERDICT : CLEAN_VERDICT;
|
|
529
|
+
const counts = {
|
|
530
|
+
files: files.length,
|
|
531
|
+
bytes: totalBytes,
|
|
532
|
+
findings: findings.length,
|
|
533
|
+
high: bySeverity.high,
|
|
534
|
+
medium: bySeverity.medium,
|
|
535
|
+
low: bySeverity.low,
|
|
536
|
+
info: bySeverity.info,
|
|
537
|
+
filesSeen,
|
|
538
|
+
truncated: truncated ? 1 : 0,
|
|
539
|
+
maxFiles: MAX_FILES,
|
|
540
|
+
maxScanBytes: MAX_SCAN_BYTES,
|
|
541
|
+
};
|
|
542
|
+
const summary = `${files.length} shipped file(s), ${totalBytes} byte(s), ${findings.length} finding(s) ` +
|
|
543
|
+
`(${bySeverity.high} high, ${bySeverity.medium} medium, ${bySeverity.low} low, ${bySeverity.info} info). ` +
|
|
544
|
+
(verdict === CLEAN_VERDICT ? "No high-severity findings." : "High-severity findings must be fixed before publishing.");
|
|
545
|
+
return {
|
|
546
|
+
tool: "npmpreflight",
|
|
547
|
+
version: VERSION,
|
|
548
|
+
target,
|
|
549
|
+
verdict,
|
|
550
|
+
counts,
|
|
551
|
+
findings,
|
|
552
|
+
summary,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
/* ------------------------------------------------------------------ */
|
|
556
|
+
/* Rendering */
|
|
557
|
+
/* ------------------------------------------------------------------ */
|
|
558
|
+
export function renderReport(report) {
|
|
559
|
+
const out = [];
|
|
560
|
+
out.push(`npmpreflight check ${report.target}: ${report.verdict}`);
|
|
561
|
+
out.push(` files: ${report.counts["files"] ?? 0}, bytes: ${report.counts["bytes"] ?? 0}, findings: ${report.counts["findings"] ?? 0} ` +
|
|
562
|
+
`(high: ${report.counts["high"] ?? 0}, medium: ${report.counts["medium"] ?? 0}, low: ${report.counts["low"] ?? 0}, info: ${report.counts["info"] ?? 0})`);
|
|
563
|
+
out.push(` summary: ${report.summary}`);
|
|
564
|
+
if ((report.counts["truncated"] ?? 0) > 0) {
|
|
565
|
+
out.push(` note: shipped file set cap reached: ${report.counts["filesSeen"] ?? 0} file(s) seen, only the first ${report.counts["maxFiles"] ?? MAX_FILES} (sorted by path within each directory) were shipped and scanned. The remaining files were NOT scanned.`);
|
|
566
|
+
}
|
|
567
|
+
for (const f of report.findings) {
|
|
568
|
+
const where = f.file ? (f.line ? `${f.file}:${f.line}` : f.file) : "package";
|
|
569
|
+
const evidence = f.evidence ? ` (evidence: ${f.evidence})` : "";
|
|
570
|
+
out.push(` [${f.severity.toUpperCase()}] ${f.rule} ${where} ${f.message}${evidence}`);
|
|
571
|
+
}
|
|
572
|
+
if (report.verdict === CLEAN_VERDICT) {
|
|
573
|
+
out.push("READY: no high-severity findings in the shipped file set.");
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
out.push("FIX-FIRST: resolve the high-severity findings before publishing.");
|
|
577
|
+
}
|
|
578
|
+
return out.join("\n");
|
|
579
|
+
}
|
|
580
|
+
//# sourceMappingURL=core.js.map
|
package/dist/core.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npmpreflight MCP server. Tool: preflight_package.
|
|
3
|
+
*/
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { runCheck, VERSION } from "./core.js";
|
|
8
|
+
const server = new McpServer({ name: "npmpreflight", version: VERSION });
|
|
9
|
+
server.tool("preflight_package", "Audit a package directory before publishing: resolves the shipped file set from the files field and ignore rules, scans shipped files for secret patterns (private keys, token shapes), totals the size, and checks package.json for required fields, a bin target that exists, license, and publishConfig on scoped names. Verdict READY or FIX-FIRST with evidence.", {
|
|
10
|
+
path: z.string().describe("Target path to check"),
|
|
11
|
+
}, async ({ path }) => {
|
|
12
|
+
const report = runCheck(path);
|
|
13
|
+
return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
|
|
14
|
+
});
|
|
15
|
+
async function main() {
|
|
16
|
+
await server.connect(new StdioServerTransport());
|
|
17
|
+
}
|
|
18
|
+
void main();
|
|
19
|
+
//# sourceMappingURL=mcp.js.map
|
package/dist/mcp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE9C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAEzE,MAAM,CAAC,IAAI,CACT,mBAAmB,EACnB,uWAAuW,EACvW;IACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;CAClD,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;IACjB,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACzF,CAAC,CACF,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,KAAK,IAAI,EAAE,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type Severity = "info" | "low" | "medium" | "high";
|
|
2
|
+
export interface Finding {
|
|
3
|
+
rule: string;
|
|
4
|
+
severity: Severity;
|
|
5
|
+
file?: string;
|
|
6
|
+
line?: number;
|
|
7
|
+
message: string;
|
|
8
|
+
evidence?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface Report {
|
|
11
|
+
tool: string;
|
|
12
|
+
version: string;
|
|
13
|
+
target: string;
|
|
14
|
+
verdict: string;
|
|
15
|
+
counts: Record<string, number>;
|
|
16
|
+
findings: Finding[];
|
|
17
|
+
summary: string;
|
|
18
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
{
|
|
2
|
+
"bin": {
|
|
3
|
+
"npmpreflight": "dist/cli.js"
|
|
4
|
+
},
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"@modelcontextprotocol/sdk": "^1",
|
|
7
|
+
"zod": "^3"
|
|
8
|
+
},
|
|
9
|
+
"description": "Pre-publish npm package auditor: what npm pack would ship, total size, secret-pattern leak scan over the shipped file set, and missing or risky package.json fields, checked before you publish instead of after.",
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@types/node": "^20",
|
|
12
|
+
"tsx": "^4",
|
|
13
|
+
"typescript": "^5"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"server.json"
|
|
21
|
+
],
|
|
22
|
+
"homepage": "https://github.com/CloudCorpRecords/npmpreflight#readme",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"mcpName": "io.github.CloudCorpRecords/npmpreflight",
|
|
2
25
|
"name": "@reneturcios/npmpreflight",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
}
|
|
26
|
+
"private": false,
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/CloudCorpRecords/npmpreflight.git"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc",
|
|
36
|
+
"dev": "tsx src/cli.ts",
|
|
37
|
+
"test": "tsx --test test/*.test.ts"
|
|
38
|
+
},
|
|
39
|
+
"type": "module",
|
|
40
|
+
"version": "0.1.0"
|
|
41
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"description": "Pre-publish npm pack auditor: shipped files, size, secret-pattern leaks, missing fields.",
|
|
4
|
+
"name": "io.github.CloudCorpRecords/npmpreflight",
|
|
5
|
+
"packages": [
|
|
6
|
+
{
|
|
7
|
+
"identifier": "@reneturcios/npmpreflight",
|
|
8
|
+
"packageArguments": [
|
|
9
|
+
{
|
|
10
|
+
"type": "positional",
|
|
11
|
+
"value": "mcp"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"registryBaseUrl": "https://registry.npmjs.org",
|
|
15
|
+
"registryType": "npm",
|
|
16
|
+
"runtimeHint": "npx",
|
|
17
|
+
"transport": {
|
|
18
|
+
"type": "stdio"
|
|
19
|
+
},
|
|
20
|
+
"version": "0.1.0"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"identifier": "ghcr.io/cloudcorprecords/npmpreflight:0.1.0",
|
|
24
|
+
"packageArguments": [
|
|
25
|
+
{
|
|
26
|
+
"type": "positional",
|
|
27
|
+
"value": "mcp"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"registryType": "oci",
|
|
31
|
+
"runtimeArguments": [
|
|
32
|
+
{
|
|
33
|
+
"name": "--rm",
|
|
34
|
+
"type": "named"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "-i",
|
|
38
|
+
"type": "named"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"runtimeHint": "docker",
|
|
42
|
+
"transport": {
|
|
43
|
+
"type": "stdio"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"repository": {
|
|
48
|
+
"source": "github",
|
|
49
|
+
"url": "https://github.com/CloudCorpRecords/npmpreflight"
|
|
50
|
+
},
|
|
51
|
+
"version": "0.1.0"
|
|
52
|
+
}
|