@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,302 @@
1
+ /**
2
+ * Runtime reachability analysis for supply-chain findings.
3
+ *
4
+ * This is the single feature that justifies "why Studio over Snyk?". A
5
+ * static scanner can tell you that `lodash@4.17.10` has CVE-X; only a
6
+ * tool sitting inside the running app can tell you that:
7
+ *
8
+ * - `src/users/users.service.ts` actually imports lodash
9
+ * - that service is wired into `UsersController`, which owns `GET /users`
10
+ * - and `GET /users` has been hit 47 times in the last hour
11
+ *
12
+ * We combine three signals to land on one of four labels per finding:
13
+ *
14
+ * - **confirmed**: a route that imports (transitively) the vulnerable
15
+ * package has been exercised in the current session.
16
+ * - **likely**: the package is imported from `src/` but we haven't
17
+ * seen a request hit a route that reaches it yet.
18
+ * - **unreachable**: we have a complete import graph for `src/` and
19
+ * the package doesn't appear anywhere. Usually means it's only
20
+ * pulled in by a dev tool or another transitive dep.
21
+ * - **unknown**: we can't compute the graph (no src dir, scanning
22
+ * disabled). Default for transitive packages with no direct
23
+ * imports — we don't follow node_modules → node_modules edges.
24
+ *
25
+ * The analyzer is intentionally cheap: regex-based import extraction
26
+ * over the same source tree the route scanner already walked. We
27
+ * don't reach for a TypeScript AST — it'd be 100× slower for almost
28
+ * no precision gain at this granularity (we only need package names,
29
+ * not symbols).
30
+ */
31
+ import * as fs from 'node:fs';
32
+ import * as path from 'node:path';
33
+ import { glob } from 'glob';
34
+ const IMPORT_PATTERNS = [
35
+ // ESM: import ... from 'pkg' / import 'pkg'
36
+ /import\s+(?:[^'"]*?\s+from\s+)?['"]([^'"]+)['"]/g,
37
+ // Re-exports: export ... from 'pkg'
38
+ /export\s+(?:[^'"]*?\s+from\s+)?['"]([^'"]+)['"]/g,
39
+ // Dynamic import: import('pkg')
40
+ /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
41
+ // CJS: require('pkg')
42
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
43
+ ];
44
+ /**
45
+ * Build a reachability snapshot for the host project. Returns an empty
46
+ * snapshot if `srcPath` doesn't exist — callers should treat that as
47
+ * "always emit `unknown`" rather than failing.
48
+ *
49
+ * Safe to call on every full scan; for a 1000-file project it usually
50
+ * completes in <100 ms.
51
+ */
52
+ export async function buildReachabilitySnapshot(cwd, structure) {
53
+ const srcPath = findSrcPath(cwd);
54
+ const importedByPkg = new Map();
55
+ const filesByImports = new Map();
56
+ if (!srcPath) {
57
+ return { importedByPkg, routesByFile: new Map() };
58
+ }
59
+ const files = await glob('**/*.{ts,tsx,js,mjs,cjs}', {
60
+ cwd: srcPath,
61
+ ignore: ['**/node_modules/**', '**/*.spec.ts', '**/*.test.ts', '**/*.d.ts'],
62
+ absolute: true,
63
+ });
64
+ for (const file of files) {
65
+ let content;
66
+ try {
67
+ content = fs.readFileSync(file, 'utf-8');
68
+ }
69
+ catch {
70
+ continue;
71
+ }
72
+ const pkgs = extractPackageImports(content);
73
+ if (pkgs.size === 0)
74
+ continue;
75
+ filesByImports.set(file, pkgs);
76
+ for (const pkg of pkgs) {
77
+ const existing = importedByPkg.get(pkg) ?? new Set();
78
+ existing.add(file);
79
+ importedByPkg.set(pkg, existing);
80
+ }
81
+ }
82
+ const routesByFile = buildRoutesByFile(structure, filesByImports);
83
+ return { importedByPkg, routesByFile };
84
+ }
85
+ /**
86
+ * Attach a `ReachabilityInfo` block to every finding in `findings`.
87
+ * Pure given the snapshot + exchanges; same call with the same inputs
88
+ * always produces the same output, which keeps hashing stable.
89
+ */
90
+ export function enrichWithReachability(findings, snapshot, exchanges) {
91
+ // Index exchanges by `method:path` so we can count hits per route
92
+ // in O(N) over routes regardless of exchange count.
93
+ const exchangeCountsByRoute = new Map();
94
+ for (const ex of exchanges) {
95
+ const method = ex.request.method;
96
+ const matchPath = ex.request.path || ex.request.url;
97
+ if (!matchPath)
98
+ continue;
99
+ const key = `${method}:${normalisePath(matchPath)}`;
100
+ exchangeCountsByRoute.set(key, (exchangeCountsByRoute.get(key) ?? 0) + 1);
101
+ }
102
+ return findings.map((finding) => {
103
+ // For transitive findings, prefer the root package — that's the
104
+ // one the user's source actually imports. We fall back to the
105
+ // vulnerable package name when no root cause is set (direct dep
106
+ // or unknown lockfile).
107
+ const probe = finding.rootCause?.rootPackage ?? finding.package;
108
+ const info = computeReachability(probe, snapshot, exchangeCountsByRoute, Boolean(finding.rootCause?.isDirect ?? snapshot.importedByPkg.has(probe)));
109
+ return { ...finding, reachability: info };
110
+ });
111
+ }
112
+ // ────────────────────────────────────────────────────────────────────────
113
+ // Internals
114
+ // ────────────────────────────────────────────────────────────────────────
115
+ function computeReachability(pkg, snapshot, exchangeCounts, hasGraph) {
116
+ const importers = snapshot.importedByPkg.get(pkg);
117
+ if (!importers || importers.size === 0) {
118
+ if (!hasGraph && snapshot.importedByPkg.size === 0) {
119
+ // We have no source-graph data at all (e.g. no `src/` dir,
120
+ // analyzer disabled). Be honest about it rather than calling
121
+ // every dep "unreachable".
122
+ return {
123
+ level: 'unknown',
124
+ importedBy: [],
125
+ routes: [],
126
+ runtimeHits: 0,
127
+ reason: 'No source graph available — Studio could not analyse `src/`.',
128
+ };
129
+ }
130
+ if (snapshot.importedByPkg.size > 0) {
131
+ // We scanned src/ successfully and the package never appears.
132
+ // For transitive packages this is the common case: they're
133
+ // only used internally by other deps.
134
+ return {
135
+ level: 'unreachable',
136
+ importedBy: [],
137
+ routes: [],
138
+ runtimeHits: 0,
139
+ reason: 'No source files import this package directly.',
140
+ };
141
+ }
142
+ return {
143
+ level: 'unknown',
144
+ importedBy: [],
145
+ routes: [],
146
+ runtimeHits: 0,
147
+ reason: 'No matching source files found.',
148
+ };
149
+ }
150
+ const importedBy = Array.from(importers);
151
+ const routes = new Map();
152
+ for (const file of importedBy) {
153
+ const fileRoutes = snapshot.routesByFile.get(file) ?? [];
154
+ for (const r of fileRoutes) {
155
+ routes.set(`${r.method}:${r.path}`, r);
156
+ }
157
+ }
158
+ const routesArr = Array.from(routes.values()).map((r) => ({
159
+ method: r.method,
160
+ path: r.path,
161
+ }));
162
+ let hits = 0;
163
+ for (const r of routesArr) {
164
+ hits += exchangeCounts.get(`${r.method}:${normalisePath(r.path)}`) ?? 0;
165
+ }
166
+ let level;
167
+ let reason;
168
+ if (hits > 0) {
169
+ level = 'confirmed';
170
+ reason = `${routesArr.length} route${routesArr.length === 1 ? '' : 's'} reach this package and ${hits} request${hits === 1 ? '' : 's'} hit them in the current session.`;
171
+ }
172
+ else if (routesArr.length > 0) {
173
+ level = 'likely';
174
+ reason = `${routesArr.length} route${routesArr.length === 1 ? '' : 's'} reach this package, but none have been exercised yet.`;
175
+ }
176
+ else {
177
+ // Imported, but we couldn't tie it back to any route — could be a
178
+ // utility module, a bootstrap helper, or a worker. Still "likely"
179
+ // because the package *is* in your code path.
180
+ level = 'likely';
181
+ reason = `Imported by ${importedBy.length} file${importedBy.length === 1 ? '' : 's'} but no route reaches it directly.`;
182
+ }
183
+ return {
184
+ level,
185
+ importedBy,
186
+ routes: routesArr,
187
+ runtimeHits: hits,
188
+ reason,
189
+ };
190
+ }
191
+ /**
192
+ * Walk `content` looking for bare-package imports. Bare = not relative
193
+ * (`./foo`) and not absolute (`/foo`). Returns deduped package names
194
+ * (scoped packages keep their `@scope/` prefix).
195
+ */
196
+ function extractPackageImports(content) {
197
+ const out = new Set();
198
+ for (const re of IMPORT_PATTERNS) {
199
+ re.lastIndex = 0;
200
+ let m;
201
+ while ((m = re.exec(content)) !== null) {
202
+ const spec = m[1];
203
+ if (!spec || spec.startsWith('.') || spec.startsWith('/'))
204
+ continue;
205
+ // Strip subpath: `foo/bar/baz` → `foo`, `@scope/pkg/sub` → `@scope/pkg`.
206
+ const parts = spec.split('/');
207
+ const name = spec.startsWith('@')
208
+ ? parts.slice(0, 2).join('/')
209
+ : parts[0];
210
+ if (name)
211
+ out.add(name);
212
+ }
213
+ }
214
+ return out;
215
+ }
216
+ /**
217
+ * Resolve `src/` for the host project. We try a few common spellings
218
+ * because not every ExpressoTS app uses literal `./src` (monorepo
219
+ * roots sometimes don't).
220
+ */
221
+ function findSrcPath(cwd) {
222
+ const candidates = ['src', 'lib', 'app'];
223
+ for (const c of candidates) {
224
+ const full = path.join(cwd, c);
225
+ if (fs.existsSync(full) && fs.statSync(full).isDirectory()) {
226
+ return full;
227
+ }
228
+ }
229
+ return null;
230
+ }
231
+ /**
232
+ * Reverse-index controllers/services by file so each importing file
233
+ * can be answered "which routes does this serve?".
234
+ *
235
+ * The mapping isn't perfect (an imported util file may not be a
236
+ * controller itself), so we also fold in transitive reach: if a
237
+ * service file imports lodash and a controller depends on that
238
+ * service, the controller's routes are credited.
239
+ */
240
+ function buildRoutesByFile(structure, filesByImports) {
241
+ const out = new Map();
242
+ if (!structure)
243
+ return out;
244
+ const controllersByFile = new Map();
245
+ for (const c of structure.controllers) {
246
+ controllersByFile.set(normalisePath(c.filePath), c);
247
+ }
248
+ const servicesByName = new Map();
249
+ for (const s of [...structure.services, ...structure.providers]) {
250
+ servicesByName.set(s.name, s);
251
+ }
252
+ // 1. Direct: a controller's own file gets all of its routes.
253
+ for (const c of structure.controllers) {
254
+ out.set(c.filePath, c.routes.slice());
255
+ }
256
+ // 2. Transitive: walk each controller's dependency graph and credit
257
+ // every file reachable through services it imports.
258
+ for (const c of structure.controllers) {
259
+ const visited = new Set();
260
+ const queue = [...c.dependencies];
261
+ while (queue.length > 0) {
262
+ const dep = queue.shift();
263
+ if (visited.has(dep))
264
+ continue;
265
+ visited.add(dep);
266
+ const svc = servicesByName.get(dep);
267
+ if (!svc)
268
+ continue;
269
+ // Credit the service's own file with the controller's routes.
270
+ const existing = out.get(svc.filePath) ?? [];
271
+ out.set(svc.filePath, mergeRoutes(existing, c.routes));
272
+ for (const next of svc.dependencies ?? [])
273
+ queue.push(next);
274
+ }
275
+ }
276
+ // 3. Mark every other source file we saw imports for as having no
277
+ // routes (so `unreachable` becomes the natural label when nothing
278
+ // references it).
279
+ for (const file of filesByImports.keys()) {
280
+ if (!out.has(file))
281
+ out.set(file, []);
282
+ }
283
+ return out;
284
+ }
285
+ function mergeRoutes(into, add) {
286
+ const seen = new Set(into.map((r) => `${r.method}:${r.path}`));
287
+ for (const r of add) {
288
+ const k = `${r.method}:${r.path}`;
289
+ if (!seen.has(k)) {
290
+ into.push(r);
291
+ seen.add(k);
292
+ }
293
+ }
294
+ return into;
295
+ }
296
+ /** Strip query string / trailing slash so route-key matching is stable. */
297
+ function normalisePath(p) {
298
+ const noQuery = p.split('?')[0] ?? p;
299
+ const trimmed = noQuery.replace(/\/+$/, '');
300
+ return trimmed || '/';
301
+ }
302
+ //# sourceMappingURL=reachability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reachability.js","sourceRoot":"","sources":["../../src/security/reachability.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Aggregate scoring for the security report.
3
+ *
4
+ * Distils a heterogeneous list of findings into a single letter grade
5
+ * (A..F) so the StatusDashboard card can show a tight summary, and the
6
+ * UI can sort/route on it. Also bundles the per-severity counts that
7
+ * the Security view consumes for its severity-grouped sections.
8
+ *
9
+ * The grading function is *not* CVSS-aware on purpose — a project with
10
+ * one critical CVE that has no exploit in the wild shouldn't be
11
+ * indistinguishable from one with ten. We follow these rules instead:
12
+ *
13
+ * F → any CRITICAL severity finding (supply-chain or posture)
14
+ * D → ≥3 HIGH, or ≥1 HIGH supply-chain
15
+ * C → any HIGH, or ≥5 MEDIUM
16
+ * B → any MEDIUM
17
+ * A → only LOW / INFO (or zero findings)
18
+ */
19
+ import type { DependencyFinding, FixGroup, PostureFinding, SecurityReport, Severity } from '../types/index.js';
20
+ declare const SEVERITY_KEYS: Severity[];
21
+ export declare function buildSecurityReport(args: {
22
+ dependencies: DependencyFinding[];
23
+ posture: PostureFinding[];
24
+ fixGroups: FixGroup[];
25
+ scanState: SecurityReport['scanState'];
26
+ }): SecurityReport;
27
+ /**
28
+ * Hash the set of finding ids. The engine uses this to decide whether
29
+ * a freshly-built report actually differs from the last one — we only
30
+ * broadcast on transitions to keep WS traffic flat for stable apps.
31
+ */
32
+ export declare function hashFindingIds(report: SecurityReport): string;
33
+ /** Empty / "nothing recorded yet" baseline used before the first scan completes. */
34
+ export declare function emptyReport(scanState: SecurityReport['scanState']): SecurityReport;
35
+ export { SEVERITY_KEYS };
36
+ //# sourceMappingURL=score.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.d.ts","sourceRoot":"","sources":["../../src/security/score.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,QAAQ,EACR,cAAc,EACd,cAAc,EACd,QAAQ,EACT,MAAM,mBAAmB,CAAC;AAE3B,QAAA,MAAM,aAAa,EAAE,QAAQ,EAAkD,CAAC;AAEhF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC;CACxC,GAAG,cAAc,CAqBjB;AAcD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAU7D;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,GAAG,cAAc,CAiBlF;AAID,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Aggregate scoring for the security report.
3
+ *
4
+ * Distils a heterogeneous list of findings into a single letter grade
5
+ * (A..F) so the StatusDashboard card can show a tight summary, and the
6
+ * UI can sort/route on it. Also bundles the per-severity counts that
7
+ * the Security view consumes for its severity-grouped sections.
8
+ *
9
+ * The grading function is *not* CVSS-aware on purpose — a project with
10
+ * one critical CVE that has no exploit in the wild shouldn't be
11
+ * indistinguishable from one with ten. We follow these rules instead:
12
+ *
13
+ * F → any CRITICAL severity finding (supply-chain or posture)
14
+ * D → ≥3 HIGH, or ≥1 HIGH supply-chain
15
+ * C → any HIGH, or ≥5 MEDIUM
16
+ * B → any MEDIUM
17
+ * A → only LOW / INFO (or zero findings)
18
+ */
19
+ const SEVERITY_KEYS = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO'];
20
+ export function buildSecurityReport(args) {
21
+ const counts = {
22
+ CRITICAL: 0,
23
+ HIGH: 0,
24
+ MEDIUM: 0,
25
+ LOW: 0,
26
+ INFO: 0,
27
+ };
28
+ for (const f of args.dependencies)
29
+ counts[f.severity]++;
30
+ for (const f of args.posture)
31
+ counts[f.severity]++;
32
+ return {
33
+ generatedAt: Date.now(),
34
+ score: gradeFromCounts(counts, args.dependencies),
35
+ counts,
36
+ dependencies: args.dependencies,
37
+ posture: args.posture,
38
+ fixGroups: args.fixGroups,
39
+ scanState: args.scanState,
40
+ };
41
+ }
42
+ function gradeFromCounts(counts, dependencies) {
43
+ if (counts.CRITICAL > 0)
44
+ return 'F';
45
+ const supplyChainHigh = dependencies.filter((d) => d.severity === 'HIGH').length;
46
+ if (counts.HIGH >= 3 || supplyChainHigh >= 1)
47
+ return 'D';
48
+ if (counts.HIGH > 0 || counts.MEDIUM >= 5)
49
+ return 'C';
50
+ if (counts.MEDIUM > 0)
51
+ return 'B';
52
+ return 'A';
53
+ }
54
+ /**
55
+ * Hash the set of finding ids. The engine uses this to decide whether
56
+ * a freshly-built report actually differs from the last one — we only
57
+ * broadcast on transitions to keep WS traffic flat for stable apps.
58
+ */
59
+ export function hashFindingIds(report) {
60
+ // The hash is intentionally simple — sort to get a stable order
61
+ // (analysis passes may emit findings in non-deterministic order if
62
+ // they iterate Maps), then join. Avoids the cost of a crypto digest
63
+ // when we just need a comparable string.
64
+ const ids = [];
65
+ for (const f of report.dependencies)
66
+ ids.push(`d:${f.id}`);
67
+ for (const f of report.posture)
68
+ ids.push(`p:${f.id}`);
69
+ ids.sort();
70
+ return ids.join('|');
71
+ }
72
+ /** Empty / "nothing recorded yet" baseline used before the first scan completes. */
73
+ export function emptyReport(scanState) {
74
+ const counts = {
75
+ CRITICAL: 0,
76
+ HIGH: 0,
77
+ MEDIUM: 0,
78
+ LOW: 0,
79
+ INFO: 0,
80
+ };
81
+ return {
82
+ generatedAt: Date.now(),
83
+ score: 'A',
84
+ counts,
85
+ dependencies: [],
86
+ posture: [],
87
+ fixGroups: [],
88
+ scanState,
89
+ };
90
+ }
91
+ // Re-exported so consumers don't have to mention the type module twice
92
+ // when iterating counts.
93
+ export { SEVERITY_KEYS };
94
+ //# sourceMappingURL=score.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.js","sourceRoot":"","sources":["../../src/security/score.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAUH,MAAM,aAAa,GAAe,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AAEhF,MAAM,UAAU,mBAAmB,CAAC,IAKnC;IACC,MAAM,MAAM,GAA6B;QACvC,QAAQ,EAAE,CAAC;QACX,IAAI,EAAE,CAAC;QACP,MAAM,EAAE,CAAC;QACT,GAAG,EAAE,CAAC;QACN,IAAI,EAAE,CAAC;KACR,CAAC;IAEF,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,YAAY;QAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;IACxD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO;QAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;IAEnD,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE;QACvB,KAAK,EAAE,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC;QACjD,MAAM;QACN,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,SAAS,EAAE,IAAI,CAAC,SAAS;KAC1B,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CACtB,MAAgC,EAChC,YAAiC;IAEjC,IAAI,MAAM,CAAC,QAAQ,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC;IACpC,MAAM,eAAe,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IACjF,IAAI,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,eAAe,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC;IACzD,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC;IACtD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC;IAClC,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,MAAsB;IACnD,gEAAgE;IAChE,mEAAmE;IACnE,oEAAoE;IACpE,yCAAyC;IACzC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,YAAY;QAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC3D,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO;QAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACtD,GAAG,CAAC,IAAI,EAAE,CAAC;IACX,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,WAAW,CAAC,SAAsC;IAChE,MAAM,MAAM,GAA6B;QACvC,QAAQ,EAAE,CAAC;QACX,IAAI,EAAE,CAAC;QACP,MAAM,EAAE,CAAC;QACT,GAAG,EAAE,CAAC;QACN,IAAI,EAAE,CAAC;KACR,CAAC;IACF,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE;QACvB,KAAK,EAAE,GAAG;QACV,MAAM;QACN,YAAY,EAAE,EAAE;QAChB,OAAO,EAAE,EAAE;QACX,SAAS,EAAE,EAAE;QACb,SAAS;KACV,CAAC;AACJ,CAAC;AAED,uEAAuE;AACvE,yBAAyB;AACzB,OAAO,EAAE,aAAa,EAAE,CAAC"}