@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.
Files changed (86) hide show
  1. package/README.md +143 -0
  2. package/dist/agent.d.ts +127 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +1031 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/discovery/index.d.ts +2 -0
  7. package/dist/discovery/index.d.ts.map +1 -0
  8. package/dist/discovery/index.js +2 -0
  9. package/dist/discovery/index.js.map +1 -0
  10. package/dist/discovery/route-scanner.d.ts +35 -0
  11. package/dist/discovery/route-scanner.d.ts.map +1 -0
  12. package/dist/discovery/route-scanner.js +385 -0
  13. package/dist/discovery/route-scanner.js.map +1 -0
  14. package/dist/index.d.ts +15 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +15 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/instrumentation/index.d.ts +2 -0
  19. package/dist/instrumentation/index.d.ts.map +1 -0
  20. package/dist/instrumentation/index.js +2 -0
  21. package/dist/instrumentation/index.js.map +1 -0
  22. package/dist/instrumentation/tracer.d.ts +40 -0
  23. package/dist/instrumentation/tracer.d.ts.map +1 -0
  24. package/dist/instrumentation/tracer.js +190 -0
  25. package/dist/instrumentation/tracer.js.map +1 -0
  26. package/dist/introspection/container-introspector.d.ts +81 -0
  27. package/dist/introspection/container-introspector.d.ts.map +1 -0
  28. package/dist/introspection/container-introspector.js +251 -0
  29. package/dist/introspection/container-introspector.js.map +1 -0
  30. package/dist/logging/log-capture.d.ts +58 -0
  31. package/dist/logging/log-capture.d.ts.map +1 -0
  32. package/dist/logging/log-capture.js +184 -0
  33. package/dist/logging/log-capture.js.map +1 -0
  34. package/dist/recording/index.d.ts +2 -0
  35. package/dist/recording/index.d.ts.map +1 -0
  36. package/dist/recording/index.js +2 -0
  37. package/dist/recording/index.js.map +1 -0
  38. package/dist/recording/request-recorder.d.ts +43 -0
  39. package/dist/recording/request-recorder.d.ts.map +1 -0
  40. package/dist/recording/request-recorder.js +373 -0
  41. package/dist/recording/request-recorder.js.map +1 -0
  42. package/dist/security/fix-resolver.d.ts +40 -0
  43. package/dist/security/fix-resolver.d.ts.map +1 -0
  44. package/dist/security/fix-resolver.js +283 -0
  45. package/dist/security/fix-resolver.js.map +1 -0
  46. package/dist/security/fix-runner.d.ts +60 -0
  47. package/dist/security/fix-runner.d.ts.map +1 -0
  48. package/dist/security/fix-runner.js +188 -0
  49. package/dist/security/fix-runner.js.map +1 -0
  50. package/dist/security/index.d.ts +140 -0
  51. package/dist/security/index.d.ts.map +1 -0
  52. package/dist/security/index.js +460 -0
  53. package/dist/security/index.js.map +1 -0
  54. package/dist/security/lockfile-graph.d.ts +69 -0
  55. package/dist/security/lockfile-graph.d.ts.map +1 -0
  56. package/dist/security/lockfile-graph.js +245 -0
  57. package/dist/security/lockfile-graph.js.map +1 -0
  58. package/dist/security/npm-audit.d.ts +67 -0
  59. package/dist/security/npm-audit.d.ts.map +1 -0
  60. package/dist/security/npm-audit.js +320 -0
  61. package/dist/security/npm-audit.js.map +1 -0
  62. package/dist/security/osv-cache.d.ts +51 -0
  63. package/dist/security/osv-cache.d.ts.map +1 -0
  64. package/dist/security/osv-cache.js +99 -0
  65. package/dist/security/osv-cache.js.map +1 -0
  66. package/dist/security/osv-client.d.ts +47 -0
  67. package/dist/security/osv-client.d.ts.map +1 -0
  68. package/dist/security/osv-client.js +247 -0
  69. package/dist/security/osv-client.js.map +1 -0
  70. package/dist/security/posture-analyzer.d.ts +44 -0
  71. package/dist/security/posture-analyzer.d.ts.map +1 -0
  72. package/dist/security/posture-analyzer.js +397 -0
  73. package/dist/security/posture-analyzer.js.map +1 -0
  74. package/dist/security/reachability.d.ts +59 -0
  75. package/dist/security/reachability.d.ts.map +1 -0
  76. package/dist/security/reachability.js +302 -0
  77. package/dist/security/reachability.js.map +1 -0
  78. package/dist/security/score.d.ts +36 -0
  79. package/dist/security/score.d.ts.map +1 -0
  80. package/dist/security/score.js +94 -0
  81. package/dist/security/score.js.map +1 -0
  82. package/dist/types/index.d.ts +587 -0
  83. package/dist/types/index.d.ts.map +1 -0
  84. package/dist/types/index.js +14 -0
  85. package/dist/types/index.js.map +1 -0
  86. package/package.json +75 -0
