ohdear-npm-audit 1.3.0 → 1.5.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 +23 -4
- package/dist/bin.js +2 -2
- package/dist/generate.d.ts +2 -10
- package/dist/generate.js +7 -9
- package/dist/handler.d.ts +6 -2
- package/dist/handler.js +86 -47
- package/dist/next.d.ts +5 -0
- package/dist/next.js +47 -28
- package/dist/types.d.ts +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,13 @@ export default withOhDearHealth({
|
|
|
33
33
|
});
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
The wrapper generates the manifest automatically when the config is evaluated (before the build starts). It also checks for critical vulnerabilities at build time and logs
|
|
36
|
+
The wrapper generates the manifest automatically when the config is evaluated (before the build starts). It also checks for critical vulnerabilities at build time and logs enriched results including dependency chains and vulnerable version ranges:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
ohdear-npm-audit: 1 critical vulnerabilities:
|
|
40
|
+
- form-data@1.0.0 (via axios → form-data): unsafe random function
|
|
41
|
+
vulnerable: <1.0.1 — https://github.com/advisories/GHSA-xxx
|
|
42
|
+
```
|
|
37
43
|
|
|
38
44
|
```js
|
|
39
45
|
withOhDearHealth(nextConfig, {
|
|
@@ -72,8 +78,8 @@ This must match the secret configured in Oh Dear for your application health che
|
|
|
72
78
|
|
|
73
79
|
## How it works
|
|
74
80
|
|
|
75
|
-
1. **Build time** — The CLI or Next.js wrapper runs `pnpm list` / `npm ls` to extract all production dependencies (including transitive) and writes them to a JSON manifest. When using the Next.js wrapper, a build-time vulnerability check is
|
|
76
|
-
2. **Runtime** — On each GET request, the handler verifies the Oh Dear secret header, POSTs the manifest to the [npm bulk advisory API](https://docs.npmjs.com/about-audit-reports), filters for critical severity, and returns the result in the [Oh Dear health check format](https://ohdear.app/docs/features/application-health-monitoring)
|
|
81
|
+
1. **Build time** — The CLI or Next.js wrapper runs `pnpm list` / `npm ls` to extract all production dependencies (including transitive) and writes them to a JSON manifest that includes a reverse dependency map for tracing dependency chains. When using the Next.js wrapper, a build-time vulnerability check is performed and results are logged with the full dependency chain (e.g. `axios → form-data`) and vulnerable version ranges
|
|
82
|
+
2. **Runtime** — On each GET request, the handler verifies the Oh Dear secret header, POSTs the manifest to the [npm bulk advisory API](https://docs.npmjs.com/about-audit-reports), filters for critical severity, and returns the result in the [Oh Dear health check format](https://ohdear.app/docs/features/application-health-monitoring). Each vulnerability includes installed versions, vulnerable version range, and the dependency chain
|
|
77
83
|
|
|
78
84
|
### Response format
|
|
79
85
|
|
|
@@ -93,6 +99,19 @@ This must match the secret configured in Oh Dear for your application health che
|
|
|
93
99
|
}
|
|
94
100
|
```
|
|
95
101
|
|
|
102
|
+
When vulnerabilities are found, `meta.vulnerabilities` contains an array of:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"package": "form-data",
|
|
107
|
+
"installedVersions": ["1.0.0"],
|
|
108
|
+
"title": "form-data uses unsafe random function",
|
|
109
|
+
"url": "https://github.com/advisories/GHSA-xxx",
|
|
110
|
+
"vulnerableVersions": "<1.0.1",
|
|
111
|
+
"dependencyChain": ["axios", "form-data"]
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
96
115
|
`status` is `"ok"` when there are no critical vulnerabilities, `"warning"` when the npm advisory API is unreachable, `"failed"` otherwise.
|
|
97
116
|
|
|
98
117
|
## API
|
|
@@ -132,7 +151,7 @@ Defaults to `deps-manifest.json` in the current directory. Detects the package m
|
|
|
132
151
|
```
|
|
133
152
|
src/
|
|
134
153
|
├── types.ts # Shared types (DepsManifest, Vulnerability, HealthCheckResponse)
|
|
135
|
-
├── generate.ts # Manifest generation
|
|
154
|
+
├── generate.ts # Manifest + reverse dep map generation (build-time, execSync)
|
|
136
155
|
├── handler.ts # createHealthHandler factory — main export "."
|
|
137
156
|
├── next.ts # withOhDearHealth wrapper — export "./next"
|
|
138
157
|
└── bin.ts # CLI entry point — bin "ohdear-deps-manifest"
|
package/dist/bin.js
CHANGED
|
@@ -11,5 +11,5 @@ if (outputIdx !== -1 && args[outputIdx + 1]) {
|
|
|
11
11
|
}
|
|
12
12
|
const cwd = process.cwd();
|
|
13
13
|
const outputPath = (0, node_path_1.resolve)(cwd, output);
|
|
14
|
-
const
|
|
15
|
-
console.log(
|
|
14
|
+
const manifest = (0, generate_js_1.writeManifest)(outputPath, cwd);
|
|
15
|
+
console.log(`${output}: ${Object.keys(manifest.packages).length} packages written`);
|
package/dist/generate.d.ts
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
1
|
import type { DepsManifest } from "./types.js";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
reverseDeps: Record<string, string[]>;
|
|
5
|
-
}
|
|
6
|
-
export declare function generateManifest(cwd: string): GenerateResult;
|
|
7
|
-
export interface WriteManifestResult {
|
|
8
|
-
manifest: DepsManifest;
|
|
9
|
-
reverseMapPath: string;
|
|
10
|
-
}
|
|
11
|
-
export declare function writeManifest(outputPath: string, cwd: string): WriteManifestResult;
|
|
2
|
+
export declare function generateManifest(cwd: string): DepsManifest;
|
|
3
|
+
export declare function writeManifest(outputPath: string, cwd: string): DepsManifest;
|
package/dist/generate.js
CHANGED
|
@@ -23,9 +23,9 @@ function walkDeps(deps, acc, reverseDeps, parent) {
|
|
|
23
23
|
if (!acc[name])
|
|
24
24
|
acc[name] = new Set();
|
|
25
25
|
acc[name].add(info.version);
|
|
26
|
+
if (!reverseDeps[name])
|
|
27
|
+
reverseDeps[name] = new Set();
|
|
26
28
|
if (parent !== "root") {
|
|
27
|
-
if (!reverseDeps[name])
|
|
28
|
-
reverseDeps[name] = new Set();
|
|
29
29
|
reverseDeps[name].add(parent);
|
|
30
30
|
}
|
|
31
31
|
walkDeps(info.dependencies, acc, reverseDeps, name);
|
|
@@ -75,21 +75,19 @@ function generateManifest(cwd) {
|
|
|
75
75
|
const acc = {};
|
|
76
76
|
const reverseAcc = {};
|
|
77
77
|
walkDeps(tree.dependencies, acc, reverseAcc, "root");
|
|
78
|
-
const
|
|
78
|
+
const packages = {};
|
|
79
79
|
for (const [name, versions] of Object.entries(acc)) {
|
|
80
|
-
|
|
80
|
+
packages[name] = [...versions];
|
|
81
81
|
}
|
|
82
82
|
const reverseDeps = {};
|
|
83
83
|
for (const [name, parents] of Object.entries(reverseAcc)) {
|
|
84
84
|
reverseDeps[name] = [...parents];
|
|
85
85
|
}
|
|
86
|
-
return {
|
|
86
|
+
return { packages, reverseDeps };
|
|
87
87
|
}
|
|
88
88
|
function writeManifest(outputPath, cwd) {
|
|
89
|
-
const
|
|
89
|
+
const manifest = generateManifest(cwd);
|
|
90
90
|
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(outputPath), { recursive: true });
|
|
91
91
|
(0, node_fs_1.writeFileSync)(outputPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
92
|
-
|
|
93
|
-
(0, node_fs_1.writeFileSync)(reverseMapPath, JSON.stringify(reverseDeps, null, 2) + "\n");
|
|
94
|
-
return { manifest, reverseMapPath };
|
|
92
|
+
return manifest;
|
|
95
93
|
}
|
package/dist/handler.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import type { DepsManifest } from "./types.js";
|
|
2
|
-
export type { DepsManifest, HealthCheckResponse, Vulnerability, } from "./types.js";
|
|
1
|
+
import type { DepsManifest, Severity } from "./types.js";
|
|
2
|
+
export type { DepsManifest, HealthCheckResponse, HealthCheckResult, Severity, Vulnerability, } from "./types.js";
|
|
3
3
|
export interface CreateHealthHandlerOptions {
|
|
4
4
|
/** Environment variable name for the secret. Default: "OHDEAR_HEALTH_SECRET" */
|
|
5
5
|
secretEnvVar?: string;
|
|
6
6
|
/** Header name for the secret. Default: "oh-dear-health-check-secret" */
|
|
7
7
|
secretHeader?: string;
|
|
8
|
+
/** Severity levels to report. Default: ["critical"] */
|
|
9
|
+
severity?: Severity[];
|
|
10
|
+
/** Package names to ignore (e.g. known/accepted vulnerabilities). Default: [] */
|
|
11
|
+
ignorePackages?: string[];
|
|
8
12
|
}
|
|
9
13
|
export declare function createHealthHandler(manifest: DepsManifest, options?: CreateHealthHandlerOptions): (request: Request) => Promise<Response>;
|
package/dist/handler.js
CHANGED
|
@@ -3,24 +3,49 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createHealthHandler = createHealthHandler;
|
|
4
4
|
const NPM_BULK_ADVISORY_URL = "https://registry.npmjs.org/-/npm/v1/security/advisories/bulk";
|
|
5
5
|
const FETCH_TIMEOUT_MS = 8_000;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
const SEVERITY_OHDEAR_STATUS = {
|
|
7
|
+
critical: "failed",
|
|
8
|
+
high: "warning",
|
|
9
|
+
moderate: "warning",
|
|
10
|
+
low: "warning",
|
|
11
|
+
};
|
|
12
|
+
function capitalize(s) {
|
|
13
|
+
return s[0].toUpperCase() + s.slice(1);
|
|
14
|
+
}
|
|
15
|
+
/** BFS from pkg up through reverseMap to find the shortest chain to a root dep. */
|
|
16
|
+
function buildChain(pkg, reverseMap) {
|
|
17
|
+
const queue = [[pkg]];
|
|
18
|
+
const visited = new Set([pkg]);
|
|
19
|
+
while (queue.length > 0) {
|
|
20
|
+
const path = queue.shift();
|
|
21
|
+
const current = path[path.length - 1];
|
|
22
|
+
const parents = reverseMap[current];
|
|
23
|
+
if (!parents || parents.length === 0)
|
|
24
|
+
return [...path].reverse();
|
|
25
|
+
for (const parent of parents) {
|
|
26
|
+
if (visited.has(parent))
|
|
27
|
+
continue;
|
|
28
|
+
visited.add(parent);
|
|
29
|
+
queue.push([...path, parent]);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return [pkg];
|
|
33
|
+
}
|
|
34
|
+
function makeWarningResults(message, severityFilter) {
|
|
35
|
+
return severityFilter.map((sev) => ({
|
|
36
|
+
name: `npm_vulnerabilities_${sev}`,
|
|
37
|
+
label: `NPM ${capitalize(sev)} Vulnerabilities`,
|
|
38
|
+
status: "warning",
|
|
39
|
+
notificationMessage: message,
|
|
40
|
+
shortSummary: "check error",
|
|
41
|
+
meta: {},
|
|
42
|
+
}));
|
|
20
43
|
}
|
|
21
44
|
function createHealthHandler(manifest, options) {
|
|
22
45
|
const envVar = options?.secretEnvVar ?? "OHDEAR_HEALTH_SECRET";
|
|
23
46
|
const headerName = options?.secretHeader ?? "oh-dear-health-check-secret";
|
|
47
|
+
const severityFilter = options?.severity ?? ["critical"];
|
|
48
|
+
const ignorePackages = options?.ignorePackages ?? [];
|
|
24
49
|
let didWarn = false;
|
|
25
50
|
return async (request) => {
|
|
26
51
|
const secret = request.headers.get(headerName);
|
|
@@ -36,7 +61,7 @@ function createHealthHandler(manifest, options) {
|
|
|
36
61
|
res = await fetch(NPM_BULK_ADVISORY_URL, {
|
|
37
62
|
method: "POST",
|
|
38
63
|
headers: { "Content-Type": "application/json" },
|
|
39
|
-
body: JSON.stringify(manifest),
|
|
64
|
+
body: JSON.stringify(manifest.packages),
|
|
40
65
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
41
66
|
});
|
|
42
67
|
}
|
|
@@ -44,51 +69,65 @@ function createHealthHandler(manifest, options) {
|
|
|
44
69
|
const message = err instanceof DOMException && err.name === "TimeoutError"
|
|
45
70
|
? "npm advisory API timed out"
|
|
46
71
|
: "npm advisory API request failed";
|
|
47
|
-
return Response.json(
|
|
72
|
+
return Response.json({
|
|
73
|
+
finishedAt: Math.floor(Date.now() / 1000),
|
|
74
|
+
checkResults: makeWarningResults(message, severityFilter),
|
|
75
|
+
});
|
|
48
76
|
}
|
|
49
77
|
if (!res.ok) {
|
|
50
|
-
|
|
78
|
+
const msg = `npm advisory API returned HTTP ${res.status}`;
|
|
79
|
+
return Response.json({
|
|
80
|
+
finishedAt: Math.floor(Date.now() / 1000),
|
|
81
|
+
checkResults: makeWarningResults(msg, severityFilter),
|
|
82
|
+
});
|
|
51
83
|
}
|
|
52
84
|
let advisories;
|
|
53
85
|
try {
|
|
54
86
|
advisories = await res.json();
|
|
55
87
|
}
|
|
56
88
|
catch {
|
|
57
|
-
return Response.json(
|
|
89
|
+
return Response.json({
|
|
90
|
+
finishedAt: Math.floor(Date.now() / 1000),
|
|
91
|
+
checkResults: makeWarningResults("Failed to parse npm advisory response", severityFilter),
|
|
92
|
+
});
|
|
58
93
|
}
|
|
59
|
-
|
|
94
|
+
// Group vulnerabilities by severity level
|
|
95
|
+
const bySeverity = new Map(severityFilter.map((sev) => [sev, []]));
|
|
60
96
|
for (const [pkg, entries] of Object.entries(advisories)) {
|
|
97
|
+
if (ignorePackages.includes(pkg))
|
|
98
|
+
continue;
|
|
61
99
|
for (const entry of entries) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
100
|
+
const sev = entry.severity;
|
|
101
|
+
const bucket = bySeverity.get(sev);
|
|
102
|
+
if (!bucket)
|
|
103
|
+
continue;
|
|
104
|
+
bucket.push({
|
|
105
|
+
package: pkg,
|
|
106
|
+
installedVersions: manifest.packages[pkg] ?? [],
|
|
107
|
+
title: entry.title,
|
|
108
|
+
url: entry.url,
|
|
109
|
+
vulnerableVersions: entry.vulnerable_versions ?? "",
|
|
110
|
+
dependencyChain: buildChain(pkg, manifest.reverseDeps),
|
|
111
|
+
});
|
|
72
112
|
}
|
|
73
113
|
}
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
114
|
+
const checkResults = severityFilter.map((sev) => {
|
|
115
|
+
const vulns = bySeverity.get(sev);
|
|
116
|
+
const status = vulns.length === 0 ? "ok" : SEVERITY_OHDEAR_STATUS[sev];
|
|
117
|
+
return {
|
|
118
|
+
name: `npm_vulnerabilities_${sev}`,
|
|
119
|
+
label: `NPM ${capitalize(sev)} Vulnerabilities`,
|
|
120
|
+
status,
|
|
121
|
+
shortSummary: `${vulns.length} ${sev}`,
|
|
122
|
+
notificationMessage: vulns.length === 0
|
|
123
|
+
? `No ${sev} npm vulnerabilities found.`
|
|
124
|
+
: `${capitalize(sev)} vulnerabilities in: ${vulns.map((v) => v.package).join(", ")}`,
|
|
125
|
+
meta: vulns.length > 0 ? { vulnerabilities: vulns } : {},
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
return Response.json({
|
|
80
129
|
finishedAt: Math.floor(Date.now() / 1000),
|
|
81
|
-
checkResults
|
|
82
|
-
|
|
83
|
-
name: "npm_vulnerabilities",
|
|
84
|
-
label: "NPM Critical Vulnerabilities",
|
|
85
|
-
status,
|
|
86
|
-
notificationMessage,
|
|
87
|
-
shortSummary,
|
|
88
|
-
meta: critical.length > 0 ? { vulnerabilities: critical } : {},
|
|
89
|
-
},
|
|
90
|
-
],
|
|
91
|
-
};
|
|
92
|
-
return Response.json(body);
|
|
130
|
+
checkResults,
|
|
131
|
+
});
|
|
93
132
|
};
|
|
94
133
|
}
|
package/dist/next.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import type { Severity } from "./types.js";
|
|
1
2
|
export interface WithOhDearHealthOptions {
|
|
2
3
|
/** Output path for the manifest, relative to project root.
|
|
3
4
|
* Default: "src/app/api/health/deps-manifest.json" */
|
|
4
5
|
output?: string;
|
|
5
6
|
/** Check for critical vulnerabilities at build time. Default: true */
|
|
6
7
|
checkOnBuild?: boolean;
|
|
8
|
+
/** Severity levels to check at build time. Default: ["critical"] */
|
|
9
|
+
severity?: Severity[];
|
|
10
|
+
/** Package names to ignore. Default: [] */
|
|
11
|
+
ignorePackages?: string[];
|
|
7
12
|
}
|
|
8
13
|
export declare function withOhDearHealth<T>(nextConfig: T, options?: WithOhDearHealthOptions): T;
|
package/dist/next.js
CHANGED
|
@@ -52,14 +52,21 @@ function hasRecentLock(output) {
|
|
|
52
52
|
*/
|
|
53
53
|
function buildChainScript() {
|
|
54
54
|
return [
|
|
55
|
-
`function buildChain(pkg, reverseMap
|
|
56
|
-
`
|
|
57
|
-
` visited
|
|
58
|
-
`
|
|
59
|
-
`
|
|
60
|
-
`
|
|
61
|
-
`
|
|
62
|
-
`
|
|
55
|
+
`function buildChain(pkg, reverseMap) {`,
|
|
56
|
+
` const queue = [[pkg]];`,
|
|
57
|
+
` const visited = new Set([pkg]);`,
|
|
58
|
+
` while (queue.length > 0) {`,
|
|
59
|
+
` const path = queue.shift();`,
|
|
60
|
+
` const current = path[path.length - 1];`,
|
|
61
|
+
` const parents = reverseMap[current];`,
|
|
62
|
+
` if (!parents || parents.length === 0) return path.slice().reverse();`,
|
|
63
|
+
` for (const parent of parents) {`,
|
|
64
|
+
` if (visited.has(parent)) continue;`,
|
|
65
|
+
` visited.add(parent);`,
|
|
66
|
+
` queue.push([...path, parent]);`,
|
|
67
|
+
` }`,
|
|
68
|
+
` }`,
|
|
69
|
+
` return [pkg];`,
|
|
63
70
|
`}`,
|
|
64
71
|
].join("\n");
|
|
65
72
|
}
|
|
@@ -68,45 +75,57 @@ function buildChainScript() {
|
|
|
68
75
|
* API. Output is inherited so logs appear in the build output. Does not
|
|
69
76
|
* block the build — the subprocess runs in parallel with Next.js compilation.
|
|
70
77
|
*/
|
|
71
|
-
function checkVulnerabilities(manifestPath,
|
|
72
|
-
const
|
|
73
|
-
const
|
|
78
|
+
function checkVulnerabilities(manifestPath, severity, ignorePackages) {
|
|
79
|
+
const safePath = JSON.stringify(manifestPath);
|
|
80
|
+
const safeSeverity = JSON.stringify(severity);
|
|
81
|
+
const safeIgnore = JSON.stringify(ignorePackages);
|
|
74
82
|
const script = [
|
|
75
83
|
`const fs = require("fs");`,
|
|
76
|
-
`const
|
|
77
|
-
`const
|
|
84
|
+
`const data = JSON.parse(fs.readFileSync(${safePath}, "utf-8"));`,
|
|
85
|
+
`const packages = data.packages;`,
|
|
86
|
+
`const reverseMap = data.reverseDeps;`,
|
|
87
|
+
`const severityFilter = ${safeSeverity};`,
|
|
88
|
+
`const ignorePackages = ${safeIgnore};`,
|
|
78
89
|
buildChainScript(),
|
|
79
90
|
`fetch("https://registry.npmjs.org/-/npm/v1/security/advisories/bulk", {`,
|
|
80
91
|
` method: "POST",`,
|
|
81
92
|
` headers: { "Content-Type": "application/json" },`,
|
|
82
|
-
` body: JSON.stringify(
|
|
93
|
+
` body: JSON.stringify(packages),`,
|
|
83
94
|
` signal: AbortSignal.timeout(8000),`,
|
|
84
95
|
`})`,
|
|
85
96
|
`.then(r => { if (!r.ok) throw new Error("HTTP " + r.status); return r.json(); })`,
|
|
86
97
|
`.then(data => {`,
|
|
87
|
-
` const
|
|
98
|
+
` const bySev = {};`,
|
|
99
|
+
` severityFilter.forEach(s => bySev[s] = []);`,
|
|
88
100
|
` for (const [pkg, entries] of Object.entries(data)) {`,
|
|
101
|
+
` if (ignorePackages.includes(pkg)) continue;`,
|
|
89
102
|
` for (const e of entries) {`,
|
|
90
|
-
` if (e.severity
|
|
91
|
-
` const versions =
|
|
92
|
-
` const chain = buildChain(pkg, reverseMap
|
|
103
|
+
` if (!bySev[e.severity]) continue;`,
|
|
104
|
+
` const versions = packages[pkg] || [];`,
|
|
105
|
+
` const chain = buildChain(pkg, reverseMap);`,
|
|
93
106
|
` const versionStr = versions.join(", ");`,
|
|
94
107
|
` const chainStr = chain.length > 1 ? " (via " + chain.join(" \\u2192 ") + ")" : "";`,
|
|
95
108
|
` let line = " - " + pkg + "@" + versionStr + chainStr + ": " + e.title;`,
|
|
96
109
|
` if (e.vulnerable_versions) {`,
|
|
97
110
|
` line += "\\n vulnerable: " + e.vulnerable_versions + " \\u2014 " + e.url;`,
|
|
98
111
|
` }`,
|
|
99
|
-
`
|
|
112
|
+
` bySev[e.severity].push(line);`,
|
|
113
|
+
` }`,
|
|
114
|
+
` }`,
|
|
115
|
+
` let total = 0;`,
|
|
116
|
+
` for (const sev of severityFilter) {`,
|
|
117
|
+
` const lines = bySev[sev];`,
|
|
118
|
+
` total += lines.length;`,
|
|
119
|
+
` if (lines.length > 0) {`,
|
|
120
|
+
` console.warn("ohdear-npm-audit: " + lines.length + " " + sev + " vulnerabilities:");`,
|
|
121
|
+
` lines.forEach(c => console.warn(c));`,
|
|
100
122
|
` }`,
|
|
101
123
|
` }`,
|
|
102
|
-
` if (
|
|
103
|
-
` console.
|
|
104
|
-
` crits.forEach(c => console.warn(c));`,
|
|
105
|
-
` } else {`,
|
|
106
|
-
` console.log("ohdear-npm-audit: no critical vulnerabilities \\u2713");`,
|
|
124
|
+
` if (total === 0) {`,
|
|
125
|
+
` console.log("ohdear-npm-audit: no " + severityFilter.join("/") + " vulnerabilities \\u2713");`,
|
|
107
126
|
` }`,
|
|
108
127
|
`})`,
|
|
109
|
-
`.catch(
|
|
128
|
+
`.catch(err => console.warn("ohdear-npm-audit: build-time vulnerability check failed:", err.message || err));`,
|
|
110
129
|
].join("\n");
|
|
111
130
|
const child = (0, node_child_process_1.spawn)("node", ["-e", script], {
|
|
112
131
|
stdio: "inherit",
|
|
@@ -121,8 +140,8 @@ function withOhDearHealth(nextConfig, options) {
|
|
|
121
140
|
if (hasRecentLock(output))
|
|
122
141
|
return nextConfig;
|
|
123
142
|
try {
|
|
124
|
-
const
|
|
125
|
-
console.log(`ohdear-npm-audit: ${Object.keys(manifest).length} packages written → ${output}`);
|
|
143
|
+
const manifest = (0, generate_js_1.writeManifest)(output, cwd);
|
|
144
|
+
console.log(`ohdear-npm-audit: ${Object.keys(manifest.packages).length} packages written → ${output}`);
|
|
126
145
|
const routePath = deriveRoutePath((0, node_path_1.relative)(cwd, output));
|
|
127
146
|
const domain = process.env.VERCEL_PROJECT_PRODUCTION_URL;
|
|
128
147
|
if (routePath && domain) {
|
|
@@ -138,7 +157,7 @@ function withOhDearHealth(nextConfig, options) {
|
|
|
138
157
|
console.warn("ohdear-npm-audit: OHDEAR_HEALTH_SECRET is not set — health check will reject all requests.");
|
|
139
158
|
}
|
|
140
159
|
if (options?.checkOnBuild !== false) {
|
|
141
|
-
checkVulnerabilities(output,
|
|
160
|
+
checkVulnerabilities(output, options?.severity ?? ["critical"], options?.ignorePackages ?? []);
|
|
142
161
|
}
|
|
143
162
|
}
|
|
144
163
|
catch (err) {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type Severity = "critical" | "high" | "moderate" | "low";
|
|
2
|
+
export interface DepsManifest {
|
|
3
|
+
packages: Record<string, string[]>;
|
|
4
|
+
reverseDeps: Record<string, string[]>;
|
|
5
|
+
}
|
|
2
6
|
export interface Vulnerability {
|
|
3
7
|
package: string;
|
|
4
|
-
|
|
8
|
+
installedVersions: string[];
|
|
5
9
|
title: string;
|
|
6
10
|
url: string;
|
|
7
11
|
vulnerableVersions: string;
|