@expressots/studio-agent 4.0.0-preview.1
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 +143 -0
- package/dist/agent.d.ts +127 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +1031 -0
- package/dist/agent.js.map +1 -0
- package/dist/discovery/index.d.ts +2 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +2 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/route-scanner.d.ts +35 -0
- package/dist/discovery/route-scanner.d.ts.map +1 -0
- package/dist/discovery/route-scanner.js +385 -0
- package/dist/discovery/route-scanner.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation/index.d.ts +2 -0
- package/dist/instrumentation/index.d.ts.map +1 -0
- package/dist/instrumentation/index.js +2 -0
- package/dist/instrumentation/index.js.map +1 -0
- package/dist/instrumentation/tracer.d.ts +40 -0
- package/dist/instrumentation/tracer.d.ts.map +1 -0
- package/dist/instrumentation/tracer.js +190 -0
- package/dist/instrumentation/tracer.js.map +1 -0
- package/dist/introspection/container-introspector.d.ts +81 -0
- package/dist/introspection/container-introspector.d.ts.map +1 -0
- package/dist/introspection/container-introspector.js +251 -0
- package/dist/introspection/container-introspector.js.map +1 -0
- package/dist/logging/log-capture.d.ts +58 -0
- package/dist/logging/log-capture.d.ts.map +1 -0
- package/dist/logging/log-capture.js +184 -0
- package/dist/logging/log-capture.js.map +1 -0
- package/dist/recording/index.d.ts +2 -0
- package/dist/recording/index.d.ts.map +1 -0
- package/dist/recording/index.js +2 -0
- package/dist/recording/index.js.map +1 -0
- package/dist/recording/request-recorder.d.ts +43 -0
- package/dist/recording/request-recorder.d.ts.map +1 -0
- package/dist/recording/request-recorder.js +373 -0
- package/dist/recording/request-recorder.js.map +1 -0
- package/dist/security/fix-resolver.d.ts +40 -0
- package/dist/security/fix-resolver.d.ts.map +1 -0
- package/dist/security/fix-resolver.js +283 -0
- package/dist/security/fix-resolver.js.map +1 -0
- package/dist/security/fix-runner.d.ts +60 -0
- package/dist/security/fix-runner.d.ts.map +1 -0
- package/dist/security/fix-runner.js +188 -0
- package/dist/security/fix-runner.js.map +1 -0
- package/dist/security/index.d.ts +140 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +460 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/lockfile-graph.d.ts +69 -0
- package/dist/security/lockfile-graph.d.ts.map +1 -0
- package/dist/security/lockfile-graph.js +245 -0
- package/dist/security/lockfile-graph.js.map +1 -0
- package/dist/security/npm-audit.d.ts +67 -0
- package/dist/security/npm-audit.d.ts.map +1 -0
- package/dist/security/npm-audit.js +320 -0
- package/dist/security/npm-audit.js.map +1 -0
- package/dist/security/osv-cache.d.ts +51 -0
- package/dist/security/osv-cache.d.ts.map +1 -0
- package/dist/security/osv-cache.js +99 -0
- package/dist/security/osv-cache.js.map +1 -0
- package/dist/security/osv-client.d.ts +47 -0
- package/dist/security/osv-client.d.ts.map +1 -0
- package/dist/security/osv-client.js +247 -0
- package/dist/security/osv-client.js.map +1 -0
- package/dist/security/posture-analyzer.d.ts +44 -0
- package/dist/security/posture-analyzer.d.ts.map +1 -0
- package/dist/security/posture-analyzer.js +397 -0
- package/dist/security/posture-analyzer.js.map +1 -0
- package/dist/security/reachability.d.ts +59 -0
- package/dist/security/reachability.d.ts.map +1 -0
- package/dist/security/reachability.js +302 -0
- package/dist/security/reachability.js.map +1 -0
- package/dist/security/score.d.ts +36 -0
- package/dist/security/score.d.ts.map +1 -0
- package/dist/security/score.js +94 -0
- package/dist/security/score.js.map +1 -0
- package/dist/types/index.d.ts +587 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +14 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory model of `package-lock.json` v2/v3.
|
|
3
|
+
*
|
|
4
|
+
* The lockfile is the ground truth for *what's actually installed* in
|
|
5
|
+
* the user's `node_modules`. npm audit's output is a flat list keyed by
|
|
6
|
+
* package name and a partial `effects` array — useful, but it doesn't
|
|
7
|
+
* give us the full transitive chain or the direct dependency that
|
|
8
|
+
* brought a vulnerable package in. We need that for two features:
|
|
9
|
+
*
|
|
10
|
+
* 1. Root-cause routing — show "lodash@4.17.10 reached via
|
|
11
|
+
* express-session → fix by upgrading express-session 1.17.0 → 1.17.3".
|
|
12
|
+
* 2. Fix-group construction — collapse N CVEs that all resolve via
|
|
13
|
+
* one upgrade into a single "Upgrade X — fixes N advisories" row.
|
|
14
|
+
*
|
|
15
|
+
* The graph is intentionally minimal: we parse the lockfile once,
|
|
16
|
+
* resolve each "node_modules/<path>" entry's dependencies against npm's
|
|
17
|
+
* hoist rules (nearest ancestor wins), and store the forward edges. We
|
|
18
|
+
* do *not* try to invert it — root-cause queries do a small BFS from
|
|
19
|
+
* the project's direct deps, which is cheap enough at any realistic
|
|
20
|
+
* project size (thousands of packages, milliseconds).
|
|
21
|
+
*/
|
|
22
|
+
import type { RootCause } from '../types/index.js';
|
|
23
|
+
/**
|
|
24
|
+
* Loaded lockfile graph. A `null` instance means we couldn't parse the
|
|
25
|
+
* lockfile (no file, unsupported version, etc.) — callers should treat
|
|
26
|
+
* that as "no transitive resolution available" and skip enrichment.
|
|
27
|
+
*/
|
|
28
|
+
export declare class LockfileGraph {
|
|
29
|
+
private readonly root;
|
|
30
|
+
private readonly nodes;
|
|
31
|
+
/** Adjacency cache: node-key → array of resolved-child node-keys. */
|
|
32
|
+
private readonly childCache;
|
|
33
|
+
private constructor();
|
|
34
|
+
/**
|
|
35
|
+
* Parse the lockfile at `cwd`. Returns `null` for missing /
|
|
36
|
+
* unparseable lockfiles — root-cause routing then no-ops, which is
|
|
37
|
+
* better than a hard failure.
|
|
38
|
+
*/
|
|
39
|
+
static load(cwd: string): LockfileGraph | null;
|
|
40
|
+
/** True when `pkg` is declared as a direct dep / devDep / optionalDep. */
|
|
41
|
+
isDirect(pkg: string): boolean;
|
|
42
|
+
/** Version of a direct dep, or undefined if it's not a direct dep. */
|
|
43
|
+
directVersion(pkg: string): string | undefined;
|
|
44
|
+
/** Returns the set of every distinct package name in the graph. */
|
|
45
|
+
packageNames(): Set<string>;
|
|
46
|
+
/**
|
|
47
|
+
* Find the shortest chain of dependency names from any direct dep to
|
|
48
|
+
* an installation of `vulnerablePkg`.
|
|
49
|
+
*
|
|
50
|
+
* Returns `null` when:
|
|
51
|
+
* - the vulnerable package isn't installed at all, or
|
|
52
|
+
* - the BFS exhausts the graph without finding a route (shouldn't
|
|
53
|
+
* happen if the lockfile is internally consistent).
|
|
54
|
+
*/
|
|
55
|
+
findRootCause(vulnerablePkg: string): RootCause | null;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve `parentKey`'s declared dep `depName` to the lockfile entry
|
|
58
|
+
* that actually fulfils it. npm's hoist rule is "nearest ancestor with
|
|
59
|
+
* a matching `node_modules/<name>` wins" — we walk back up by trimming
|
|
60
|
+
* the path one segment at a time until we hit a match.
|
|
61
|
+
*/
|
|
62
|
+
private resolveDep;
|
|
63
|
+
/**
|
|
64
|
+
* Find *some* installation of `pkg`. We prefer a hoisted top-level
|
|
65
|
+
* one (most common); falling back to the first nested copy found.
|
|
66
|
+
*/
|
|
67
|
+
private findInstalledNode;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=lockfile-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lockfile-graph.d.ts","sourceRoot":"","sources":["../../src/security/lockfile-graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAqBnD;;;;GAIG;AACH,qBAAa,aAAa;IAKJ,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;IACrD,qEAAqE;IACrE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA+B;IAE1D,OAAO;IAIP;;;;OAIG;IACH,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IA4D9C,0EAA0E;IAC1E,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI9B,sEAAsE;IACtE,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAK9C,mEAAmE;IACnE,YAAY,IAAI,GAAG,CAAC,MAAM,CAAC;IAQ3B;;;;;;;;OAQG;IACH,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IA8DtD;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAyBlB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;CAQ1B"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory model of `package-lock.json` v2/v3.
|
|
3
|
+
*
|
|
4
|
+
* The lockfile is the ground truth for *what's actually installed* in
|
|
5
|
+
* the user's `node_modules`. npm audit's output is a flat list keyed by
|
|
6
|
+
* package name and a partial `effects` array — useful, but it doesn't
|
|
7
|
+
* give us the full transitive chain or the direct dependency that
|
|
8
|
+
* brought a vulnerable package in. We need that for two features:
|
|
9
|
+
*
|
|
10
|
+
* 1. Root-cause routing — show "lodash@4.17.10 reached via
|
|
11
|
+
* express-session → fix by upgrading express-session 1.17.0 → 1.17.3".
|
|
12
|
+
* 2. Fix-group construction — collapse N CVEs that all resolve via
|
|
13
|
+
* one upgrade into a single "Upgrade X — fixes N advisories" row.
|
|
14
|
+
*
|
|
15
|
+
* The graph is intentionally minimal: we parse the lockfile once,
|
|
16
|
+
* resolve each "node_modules/<path>" entry's dependencies against npm's
|
|
17
|
+
* hoist rules (nearest ancestor wins), and store the forward edges. We
|
|
18
|
+
* do *not* try to invert it — root-cause queries do a small BFS from
|
|
19
|
+
* the project's direct deps, which is cheap enough at any realistic
|
|
20
|
+
* project size (thousands of packages, milliseconds).
|
|
21
|
+
*/
|
|
22
|
+
import * as fs from 'node:fs';
|
|
23
|
+
import * as path from 'node:path';
|
|
24
|
+
/**
|
|
25
|
+
* Loaded lockfile graph. A `null` instance means we couldn't parse the
|
|
26
|
+
* lockfile (no file, unsupported version, etc.) — callers should treat
|
|
27
|
+
* that as "no transitive resolution available" and skip enrichment.
|
|
28
|
+
*/
|
|
29
|
+
export class LockfileGraph {
|
|
30
|
+
root;
|
|
31
|
+
nodes = new Map();
|
|
32
|
+
/** Adjacency cache: node-key → array of resolved-child node-keys. */
|
|
33
|
+
childCache = new Map();
|
|
34
|
+
constructor(root) {
|
|
35
|
+
this.root = root;
|
|
36
|
+
this.nodes.set(root.key, root);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Parse the lockfile at `cwd`. Returns `null` for missing /
|
|
40
|
+
* unparseable lockfiles — root-cause routing then no-ops, which is
|
|
41
|
+
* better than a hard failure.
|
|
42
|
+
*/
|
|
43
|
+
static load(cwd) {
|
|
44
|
+
const file = path.join(cwd, 'package-lock.json');
|
|
45
|
+
let raw;
|
|
46
|
+
try {
|
|
47
|
+
raw = fs.readFileSync(file, 'utf-8');
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
let parsed;
|
|
53
|
+
try {
|
|
54
|
+
parsed = JSON.parse(raw);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
// We only handle lockfile v2/v3 (which share the `packages` map).
|
|
60
|
+
// npm v7+ defaults to v2; npm v9+ to v3. Both are universally
|
|
61
|
+
// supported by the Node LTS releases we care about.
|
|
62
|
+
if (!parsed.packages || typeof parsed.packages !== 'object') {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const rootEntry = parsed.packages[''];
|
|
66
|
+
const root = {
|
|
67
|
+
key: '',
|
|
68
|
+
name: parsed.name ?? rootEntry?.name ?? '<project>',
|
|
69
|
+
version: parsed.version ?? rootEntry?.version ?? '0.0.0',
|
|
70
|
+
deps: {
|
|
71
|
+
...(rootEntry?.dependencies ?? {}),
|
|
72
|
+
...(rootEntry?.devDependencies ?? {}),
|
|
73
|
+
...(rootEntry?.optionalDependencies ?? {}),
|
|
74
|
+
},
|
|
75
|
+
isRoot: true,
|
|
76
|
+
};
|
|
77
|
+
const graph = new LockfileGraph(root);
|
|
78
|
+
for (const [key, entry] of Object.entries(parsed.packages)) {
|
|
79
|
+
if (key === '')
|
|
80
|
+
continue;
|
|
81
|
+
// The lockfile key encodes the install path; the package name is
|
|
82
|
+
// the last segment after the final `node_modules/`. Take that
|
|
83
|
+
// rather than trust `entry.name` (which v2 sometimes omits).
|
|
84
|
+
const name = nameFromKey(key) ?? entry?.name;
|
|
85
|
+
if (!name)
|
|
86
|
+
continue;
|
|
87
|
+
graph.nodes.set(key, {
|
|
88
|
+
key,
|
|
89
|
+
name,
|
|
90
|
+
version: entry?.version ?? 'unknown',
|
|
91
|
+
deps: {
|
|
92
|
+
...(entry?.dependencies ?? {}),
|
|
93
|
+
...(entry?.optionalDependencies ?? {}),
|
|
94
|
+
},
|
|
95
|
+
isRoot: false,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return graph;
|
|
99
|
+
}
|
|
100
|
+
/** True when `pkg` is declared as a direct dep / devDep / optionalDep. */
|
|
101
|
+
isDirect(pkg) {
|
|
102
|
+
return pkg in this.root.deps;
|
|
103
|
+
}
|
|
104
|
+
/** Version of a direct dep, or undefined if it's not a direct dep. */
|
|
105
|
+
directVersion(pkg) {
|
|
106
|
+
const child = this.resolveDep(this.root.key, pkg);
|
|
107
|
+
return child ? this.nodes.get(child)?.version : undefined;
|
|
108
|
+
}
|
|
109
|
+
/** Returns the set of every distinct package name in the graph. */
|
|
110
|
+
packageNames() {
|
|
111
|
+
const out = new Set();
|
|
112
|
+
for (const node of this.nodes.values()) {
|
|
113
|
+
if (!node.isRoot)
|
|
114
|
+
out.add(node.name);
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Find the shortest chain of dependency names from any direct dep to
|
|
120
|
+
* an installation of `vulnerablePkg`.
|
|
121
|
+
*
|
|
122
|
+
* Returns `null` when:
|
|
123
|
+
* - the vulnerable package isn't installed at all, or
|
|
124
|
+
* - the BFS exhausts the graph without finding a route (shouldn't
|
|
125
|
+
* happen if the lockfile is internally consistent).
|
|
126
|
+
*/
|
|
127
|
+
findRootCause(vulnerablePkg) {
|
|
128
|
+
const vulnNode = this.findInstalledNode(vulnerablePkg);
|
|
129
|
+
if (!vulnNode)
|
|
130
|
+
return null;
|
|
131
|
+
// Direct dep? No further work — the "chain" is just the package itself.
|
|
132
|
+
if (this.isDirect(vulnerablePkg)) {
|
|
133
|
+
return {
|
|
134
|
+
rootPackage: vulnerablePkg,
|
|
135
|
+
rootInstalledVersion: vulnNode.version,
|
|
136
|
+
chain: [vulnerablePkg],
|
|
137
|
+
isDirect: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// BFS from every direct dep, taking the first chain that reaches a
|
|
141
|
+
// node whose `name === vulnerablePkg`. We keep a parent map keyed
|
|
142
|
+
// by node-key so we can walk back to the root once we find a hit.
|
|
143
|
+
// Tracking visited *nodes* (by key) prevents cycles and keeps the
|
|
144
|
+
// search bounded by the lockfile size.
|
|
145
|
+
const parent = new Map(); // childKey → parentKey ('' marks a root entry)
|
|
146
|
+
const queue = [];
|
|
147
|
+
for (const directName of Object.keys(this.root.deps)) {
|
|
148
|
+
const childKey = this.resolveDep(this.root.key, directName);
|
|
149
|
+
if (!childKey || parent.has(childKey))
|
|
150
|
+
continue;
|
|
151
|
+
parent.set(childKey, null);
|
|
152
|
+
queue.push(childKey);
|
|
153
|
+
}
|
|
154
|
+
while (queue.length > 0) {
|
|
155
|
+
const curKey = queue.shift();
|
|
156
|
+
const node = this.nodes.get(curKey);
|
|
157
|
+
if (!node)
|
|
158
|
+
continue;
|
|
159
|
+
if (node.name === vulnerablePkg) {
|
|
160
|
+
const chain = [];
|
|
161
|
+
let cursor = curKey;
|
|
162
|
+
while (cursor !== null) {
|
|
163
|
+
const n = this.nodes.get(cursor);
|
|
164
|
+
if (n)
|
|
165
|
+
chain.unshift(n.name);
|
|
166
|
+
cursor = parent.get(cursor) ?? null;
|
|
167
|
+
}
|
|
168
|
+
const rootName = chain[0] ?? vulnerablePkg;
|
|
169
|
+
return {
|
|
170
|
+
rootPackage: rootName,
|
|
171
|
+
rootInstalledVersion: this.directVersion(rootName) ?? 'unknown',
|
|
172
|
+
chain,
|
|
173
|
+
isDirect: false,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
for (const depName of Object.keys(node.deps)) {
|
|
177
|
+
const nextKey = this.resolveDep(curKey, depName);
|
|
178
|
+
if (!nextKey || parent.has(nextKey))
|
|
179
|
+
continue;
|
|
180
|
+
parent.set(nextKey, curKey);
|
|
181
|
+
queue.push(nextKey);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Resolve `parentKey`'s declared dep `depName` to the lockfile entry
|
|
188
|
+
* that actually fulfils it. npm's hoist rule is "nearest ancestor with
|
|
189
|
+
* a matching `node_modules/<name>` wins" — we walk back up by trimming
|
|
190
|
+
* the path one segment at a time until we hit a match.
|
|
191
|
+
*/
|
|
192
|
+
resolveDep(parentKey, depName) {
|
|
193
|
+
const cacheKey = `${parentKey}::${depName}`;
|
|
194
|
+
const hit = this.childCache.get(cacheKey);
|
|
195
|
+
if (hit !== undefined)
|
|
196
|
+
return hit[0] ?? null;
|
|
197
|
+
// Direct attempt: `<parentKey>/node_modules/<depName>`.
|
|
198
|
+
let cursor = parentKey;
|
|
199
|
+
while (true) {
|
|
200
|
+
const candidate = cursor
|
|
201
|
+
? `${cursor}/node_modules/${depName}`
|
|
202
|
+
: `node_modules/${depName}`;
|
|
203
|
+
if (this.nodes.has(candidate)) {
|
|
204
|
+
this.childCache.set(cacheKey, [candidate]);
|
|
205
|
+
return candidate;
|
|
206
|
+
}
|
|
207
|
+
if (!cursor)
|
|
208
|
+
break;
|
|
209
|
+
// Strip the last `node_modules/<x>` segment, if any.
|
|
210
|
+
const idx = cursor.lastIndexOf('/node_modules/');
|
|
211
|
+
cursor = idx === -1 ? '' : cursor.slice(0, idx);
|
|
212
|
+
}
|
|
213
|
+
this.childCache.set(cacheKey, []);
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Find *some* installation of `pkg`. We prefer a hoisted top-level
|
|
218
|
+
* one (most common); falling back to the first nested copy found.
|
|
219
|
+
*/
|
|
220
|
+
findInstalledNode(pkg) {
|
|
221
|
+
const top = this.nodes.get(`node_modules/${pkg}`);
|
|
222
|
+
if (top)
|
|
223
|
+
return top;
|
|
224
|
+
for (const node of this.nodes.values()) {
|
|
225
|
+
if (!node.isRoot && node.name === pkg)
|
|
226
|
+
return node;
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Pull the package name from a lockfile key. Handles scoped packages
|
|
233
|
+
* (`node_modules/@scope/name`) and nested ones
|
|
234
|
+
* (`node_modules/foo/node_modules/@scope/bar`).
|
|
235
|
+
*/
|
|
236
|
+
function nameFromKey(key) {
|
|
237
|
+
const idx = key.lastIndexOf('node_modules/');
|
|
238
|
+
if (idx === -1)
|
|
239
|
+
return null;
|
|
240
|
+
const tail = key.slice(idx + 'node_modules/'.length);
|
|
241
|
+
if (!tail)
|
|
242
|
+
return null;
|
|
243
|
+
return tail; // `@scope/name` or `name`
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=lockfile-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lockfile-graph.js","sourceRoot":"","sources":["../../src/security/lockfile-graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `npm audit --json` runner.
|
|
3
|
+
*
|
|
4
|
+
* Spawns the host project's `npm audit` in a child process, captures
|
|
5
|
+
* stdout, and normalises the result into `DependencyFinding[]` so the
|
|
6
|
+
* agent doesn't have to know about npm's wire format anywhere else.
|
|
7
|
+
*
|
|
8
|
+
* Design constraints:
|
|
9
|
+
* - **Async only.** This runs inside the host's process; blocking the
|
|
10
|
+
* event loop with `execSync` would freeze the host server.
|
|
11
|
+
* - **Bounded.** We cap stdout at 16 MB and impose a 30 s wall timeout
|
|
12
|
+
* to keep a misbehaving npm install from hanging Studio forever.
|
|
13
|
+
* - **Defensive.** `npm audit` writes useful JSON to stdout even when
|
|
14
|
+
* it exits 1 (the convention is "exit 1 means vulns were found").
|
|
15
|
+
* We treat exit codes 0 and 1 as success.
|
|
16
|
+
*
|
|
17
|
+
* The runner is decoupled from the engine: it returns a structured
|
|
18
|
+
* result plus a `state` value the engine maps onto `scanState.audit`.
|
|
19
|
+
*/
|
|
20
|
+
import type { DependencyFinding } from '../types/index.js';
|
|
21
|
+
/**
|
|
22
|
+
* Per-package raw `fixAvailable` data lifted from npm audit's report,
|
|
23
|
+
* indexed by package name. Used by the fix-resolver to decide whether
|
|
24
|
+
* a finding can be cleared by `npm audit fix`, `npm audit fix --force`,
|
|
25
|
+
* or a manual `npm install`.
|
|
26
|
+
*
|
|
27
|
+
* npm's `fixAvailable` is tri-modal:
|
|
28
|
+
*
|
|
29
|
+
* - `false` → no upstream fix exists.
|
|
30
|
+
* - `true` → `npm audit fix` can resolve it, but npm declines
|
|
31
|
+
* to commit to a specific target version (typical
|
|
32
|
+
* for transitive vulns where the root upgrade is
|
|
33
|
+
* `audit-fix`'s choice).
|
|
34
|
+
* - `{ name, version, isSemVerMajor }` → exact target.
|
|
35
|
+
*
|
|
36
|
+
* We keep all three cases distinct here so the fix-resolver can produce
|
|
37
|
+
* the right command for each. Kept separate from the public
|
|
38
|
+
* `DependencyFinding` shape so the agent doesn't leak npm's wire format
|
|
39
|
+
* into the WS protocol.
|
|
40
|
+
*/
|
|
41
|
+
export type AuditFixAvailability = {
|
|
42
|
+
kind: 'specific';
|
|
43
|
+
name: string;
|
|
44
|
+
version: string;
|
|
45
|
+
isSemVerMajor: boolean;
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'auto';
|
|
48
|
+
} | {
|
|
49
|
+
kind: 'none';
|
|
50
|
+
};
|
|
51
|
+
export interface NpmAuditResult {
|
|
52
|
+
state: 'ok' | 'error' | 'missing-lockfile';
|
|
53
|
+
/** Empty on `missing-lockfile` / `error`. */
|
|
54
|
+
findings: DependencyFinding[];
|
|
55
|
+
/** Raw `fixAvailable` info keyed by *vulnerable package name*. */
|
|
56
|
+
fixAvailability: Map<string, AuditFixAvailability>;
|
|
57
|
+
/** Short, user-facing error message when state === 'error'. */
|
|
58
|
+
error?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Run `npm audit --json` in `cwd` and return findings. The function is
|
|
62
|
+
* resilient to npm not being installed, the project missing a
|
|
63
|
+
* lockfile, or audit returning malformed JSON — every error path
|
|
64
|
+
* yields a structured `NpmAuditResult` instead of throwing.
|
|
65
|
+
*/
|
|
66
|
+
export declare function runNpmAudit(cwd: string): Promise<NpmAuditResult>;
|
|
67
|
+
//# sourceMappingURL=npm-audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm-audit.d.ts","sourceRoot":"","sources":["../../src/security/npm-audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,KAAK,EACV,iBAAiB,EAElB,MAAM,mBAAmB,CAAC;AAO3B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,OAAO,CAAA;CAAE,GAC3E;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,IAAI,GAAG,OAAO,GAAG,kBAAkB,CAAC;IAC3C,6CAA6C;IAC7C,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,kEAAkE;IAClE,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IACnD,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmCD;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAoDtE"}
|