@@ -0,0 +1,320 @@
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 { spawn } from 'node:child_process';
21
+ import * as fs from 'node:fs';
22
+ import * as path from 'node:path';
23
+ /** Maximum bytes of stdout we'll buffer from `npm audit`. */
24
+ const MAX_STDOUT_BYTES = 16 * 1024 * 1024; // 16 MB
25
+ /** How long we wait before killing the child process. */
26
+ const AUDIT_TIMEOUT_MS = 30_000;
27
+ /**
28
+ * Run `npm audit --json` in `cwd` and return findings. The function is
29
+ * resilient to npm not being installed, the project missing a
30
+ * lockfile, or audit returning malformed JSON — every error path
31
+ * yields a structured `NpmAuditResult` instead of throwing.
32
+ */
33
+ export async function runNpmAudit(cwd) {
34
+ // Bail early if there's no lockfile — `npm audit` would just fail
35
+ // anyway, and we want to give the UI a clearer signal so it can
36
+ // render an empty state with instructions.
37
+ if (!hasLockfile(cwd)) {
38
+ return { state: 'missing-lockfile', findings: [], fixAvailability: new Map() };
39
+ }
40
+ let stdout = '';
41
+ let stderr = '';
42
+ try {
43
+ const result = await spawnAudit(cwd);
44
+ stdout = result.stdout;
45
+ stderr = result.stderr;
46
+ // npm audit exits 1 when it finds vulns, which is normal. Codes >=2
47
+ // signal a real failure (network, malformed package.json, etc.).
48
+ if (result.code !== null && result.code > 1) {
49
+ return {
50
+ state: 'error',
51
+ findings: [],
52
+ fixAvailability: new Map(),
53
+ error: extractShortError(stderr) ||
54
+ `npm audit exited with code ${result.code}`,
55
+ };
56
+ }
57
+ }
58
+ catch (err) {
59
+ return {
60
+ state: 'error',
61
+ findings: [],
62
+ fixAvailability: new Map(),
63
+ error: err.message || 'failed to spawn npm audit',
64
+ };
65
+ }
66
+ let parsed;
67
+ try {
68
+ parsed = JSON.parse(stdout);
69
+ }
70
+ catch {
71
+ return {
72
+ state: 'error',
73
+ findings: [],
74
+ fixAvailability: new Map(),
75
+ error: 'npm audit produced invalid JSON',
76
+ };
77
+ }
78
+ const findings = normaliseNpmAudit(parsed);
79
+ const fixAvailability = extractFixAvailability(parsed);
80
+ return { state: 'ok', findings, fixAvailability };
81
+ }
82
+ /**
83
+ * Walk the parsed audit report and produce a per-package `fixAvailable`
84
+ * map. Preserves npm's three-way semantics:
85
+ *
86
+ * - `false` → `{ kind: 'none' }` — no upstream fix exists.
87
+ * - `true` → `{ kind: 'auto' }` — `npm audit fix` can resolve it
88
+ * but no concrete pinned target.
89
+ * - object → `{ kind: 'specific', name, version, isSemVerMajor }`.
90
+ */
91
+ function extractFixAvailability(report) {
92
+ const out = new Map();
93
+ const vulns = report.vulnerabilities ?? {};
94
+ for (const [pkgName, vuln] of Object.entries(vulns)) {
95
+ const fa = vuln.fixAvailable;
96
+ if (typeof fa === 'object' && fa !== null) {
97
+ out.set(pkgName, {
98
+ kind: 'specific',
99
+ name: fa.name,
100
+ version: fa.version,
101
+ isSemVerMajor: Boolean(fa.isSemVerMajor),
102
+ });
103
+ }
104
+ else if (fa === true) {
105
+ out.set(pkgName, { kind: 'auto' });
106
+ }
107
+ else {
108
+ out.set(pkgName, { kind: 'none' });
109
+ }
110
+ }
111
+ return out;
112
+ }
113
+ /**
114
+ * Translate the `npm audit --json` vulnerability map into the agent's
115
+ * canonical `DependencyFinding` shape.
116
+ *
117
+ * Two filtering rules de-noise the report:
118
+ *
119
+ * 1. We dedupe advisories by id within a package — a single CVE
120
+ * affecting multiple packages yields one finding per affected
121
+ * package, but a package isn't reported twice for the same CVE.
122
+ *
123
+ * 2. **Alias-only entries are skipped when an alias target has its
124
+ * own real advisory in the same report.** npm audit cascades
125
+ * "effects" up the dependency chain — every package that
126
+ * transitively depends on `path-to-regexp` shows up as a separate
127
+ * vulnerability entry with `via: ['path-to-regexp']`. Surfacing
128
+ * each link in that chain triples the finding count without
129
+ * adding signal; users only care about the package they actually
130
+ * need to upgrade (the real advisory's owner).
131
+ */
132
+ function normaliseNpmAudit(report) {
133
+ const out = [];
134
+ const vulns = report.vulnerabilities ?? {};
135
+ // First pass: figure out which package names have *real* advisories
136
+ // (a `via` entry that's an object, not a string alias). Used below
137
+ // to drop alias-only cascades that point at one of these.
138
+ const packagesWithRealAdvisories = new Set();
139
+ for (const [pkgName, vuln] of Object.entries(vulns)) {
140
+ const hasReal = (vuln.via ?? []).some((v) => typeof v === 'object' && v !== null);
141
+ if (hasReal)
142
+ packagesWithRealAdvisories.add(pkgName);
143
+ }
144
+ for (const [pkgName, vuln] of Object.entries(vulns)) {
145
+ const advisories = (vuln.via ?? []).filter((v) => typeof v === 'object' && v !== null);
146
+ for (const adv of advisories) {
147
+ const id = buildFindingId(adv, pkgName);
148
+ out.push({
149
+ id,
150
+ package: pkgName,
151
+ installedVersion: vuln.range ?? adv.range ?? 'unknown',
152
+ fixedVersion: typeof vuln.fixAvailable === 'object' && vuln.fixAvailable
153
+ ? vuln.fixAvailable.version
154
+ : undefined,
155
+ severity: parseSeverity(adv.severity ?? vuln.severity),
156
+ cvss: typeof adv.cvss?.score === 'number' && Number.isFinite(adv.cvss.score)
157
+ ? adv.cvss.score
158
+ : undefined,
159
+ title: adv.title ?? `Vulnerability in ${pkgName}`,
160
+ summary: adv.title ?? '',
161
+ references: adv.url ? [adv.url] : [],
162
+ // npm audit reports `effects` as the chain of affected dependents;
163
+ // we surface it so users can see what brings the vulnerable
164
+ // package in. Most direct deps will have an empty effects array.
165
+ path: [pkgName, ...(vuln.effects ?? [])],
166
+ });
167
+ }
168
+ // Alias-only entries: surface as a finding ONLY if no alias target
169
+ // already has a real advisory in this report. Otherwise it's pure
170
+ // cascade noise — every transitive dependent of path-to-regexp
171
+ // shouldn't get its own row.
172
+ const aliasOnly = (vuln.via ?? []).filter((v) => typeof v === 'string');
173
+ if (advisories.length === 0 && aliasOnly.length > 0) {
174
+ const allAliasesCoveredByRealFindings = aliasOnly.every((a) => packagesWithRealAdvisories.has(a));
175
+ if (allAliasesCoveredByRealFindings)
176
+ continue;
177
+ out.push({
178
+ id: `npm-audit-${pkgName}-${vuln.severity ?? 'unknown'}`,
179
+ package: pkgName,
180
+ installedVersion: vuln.range ?? 'unknown',
181
+ fixedVersion: typeof vuln.fixAvailable === 'object' && vuln.fixAvailable
182
+ ? vuln.fixAvailable.version
183
+ : undefined,
184
+ severity: parseSeverity(vuln.severity),
185
+ title: `Vulnerable transitive dependency via ${aliasOnly.join(', ')}`,
186
+ summary: '',
187
+ references: [],
188
+ path: [pkgName, ...aliasOnly],
189
+ });
190
+ }
191
+ }
192
+ return out;
193
+ }
194
+ /**
195
+ * Build a stable advisory id. Prefer the GHSA / CVE embedded in the
196
+ * advisory URL — that's what we'll dedupe against OSV later. Falls
197
+ * back to a hash-equivalent synthetic id otherwise.
198
+ */
199
+ function buildFindingId(adv, pkgName) {
200
+ const url = adv.url ?? '';
201
+ const ghsa = url.match(/(GHSA-[a-z0-9-]+)/i);
202
+ if (ghsa)
203
+ return ghsa[1].toUpperCase();
204
+ const cve = url.match(/(CVE-\d{4}-\d+)/i);
205
+ if (cve)
206
+ return cve[1].toUpperCase();
207
+ if (typeof adv.source === 'number') {
208
+ return `npm-${adv.source}-${pkgName}`;
209
+ }
210
+ // Last resort: deterministic synthetic id keyed by title+pkg so reruns
211
+ // produce the same id across scans.
212
+ return `npm-${pkgName}-${(adv.title ?? 'unknown').slice(0, 40).replace(/\s+/g, '-')}`;
213
+ }
214
+ function parseSeverity(input) {
215
+ switch ((input ?? '').toLowerCase()) {
216
+ case 'critical':
217
+ return 'CRITICAL';
218
+ case 'high':
219
+ return 'HIGH';
220
+ case 'moderate':
221
+ case 'medium':
222
+ return 'MEDIUM';
223
+ case 'low':
224
+ return 'LOW';
225
+ case 'info':
226
+ case 'informational':
227
+ return 'INFO';
228
+ default:
229
+ return 'LOW';
230
+ }
231
+ }
232
+ /** Does the project at `cwd` have an npm lockfile? */
233
+ function hasLockfile(cwd) {
234
+ return (fs.existsSync(path.join(cwd, 'package-lock.json')) ||
235
+ fs.existsSync(path.join(cwd, 'npm-shrinkwrap.json')));
236
+ }
237
+ /**
238
+ * Spawn `npm audit --json`, captures both streams, enforces the
239
+ * timeout, and resolves once the child exits (or we kill it).
240
+ */
241
+ function spawnAudit(cwd) {
242
+ return new Promise((resolve, reject) => {
243
+ // On Windows the actual binary is `npm.cmd`. Spawn through the shell
244
+ // so platform differences don't matter. We don't pass any user input
245
+ // through the shell — only constant flags — so this is safe.
246
+ const child = spawn('npm', ['audit', '--json'], {
247
+ cwd,
248
+ shell: process.platform === 'win32',
249
+ // Inherit env so npm picks up the user's registry config / proxy.
250
+ env: process.env,
251
+ });
252
+ let stdout = '';
253
+ let stderr = '';
254
+ let bytes = 0;
255
+ child.stdout.on('data', (chunk) => {
256
+ bytes += chunk.length;
257
+ if (bytes > MAX_STDOUT_BYTES) {
258
+ // Truncate rather than ENOMEM. The first 16 MB of audit output
259
+ // is far more than any real project produces — if we hit this
260
+ // limit it's almost certainly a runaway.
261
+ if (stdout.length < MAX_STDOUT_BYTES) {
262
+ stdout += chunk.toString('utf-8');
263
+ stdout = stdout.slice(0, MAX_STDOUT_BYTES);
264
+ }
265
+ return;
266
+ }
267
+ stdout += chunk.toString('utf-8');
268
+ });
269
+ child.stderr.on('data', (chunk) => {
270
+ // npm warning lines can be plentiful but are short; cap conservatively.
271
+ if (stderr.length < 32 * 1024) {
272
+ stderr += chunk.toString('utf-8');
273
+ }
274
+ });
275
+ let settled = false;
276
+ const settle = (result) => {
277
+ if (settled)
278
+ return;
279
+ settled = true;
280
+ clearTimeout(timer);
281
+ resolve(result);
282
+ };
283
+ const timer = setTimeout(() => {
284
+ if (settled)
285
+ return;
286
+ try {
287
+ child.kill('SIGTERM');
288
+ }
289
+ catch {
290
+ // already gone
291
+ }
292
+ // Give it 250ms to actually die before resolving with what we have.
293
+ setTimeout(() => {
294
+ settle({ stdout, stderr, code: null });
295
+ }, 250);
296
+ }, AUDIT_TIMEOUT_MS);
297
+ child.on('error', (err) => {
298
+ if (settled)
299
+ return;
300
+ settled = true;
301
+ clearTimeout(timer);
302
+ reject(err);
303
+ });
304
+ child.on('close', (code) => {
305
+ settle({ stdout, stderr, code });
306
+ });
307
+ });
308
+ }
309
+ /**
310
+ * Pull the first informative line out of `npm audit` stderr for display.
311
+ * npm prints a lot of `npm warn …` noise we don't want to surface.
312
+ */
313
+ function extractShortError(stderr) {
314
+ const lines = stderr
315
+ .split('\n')
316
+ .map((l) => l.trim())
317
+ .filter((l) => l.length > 0 && !/^npm warn/i.test(l));
318
+ return lines[0] ?? '';
319
+ }
320
+ //# sourceMappingURL=npm-audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm-audit.js","sourceRoot":"","sources":["../../src/security/npm-audit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * On-disk cache for OSV.dev advisory lookups.
3
+ *
4
+ * The OSV batch endpoint is generous but not infinite — repeating a
5
+ * scan in a project with 500+ deps every few minutes would burn quota
6
+ * fast. We cache results in `.studio/security-cache.json` keyed by
7
+ * `(ecosystem, package, version)` with a 6h TTL, so subsequent scans
8
+ * inside the TTL do zero network work for stable deps.
9
+ *
10
+ * The cache is best-effort: any I/O / parse error degrades to a
11
+ * cache-miss rather than failing the scan.
12
+ */
13
+ export interface OsvAdvisory {
14
+ id: string;
15
+ aliases: string[];
16
+ summary: string;
17
+ details?: string;
18
+ severity?: number;
19
+ references: string[];
20
+ fixedVersion?: string;
21
+ }
22
+ export declare class OsvCache {
23
+ private readonly filePath;
24
+ private data;
25
+ private loaded;
26
+ private dirty;
27
+ /**
28
+ * Throttle disk writes — many cache puts inside one scan would
29
+ * otherwise produce many sequential writes. We coalesce into a
30
+ * single fsync at the end of a scan via `flush()`.
31
+ */
32
+ constructor(dbPath: string);
33
+ /** Load cache from disk; safe to call repeatedly. */
34
+ load(): void;
35
+ /**
36
+ * Look up advisories for a package@version. Returns null on miss /
37
+ * expired entry; callers should then fall back to a network query.
38
+ */
39
+ get(ecosystem: string, name: string, version: string): OsvAdvisory[] | null;
40
+ /** Store advisories for a package@version. Flush() persists to disk. */
41
+ put(ecosystem: string, name: string, version: string, advisories: OsvAdvisory[]): void;
42
+ /**
43
+ * Persist any pending writes. Safe to call when there's nothing to
44
+ * flush. Failures are swallowed because the cache is an optimisation
45
+ * — losing it just means the next scan pays one more OSV roundtrip.
46
+ */
47
+ flush(): void;
48
+ /** Drop everything. Exposed for tests / future "clear cache" UI. */
49
+ clear(): void;
50
+ }
51
+ //# sourceMappingURL=osv-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"osv-cache.d.ts","sourceRoot":"","sources":["../../src/security/osv-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAmBH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAWD,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,IAAI,CAAsD;IAClE,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IACtB;;;;OAIG;gBAES,MAAM,EAAE,MAAM;IAM1B,qDAAqD;IACrD,IAAI,IAAI,IAAI;IAeZ;;;OAGG;IACH,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,GAAG,IAAI;IAQ3E,wEAAwE;IACxE,GAAG,CACD,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,WAAW,EAAE,GACxB,IAAI;IASP;;;;OAIG;IACH,KAAK,IAAI,IAAI;IAcb,oEAAoE;IACpE,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * On-disk cache for OSV.dev advisory lookups.
3
+ *
4
+ * The OSV batch endpoint is generous but not infinite — repeating a
5
+ * scan in a project with 500+ deps every few minutes would burn quota
6
+ * fast. We cache results in `.studio/security-cache.json` keyed by
7
+ * `(ecosystem, package, version)` with a 6h TTL, so subsequent scans
8
+ * inside the TTL do zero network work for stable deps.
9
+ *
10
+ * The cache is best-effort: any I/O / parse error degrades to a
11
+ * cache-miss rather than failing the scan.
12
+ */
13
+ import * as fs from 'node:fs';
14
+ import * as path from 'node:path';
15
+ const TTL_MS = 6 * 60 * 60 * 1000; // 6 hours
16
+ const CACHE_VERSION = 1;
17
+ function cacheKey(ecosystem, name, version) {
18
+ return `${ecosystem}|${name}|${version}`;
19
+ }
20
+ export class OsvCache {
21
+ filePath;
22
+ data = { version: CACHE_VERSION, entries: {} };
23
+ loaded = false;
24
+ dirty = false;
25
+ /**
26
+ * Throttle disk writes — many cache puts inside one scan would
27
+ * otherwise produce many sequential writes. We coalesce into a
28
+ * single fsync at the end of a scan via `flush()`.
29
+ */
30
+ constructor(dbPath) {
31
+ // Co-locate with the studio db file so users only need to gitignore
32
+ // one folder (`.studio/`) to opt out of committing tooling state.
33
+ this.filePath = path.join(path.dirname(dbPath), 'security-cache.json');
34
+ }
35
+ /** Load cache from disk; safe to call repeatedly. */
36
+ load() {
37
+ if (this.loaded)
38
+ return;
39
+ this.loaded = true;
40
+ try {
41
+ const raw = fs.readFileSync(this.filePath, 'utf-8');
42
+ const parsed = JSON.parse(raw);
43
+ if (parsed?.version === CACHE_VERSION && parsed.entries) {
44
+ this.data = parsed;
45
+ }
46
+ }
47
+ catch {
48
+ // Missing file or corrupt JSON — start fresh, don't crash the scan.
49
+ }
50
+ }
51
+ /**
52
+ * Look up advisories for a package@version. Returns null on miss /
53
+ * expired entry; callers should then fall back to a network query.
54
+ */
55
+ get(ecosystem, name, version) {
56
+ this.load();
57
+ const entry = this.data.entries[cacheKey(ecosystem, name, version)];
58
+ if (!entry)
59
+ return null;
60
+ if (Date.now() - entry.storedAt > TTL_MS)
61
+ return null;
62
+ return entry.advisories;
63
+ }
64
+ /** Store advisories for a package@version. Flush() persists to disk. */
65
+ put(ecosystem, name, version, advisories) {
66
+ this.load();
67
+ this.data.entries[cacheKey(ecosystem, name, version)] = {
68
+ storedAt: Date.now(),
69
+ advisories,
70
+ };
71
+ this.dirty = true;
72
+ }
73
+ /**
74
+ * Persist any pending writes. Safe to call when there's nothing to
75
+ * flush. Failures are swallowed because the cache is an optimisation
76
+ * — losing it just means the next scan pays one more OSV roundtrip.
77
+ */
78
+ flush() {
79
+ if (!this.dirty)
80
+ return;
81
+ try {
82
+ const dir = path.dirname(this.filePath);
83
+ if (!fs.existsSync(dir)) {
84
+ fs.mkdirSync(dir, { recursive: true });
85
+ }
86
+ fs.writeFileSync(this.filePath, JSON.stringify(this.data));
87
+ this.dirty = false;
88
+ }
89
+ catch {
90
+ // best-effort
91
+ }
92
+ }
93
+ /** Drop everything. Exposed for tests / future "clear cache" UI. */
94
+ clear() {
95
+ this.data = { version: CACHE_VERSION, entries: {} };
96
+ this.dirty = true;
97
+ }
98
+ }
99
+ //# sourceMappingURL=osv-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"osv-cache.js","sourceRoot":"","sources":["../../src/security/osv-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * OSV.dev client — supply-chain advisory lookups for installed packages.
3
+ *
4
+ * Why OSV: it's an open vulnerability database maintained by Google,
5
+ * exposes a programmatic JSON API, doesn't require auth, and aggregates
6
+ * GHSA, npm, OSS-Fuzz and other sources behind one query shape. It's
7
+ * also the source `npm audit` increasingly delegates to internally, so
8
+ * we get the same data without scraping anything.
9
+ *
10
+ * This client batches per-scan: one POST to `/v1/querybatch` covering
11
+ * every direct dependency. Results that aren't already in cache are
12
+ * persisted via `OsvCache` so subsequent scans inside the TTL skip the
13
+ * network entirely.
14
+ *
15
+ * Network failures degrade gracefully: we return an empty findings
16
+ * array so the engine can still ship a report using only `npm audit`.
17
+ */
18
+ import type { DependencyFinding } from '../types/index.js';
19
+ import { OsvCache } from './osv-cache.js';
20
+ export interface PackageQuery {
21
+ name: string;
22
+ version: string;
23
+ }
24
+ export declare class OsvClient {
25
+ private readonly cache;
26
+ constructor(cache: OsvCache);
27
+ /**
28
+ * Look up advisories for every (package, version) pair and return
29
+ * normalised findings. Cache hits are served without touching the
30
+ * network; the rest are batched into one (or more, if very large)
31
+ * POSTs to OSV.
32
+ */
33
+ lookup(packages: PackageQuery[]): Promise<DependencyFinding[]>;
34
+ /**
35
+ * POST a batch of queries to OSV's `/v1/querybatch`. Splits oversize
36
+ * input into multiple requests to stay under the per-call limit.
37
+ */
38
+ private fetchAdvisoryIds;
39
+ /**
40
+ * Hydrate advisory id list into full records. OSV requires one GET
41
+ * per id (no batch endpoint for full records yet) — we run them in
42
+ * parallel with a hard concurrency cap so we don't open hundreds of
43
+ * sockets at once.
44
+ */
45
+ private fetchFullAdvisories;
46
+ }
47
+ //# sourceMappingURL=osv-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"osv-client.d.ts","sourceRoot":"","sources":["../../src/security/osv-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAY,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAoB,MAAM,gBAAgB,CAAC;AAQ5D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAyBD,qBAAa,SAAS;IACR,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,QAAQ;IAE5C;;;;;OAKG;IACG,MAAM,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAqDpE;;;OAGG;YACW,gBAAgB;IAsC9B;;;;;OAKG;YACW,mBAAmB;CA2BlC"}