@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,247 @@
|
|
|
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
|
+
const OSV_BATCH_URL = 'https://api.osv.dev/v1/querybatch';
|
|
19
|
+
const OSV_VULN_URL = 'https://api.osv.dev/v1/vulns';
|
|
20
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
21
|
+
/** OSV's batch endpoint accepts up to 1000 queries per request. */
|
|
22
|
+
const MAX_BATCH_SIZE = 500;
|
|
23
|
+
export class OsvClient {
|
|
24
|
+
cache;
|
|
25
|
+
constructor(cache) {
|
|
26
|
+
this.cache = cache;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Look up advisories for every (package, version) pair and return
|
|
30
|
+
* normalised findings. Cache hits are served without touching the
|
|
31
|
+
* network; the rest are batched into one (or more, if very large)
|
|
32
|
+
* POSTs to OSV.
|
|
33
|
+
*/
|
|
34
|
+
async lookup(packages) {
|
|
35
|
+
if (packages.length === 0)
|
|
36
|
+
return [];
|
|
37
|
+
const findings = [];
|
|
38
|
+
const cacheMisses = [];
|
|
39
|
+
// Phase 1: serve from cache.
|
|
40
|
+
for (const pkg of packages) {
|
|
41
|
+
const cached = this.cache.get('npm', pkg.name, pkg.version);
|
|
42
|
+
if (cached) {
|
|
43
|
+
findings.push(...advisoriesToFindings(cached, pkg));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
cacheMisses.push(pkg);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (cacheMisses.length === 0)
|
|
50
|
+
return findings;
|
|
51
|
+
// Phase 2: batch-query OSV for the misses.
|
|
52
|
+
let advisoryIdsByPkg;
|
|
53
|
+
try {
|
|
54
|
+
advisoryIdsByPkg = await this.fetchAdvisoryIds(cacheMisses);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Network failure → cache nothing, fall back to whatever cache hits
|
|
58
|
+
// we already produced. Better than refusing to ship a report.
|
|
59
|
+
return findings;
|
|
60
|
+
}
|
|
61
|
+
// Phase 3: hydrate each unique advisory id. OSV returns just an id
|
|
62
|
+
// list from the batch endpoint — we have to fetch full records to
|
|
63
|
+
// get severity, summary, references etc.
|
|
64
|
+
const uniqueIds = new Set();
|
|
65
|
+
for (const ids of advisoryIdsByPkg.values()) {
|
|
66
|
+
ids.forEach((id) => uniqueIds.add(id));
|
|
67
|
+
}
|
|
68
|
+
const fullAdvisories = await this.fetchFullAdvisories([...uniqueIds]);
|
|
69
|
+
// Phase 4: rebuild per-package advisory lists and store in cache.
|
|
70
|
+
for (const pkg of cacheMisses) {
|
|
71
|
+
const ids = advisoryIdsByPkg.get(pkgKey(pkg)) ?? [];
|
|
72
|
+
const advisories = ids
|
|
73
|
+
.map((id) => fullAdvisories.get(id))
|
|
74
|
+
.filter((v) => Boolean(v))
|
|
75
|
+
.map((v) => fullToCacheAdvisory(v, pkg.name));
|
|
76
|
+
this.cache.put('npm', pkg.name, pkg.version, advisories);
|
|
77
|
+
findings.push(...advisoriesToFindings(advisories, pkg));
|
|
78
|
+
}
|
|
79
|
+
this.cache.flush();
|
|
80
|
+
return findings;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* POST a batch of queries to OSV's `/v1/querybatch`. Splits oversize
|
|
84
|
+
* input into multiple requests to stay under the per-call limit.
|
|
85
|
+
*/
|
|
86
|
+
async fetchAdvisoryIds(queries) {
|
|
87
|
+
const out = new Map();
|
|
88
|
+
for (let i = 0; i < queries.length; i += MAX_BATCH_SIZE) {
|
|
89
|
+
const batch = queries.slice(i, i + MAX_BATCH_SIZE);
|
|
90
|
+
const body = {
|
|
91
|
+
queries: batch.map((q) => ({
|
|
92
|
+
package: { name: q.name, ecosystem: 'npm' },
|
|
93
|
+
version: q.version,
|
|
94
|
+
})),
|
|
95
|
+
};
|
|
96
|
+
const resp = await fetchWithTimeout(OSV_BATCH_URL, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: { 'content-type': 'application/json' },
|
|
99
|
+
body: JSON.stringify(body),
|
|
100
|
+
});
|
|
101
|
+
if (!resp.ok) {
|
|
102
|
+
// Treat as empty results for this batch; the caller will simply
|
|
103
|
+
// produce no findings for these packages.
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const parsed = (await resp.json());
|
|
107
|
+
const results = parsed.results ?? [];
|
|
108
|
+
for (let j = 0; j < batch.length; j++) {
|
|
109
|
+
const pkg = batch[j];
|
|
110
|
+
const ids = (results[j]?.vulns ?? []).map((v) => v.id);
|
|
111
|
+
out.set(pkgKey(pkg), ids);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Hydrate advisory id list into full records. OSV requires one GET
|
|
118
|
+
* per id (no batch endpoint for full records yet) — we run them in
|
|
119
|
+
* parallel with a hard concurrency cap so we don't open hundreds of
|
|
120
|
+
* sockets at once.
|
|
121
|
+
*/
|
|
122
|
+
async fetchFullAdvisories(ids) {
|
|
123
|
+
const out = new Map();
|
|
124
|
+
if (ids.length === 0)
|
|
125
|
+
return out;
|
|
126
|
+
const CONCURRENCY = 8;
|
|
127
|
+
let cursor = 0;
|
|
128
|
+
const workers = Array.from({ length: Math.min(CONCURRENCY, ids.length) }, async () => {
|
|
129
|
+
while (cursor < ids.length) {
|
|
130
|
+
const idx = cursor++;
|
|
131
|
+
const id = ids[idx];
|
|
132
|
+
try {
|
|
133
|
+
const resp = await fetchWithTimeout(`${OSV_VULN_URL}/${encodeURIComponent(id)}`);
|
|
134
|
+
if (!resp.ok)
|
|
135
|
+
continue;
|
|
136
|
+
const json = (await resp.json());
|
|
137
|
+
out.set(id, json);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Skip — partial coverage is fine.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
await Promise.all(workers);
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function pkgKey(p) {
|
|
149
|
+
return `${p.name}@${p.version}`;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Translate the cached / hydrated advisory into our wire-stable
|
|
153
|
+
* `DependencyFinding` shape. Severity is the trickiest bit — OSV stores
|
|
154
|
+
* CVSS vectors rather than the npm-style word ("CRITICAL"); we map both.
|
|
155
|
+
*/
|
|
156
|
+
function advisoriesToFindings(advisories, pkg) {
|
|
157
|
+
return advisories.map((adv) => ({
|
|
158
|
+
id: adv.id,
|
|
159
|
+
package: pkg.name,
|
|
160
|
+
installedVersion: pkg.version,
|
|
161
|
+
fixedVersion: adv.fixedVersion,
|
|
162
|
+
severity: numericSeverityToLabel(adv.severity),
|
|
163
|
+
cvss: adv.severity,
|
|
164
|
+
title: adv.summary || adv.id,
|
|
165
|
+
summary: adv.details ?? adv.summary ?? '',
|
|
166
|
+
references: adv.references,
|
|
167
|
+
path: [pkg.name],
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
function fullToCacheAdvisory(full, pkgName) {
|
|
171
|
+
return {
|
|
172
|
+
id: full.id,
|
|
173
|
+
aliases: full.aliases ?? [],
|
|
174
|
+
summary: full.summary ?? '',
|
|
175
|
+
details: full.details,
|
|
176
|
+
severity: extractCvssScore(full),
|
|
177
|
+
references: (full.references ?? []).map((r) => r.url).filter(Boolean),
|
|
178
|
+
fixedVersion: extractFixedVersion(full, pkgName),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/** Pull a CVSS v3.x base score out of an OSV record, if present. */
|
|
182
|
+
function extractCvssScore(full) {
|
|
183
|
+
for (const s of full.severity ?? []) {
|
|
184
|
+
if (s.type === 'CVSS_V3' || s.type === 'CVSS_V4') {
|
|
185
|
+
// OSV stores the vector string; the base score is the first
|
|
186
|
+
// numeric value after the vector header. We don't ship a full
|
|
187
|
+
// CVSS parser — just look for a familiar "/AV:..." vector and
|
|
188
|
+
// pull the base score from `database_specific` when present.
|
|
189
|
+
const numeric = Number((full.database_specific?.severity ?? '').match(/(\d+\.\d+)/)?.[1]);
|
|
190
|
+
if (Number.isFinite(numeric))
|
|
191
|
+
return numeric;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Find the first `fixed` version for the given package across all
|
|
198
|
+
* `affected` ranges. OSV records list every event (introduced / fixed)
|
|
199
|
+
* in order; we use the first `fixed` that follows an `introduced`.
|
|
200
|
+
*/
|
|
201
|
+
function extractFixedVersion(full, pkgName) {
|
|
202
|
+
for (const aff of full.affected ?? []) {
|
|
203
|
+
if (aff.package?.name !== pkgName)
|
|
204
|
+
continue;
|
|
205
|
+
for (const range of aff.ranges ?? []) {
|
|
206
|
+
for (const ev of range.events ?? []) {
|
|
207
|
+
if (ev.fixed)
|
|
208
|
+
return ev.fixed;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Map a numeric CVSS base score to our severity vocabulary using the
|
|
216
|
+
* standard NVD bands. Falls back to `MEDIUM` when no score is known so
|
|
217
|
+
* findings don't all collapse into `INFO`.
|
|
218
|
+
*/
|
|
219
|
+
function numericSeverityToLabel(score) {
|
|
220
|
+
if (typeof score !== 'number' || !Number.isFinite(score))
|
|
221
|
+
return 'MEDIUM';
|
|
222
|
+
if (score >= 9.0)
|
|
223
|
+
return 'CRITICAL';
|
|
224
|
+
if (score >= 7.0)
|
|
225
|
+
return 'HIGH';
|
|
226
|
+
if (score >= 4.0)
|
|
227
|
+
return 'MEDIUM';
|
|
228
|
+
if (score > 0)
|
|
229
|
+
return 'LOW';
|
|
230
|
+
return 'INFO';
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* `fetch` with an `AbortController` timeout. Centralised here so every
|
|
234
|
+
* OSV call has the same timeout semantics — a network hang must not be
|
|
235
|
+
* able to stall the agent's WS event loop.
|
|
236
|
+
*/
|
|
237
|
+
async function fetchWithTimeout(url, init) {
|
|
238
|
+
const controller = new AbortController();
|
|
239
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
240
|
+
try {
|
|
241
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
clearTimeout(timer);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=osv-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"osv-client.js","sourceRoot":"","sources":["../../src/security/osv-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime security posture analyzer.
|
|
3
|
+
*
|
|
4
|
+
* This is Studio's unique contribution: Snyk-style scanners only see
|
|
5
|
+
* `package.json`, but Studio has the recorded traffic, the routes, the
|
|
6
|
+
* DI graph and live logs. The analyzer turns all of that into a set
|
|
7
|
+
* of OWASP-mapped findings the user can act on directly from the UI.
|
|
8
|
+
*
|
|
9
|
+
* Rules implemented here are intentionally conservative — false
|
|
10
|
+
* positives erode trust quickly in a security tool. Every rule has
|
|
11
|
+
* either (a) a high-confidence runtime signal (e.g. literal
|
|
12
|
+
* `Access-Control-Allow-Origin: *`) or (b) an explicit "advisory"
|
|
13
|
+
* severity so users know the finding is heuristic.
|
|
14
|
+
*
|
|
15
|
+
* All passes are pure functions over a snapshot of in-memory state.
|
|
16
|
+
* The engine is responsible for debouncing and change detection.
|
|
17
|
+
*/
|
|
18
|
+
import type { AppStructure, PostureFinding, PostureEvidence, RecordedExchange, RouteInfo } from '../types/index.js';
|
|
19
|
+
import type { LogEntry } from '../logging/log-capture.js';
|
|
20
|
+
/**
|
|
21
|
+
* Inputs the analyzer needs. We accept them as an opaque object so the
|
|
22
|
+
* engine can compose its in-memory state without exposing the agent
|
|
23
|
+
* itself to the rules.
|
|
24
|
+
*/
|
|
25
|
+
export interface PostureInputs {
|
|
26
|
+
routes: RouteInfo[];
|
|
27
|
+
structure: AppStructure | null;
|
|
28
|
+
exchanges: RecordedExchange[];
|
|
29
|
+
logs: LogEntry[];
|
|
30
|
+
/**
|
|
31
|
+
* Resolved absolute path to the host project's source root (best-effort).
|
|
32
|
+
* Some checks (e.g. "missing helmet" / "no validation") want to read
|
|
33
|
+
* `app.ts` / `main.ts`. We pass it in rather than discovering inside
|
|
34
|
+
* the analyzer so tests can override it.
|
|
35
|
+
*/
|
|
36
|
+
srcRoot?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Run every posture check over the given snapshot. Returns an
|
|
40
|
+
* unsorted list — the engine aggregates and dedupes by `id`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function analyzePosture(input: PostureInputs): PostureFinding[];
|
|
43
|
+
export type { PostureEvidence };
|
|
44
|
+
//# sourceMappingURL=posture-analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"posture-analyzer.d.ts","sourceRoot":"","sources":["../../src/security/posture-analyzer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EACV,YAAY,EAEZ,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,SAAS,EAEV,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAG1D;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,SAAS,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,EAAE,CAWrE;AA4ZD,YAAY,EAAE,eAAe,EAAE,CAAC"}
|