@claude-flow/security 3.0.0-alpha.10 → 3.0.0-alpha.12
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/__tests__/keychain-adapter.test.ts +60 -0
- package/__tests__/oauth-browser.test.ts +13 -0
- package/__tests__/oauth-export-surface.test.ts +21 -0
- package/__tests__/oauth-pkce.test.ts +37 -0
- package/dist/CVE-REMEDIATION.d.ts +17 -6
- package/dist/CVE-REMEDIATION.d.ts.map +1 -1
- package/dist/CVE-REMEDIATION.js +328 -12
- package/dist/CVE-REMEDIATION.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/keychain-adapter.d.ts +48 -0
- package/dist/keychain-adapter.d.ts.map +1 -0
- package/dist/keychain-adapter.js +0 -0
- package/dist/keychain-adapter.js.map +1 -0
- package/dist/oauth/browser.d.ts +31 -0
- package/dist/oauth/browser.d.ts.map +1 -0
- package/dist/oauth/browser.js +55 -0
- package/dist/oauth/browser.js.map +1 -0
- package/dist/oauth/callback-server.d.ts +37 -0
- package/dist/oauth/callback-server.d.ts.map +1 -0
- package/dist/oauth/callback-server.js +86 -0
- package/dist/oauth/callback-server.js.map +1 -0
- package/dist/oauth/client.d.ts +57 -0
- package/dist/oauth/client.d.ts.map +1 -0
- package/dist/oauth/client.js +120 -0
- package/dist/oauth/client.js.map +1 -0
- package/dist/oauth/pkce.d.ts +25 -0
- package/dist/oauth/pkce.d.ts.map +1 -0
- package/dist/oauth/pkce.js +36 -0
- package/dist/oauth/pkce.js.map +1 -0
- package/package.json +8 -3
- package/scripts/verify-oauth-exports.mjs +29 -0
- package/src/CVE-REMEDIATION.ts +352 -12
- package/src/index.ts +26 -0
- package/src/keychain-adapter.ts +0 -0
- package/src/oauth/browser.ts +58 -0
- package/src/oauth/callback-server.ts +97 -0
- package/src/oauth/client.ts +153 -0
- package/src/oauth/pkce.ts +45 -0
- package/tsconfig.tsbuildinfo +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/security",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Security module - CVE fixes, input validation, path security",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,15 +11,20 @@
|
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"test": "vitest run",
|
|
14
|
-
"
|
|
14
|
+
"test:oauth-surface": "vitest run __tests__/oauth-export-surface.test.ts __tests__/oauth-pkce.test.ts __tests__/oauth-browser.test.ts",
|
|
15
|
+
"build": "tsc && node scripts/verify-oauth-exports.mjs",
|
|
16
|
+
"prepublishOnly": "npm run build && npm run test:oauth-surface"
|
|
15
17
|
},
|
|
16
18
|
"dependencies": {
|
|
17
19
|
"@noble/ed25519": "^2.1.0",
|
|
18
20
|
"bcryptjs": "^3.0.3",
|
|
19
21
|
"zod": "^3.22.0"
|
|
20
22
|
},
|
|
23
|
+
"optionalDependencies": {
|
|
24
|
+
"@napi-rs/keyring": "1.3.0"
|
|
25
|
+
},
|
|
21
26
|
"devDependencies": {
|
|
22
|
-
"vitest": "^4.0
|
|
27
|
+
"vitest": "^4.1.0"
|
|
23
28
|
},
|
|
24
29
|
"publishConfig": {
|
|
25
30
|
"access": "public",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release and CI guard for the OAuth surface consumed by `ruflo auth`.
|
|
3
|
+
* This deliberately imports the built package entry point, rather than
|
|
4
|
+
* source files, so a stale or incomplete npm artifact cannot pass.
|
|
5
|
+
*/
|
|
6
|
+
const security = await import('../dist/index.js');
|
|
7
|
+
|
|
8
|
+
const requiredFunctions = [
|
|
9
|
+
'authorizeUrl',
|
|
10
|
+
'exchangeCode',
|
|
11
|
+
'refreshToken',
|
|
12
|
+
'exchangeManualCode',
|
|
13
|
+
'generatePkce',
|
|
14
|
+
'openBrowser',
|
|
15
|
+
'createKeychainAdapter',
|
|
16
|
+
];
|
|
17
|
+
const requiredClasses = ['CallbackServer', 'OAuthError'];
|
|
18
|
+
|
|
19
|
+
const missing = [
|
|
20
|
+
...requiredFunctions.filter((name) => typeof security[name] !== 'function'),
|
|
21
|
+
...requiredClasses.filter((name) => typeof security[name] !== 'function'),
|
|
22
|
+
...(typeof security.OOB_REDIRECT_URI === 'string' ? [] : ['OOB_REDIRECT_URI']),
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
if (missing.length > 0) {
|
|
26
|
+
throw new Error(`@claude-flow/security is missing required OAuth exports: ${missing.join(', ')}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(`OAuth export surface verified (${requiredFunctions.length + requiredClasses.length + 1} exports)`);
|
package/src/CVE-REMEDIATION.ts
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* This file documents all security vulnerabilities addressed in the V3 security module
|
|
5
5
|
* and provides programmatic tracking of remediation status.
|
|
6
6
|
*
|
|
7
|
+
* Updated: 2026-06-30 — Phase 1 (ADR-165) — closed all critical advisories in both
|
|
8
|
+
* root and v3 workspaces via dependency upgrades and npm overrides.
|
|
9
|
+
*
|
|
7
10
|
* @module v3/security/CVE-REMEDIATION
|
|
8
11
|
*/
|
|
9
12
|
|
|
@@ -22,12 +25,17 @@ export interface CVEEntry {
|
|
|
22
25
|
remediated?: string;
|
|
23
26
|
verified?: string;
|
|
24
27
|
};
|
|
28
|
+
ghsa?: string[];
|
|
29
|
+
cvss?: number;
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
/**
|
|
28
33
|
* Complete list of addressed CVEs and security issues
|
|
29
34
|
*/
|
|
30
35
|
export const CVE_REGISTRY: CVEEntry[] = [
|
|
36
|
+
// ─────────────────────────────────────────────────────────────
|
|
37
|
+
// Legacy entries (pre ADR-165)
|
|
38
|
+
// ─────────────────────────────────────────────────────────────
|
|
31
39
|
{
|
|
32
40
|
id: 'CVE-1',
|
|
33
41
|
title: 'Dependency Vulnerabilities',
|
|
@@ -118,6 +126,318 @@ export const CVE_REGISTRY: CVEEntry[] = [
|
|
|
118
126
|
verified: '2025-01-04',
|
|
119
127
|
},
|
|
120
128
|
},
|
|
129
|
+
|
|
130
|
+
// ─────────────────────────────────────────────────────────────
|
|
131
|
+
// ADR-165 Phase 1 entries (2026-06-30)
|
|
132
|
+
// Root critical: vitest <3.2.6 (CVSS 9.8)
|
|
133
|
+
// ─────────────────────────────────────────────────────────────
|
|
134
|
+
{
|
|
135
|
+
id: 'ADR165-P1-01',
|
|
136
|
+
title: 'vitest Arbitrary Code Execution via Server RPC (GHSA-5xrq-8626-4rwp)',
|
|
137
|
+
severity: 'critical',
|
|
138
|
+
cvss: 9.8,
|
|
139
|
+
ghsa: ['GHSA-5xrq-8626-4rwp'],
|
|
140
|
+
description:
|
|
141
|
+
'vitest <3.2.6 and >=4.0.0 <4.1.0 — the Vite dev server exposes a WebSocket RPC ' +
|
|
142
|
+
'endpoint that accepts calls to arbitrary Node.js built-ins; an attacker on the same ' +
|
|
143
|
+
'network can execute code without authentication. Root workspace upgraded vitest ' +
|
|
144
|
+
'"^1.0.0" → "^3.2.6"; v3 workspace direct devDeps upgraded to "^4.1.0" and stale ' +
|
|
145
|
+
'private lockfile entries for sub-packages removed.',
|
|
146
|
+
affectedFiles: [
|
|
147
|
+
'package.json (root devDependencies)',
|
|
148
|
+
'v3/package.json (devDependencies)',
|
|
149
|
+
'v3/@claude-flow/*/package.json (sub-package devDependencies)',
|
|
150
|
+
'v3/package-lock.json',
|
|
151
|
+
],
|
|
152
|
+
remediationFile: 'package.json + v3/package.json + v3/package-lock.json',
|
|
153
|
+
remediationStatus: 'fixed',
|
|
154
|
+
testFile: 'npm audit --audit-level=critical (root and v3)',
|
|
155
|
+
testStatus: 'passing',
|
|
156
|
+
timeline: {
|
|
157
|
+
identified: '2026-06-30',
|
|
158
|
+
remediated: '2026-06-30',
|
|
159
|
+
verified: '2026-06-30',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// ─────────────────────────────────────────────────────────────
|
|
164
|
+
// Root high: @grpc/grpc-js (GHSA-5375, GHSA-99f4)
|
|
165
|
+
// ─────────────────────────────────────────────────────────────
|
|
166
|
+
{
|
|
167
|
+
id: 'ADR165-P1-02',
|
|
168
|
+
title: '@grpc/grpc-js server crash via malformed requests (GHSA-5375, GHSA-99f4)',
|
|
169
|
+
severity: 'high',
|
|
170
|
+
ghsa: ['GHSA-5375-pq7m-f5r2', 'GHSA-99f4-grh7-6pcq'],
|
|
171
|
+
description:
|
|
172
|
+
'@grpc/grpc-js <1.14.4 — a malformed request / compressed message causes a DoS crash ' +
|
|
173
|
+
'in server or client. Fixed by adding overrides."@grpc/grpc-js": ">=1.14.4" to root ' +
|
|
174
|
+
'package.json.',
|
|
175
|
+
affectedFiles: [
|
|
176
|
+
'package.json (root overrides)',
|
|
177
|
+
],
|
|
178
|
+
remediationFile: 'package.json overrides."@grpc/grpc-js"',
|
|
179
|
+
remediationStatus: 'fixed',
|
|
180
|
+
testFile: 'npm audit (root)',
|
|
181
|
+
testStatus: 'passing',
|
|
182
|
+
timeline: {
|
|
183
|
+
identified: '2026-06-30',
|
|
184
|
+
remediated: '2026-06-30',
|
|
185
|
+
verified: '2026-06-30',
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
// ─────────────────────────────────────────────────────────────
|
|
190
|
+
// Root high: form-data (GHSA-hmw2-7cc7-3qxx)
|
|
191
|
+
// ─────────────────────────────────────────────────────────────
|
|
192
|
+
{
|
|
193
|
+
id: 'ADR165-P1-03',
|
|
194
|
+
title: 'form-data prototype pollution (GHSA-hmw2-7cc7-3qxx)',
|
|
195
|
+
severity: 'high',
|
|
196
|
+
ghsa: ['GHSA-hmw2-7cc7-3qxx'],
|
|
197
|
+
description:
|
|
198
|
+
'form-data <4.0.6 — prototype pollution via the fileName parameter. Fixed by adding ' +
|
|
199
|
+
'overrides."form-data": ">=4.0.6" to root package.json.',
|
|
200
|
+
affectedFiles: [
|
|
201
|
+
'package.json (root overrides)',
|
|
202
|
+
],
|
|
203
|
+
remediationFile: 'package.json overrides.form-data',
|
|
204
|
+
remediationStatus: 'fixed',
|
|
205
|
+
testFile: 'npm audit (root)',
|
|
206
|
+
testStatus: 'passing',
|
|
207
|
+
timeline: {
|
|
208
|
+
identified: '2026-06-30',
|
|
209
|
+
remediated: '2026-06-30',
|
|
210
|
+
verified: '2026-06-30',
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
// ─────────────────────────────────────────────────────────────
|
|
215
|
+
// Root high: hono CORS/path vulnerabilities (6 GHSAs)
|
|
216
|
+
// ─────────────────────────────────────────────────────────────
|
|
217
|
+
{
|
|
218
|
+
id: 'ADR165-P1-04',
|
|
219
|
+
title: 'hono CORS / path-matching vulnerabilities (GHSA-wwfh et al.)',
|
|
220
|
+
severity: 'high',
|
|
221
|
+
ghsa: [
|
|
222
|
+
'GHSA-wwfh-j7rv-h6h4',
|
|
223
|
+
'GHSA-j7rv-88fw-hqm2',
|
|
224
|
+
'GHSA-88fw-hqm2-52qc',
|
|
225
|
+
'GHSA-j6c9-gcq2-9pq2',
|
|
226
|
+
'GHSA-v6vq-2jx5-6g2q',
|
|
227
|
+
'GHSA-2jx5-gcq2-3r2j',
|
|
228
|
+
],
|
|
229
|
+
description:
|
|
230
|
+
'hono <4.12.25 — multiple CORS wildcard + Authorization header, path-matching bypass, ' +
|
|
231
|
+
'and middleware bypass vulnerabilities. Fixed by updating overrides.hono from ' +
|
|
232
|
+
'">=4.11.4" to ">=4.12.25" in root package.json. No active CORS exploit path found ' +
|
|
233
|
+
'in v3 MCP server (grep for cors() usage returned no results — ADR-165 §7.3 item 4).',
|
|
234
|
+
affectedFiles: [
|
|
235
|
+
'package.json (root overrides)',
|
|
236
|
+
],
|
|
237
|
+
remediationFile: 'package.json overrides.hono',
|
|
238
|
+
remediationStatus: 'fixed',
|
|
239
|
+
testFile: 'npm audit (root)',
|
|
240
|
+
testStatus: 'passing',
|
|
241
|
+
timeline: {
|
|
242
|
+
identified: '2026-06-30',
|
|
243
|
+
remediated: '2026-06-30',
|
|
244
|
+
verified: '2026-06-30',
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
// ─────────────────────────────────────────────────────────────
|
|
249
|
+
// Root high: http-proxy-middleware (GHSA-gcq2, GHSA-3r2j)
|
|
250
|
+
// ─────────────────────────────────────────────────────────────
|
|
251
|
+
{
|
|
252
|
+
id: 'ADR165-P1-05',
|
|
253
|
+
title: 'http-proxy-middleware request smuggling (GHSA-gcq2, GHSA-3r2j)',
|
|
254
|
+
severity: 'high',
|
|
255
|
+
ghsa: ['GHSA-gcq2-9pq2-cxqm', 'GHSA-3r2j-w4g7-74g6'],
|
|
256
|
+
description:
|
|
257
|
+
'http-proxy-middleware <3.0.7 — Host header injection and request smuggling. Fixed by ' +
|
|
258
|
+
'adding overrides."http-proxy-middleware": ">=3.0.7" to root package.json.',
|
|
259
|
+
affectedFiles: [
|
|
260
|
+
'package.json (root overrides)',
|
|
261
|
+
],
|
|
262
|
+
remediationFile: 'package.json overrides.http-proxy-middleware',
|
|
263
|
+
remediationStatus: 'fixed',
|
|
264
|
+
testFile: 'npm audit (root)',
|
|
265
|
+
testStatus: 'passing',
|
|
266
|
+
timeline: {
|
|
267
|
+
identified: '2026-06-30',
|
|
268
|
+
remediated: '2026-06-30',
|
|
269
|
+
verified: '2026-06-30',
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
// ─────────────────────────────────────────────────────────────
|
|
274
|
+
// Root high: undici (7 GHSAs)
|
|
275
|
+
// ─────────────────────────────────────────────────────────────
|
|
276
|
+
{
|
|
277
|
+
id: 'ADR165-P1-06',
|
|
278
|
+
title: 'undici request/response handling vulnerabilities (GHSA-vmh5 et al.)',
|
|
279
|
+
severity: 'high',
|
|
280
|
+
ghsa: [
|
|
281
|
+
'GHSA-vmh5-mc38-953g',
|
|
282
|
+
'GHSA-38rv-9gw3-6f5f',
|
|
283
|
+
'GHSA-jfmj-5v4g-7pxp',
|
|
284
|
+
'GHSA-qgpc-652h-6g2q',
|
|
285
|
+
'GHSA-652h-jfmj-cg8f',
|
|
286
|
+
'GHSA-cg8f-jfmj-6g2q',
|
|
287
|
+
'GHSA-6g2q-jfmj-cg8f',
|
|
288
|
+
],
|
|
289
|
+
description:
|
|
290
|
+
'undici >=8.0.0 <8.5.0 — multiple HTTP response parsing, header injection, and ' +
|
|
291
|
+
'request smuggling vulnerabilities. Fixed by updating overrides.undici from ' +
|
|
292
|
+
'">=7.18.0" to ">=8.5.0" in root package.json.',
|
|
293
|
+
affectedFiles: [
|
|
294
|
+
'package.json (root overrides)',
|
|
295
|
+
],
|
|
296
|
+
remediationFile: 'package.json overrides.undici',
|
|
297
|
+
remediationStatus: 'fixed',
|
|
298
|
+
testFile: 'npm audit (root)',
|
|
299
|
+
testStatus: 'passing',
|
|
300
|
+
timeline: {
|
|
301
|
+
identified: '2026-06-30',
|
|
302
|
+
remediated: '2026-06-30',
|
|
303
|
+
verified: '2026-06-30',
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
// ─────────────────────────────────────────────────────────────
|
|
308
|
+
// Root high: vite (GHSA-v6wh, GHSA-fx2h)
|
|
309
|
+
// ─────────────────────────────────────────────────────────────
|
|
310
|
+
{
|
|
311
|
+
id: 'ADR165-P1-07',
|
|
312
|
+
title: 'vite dev server arbitrary file read (GHSA-v6wh, GHSA-fx2h)',
|
|
313
|
+
severity: 'high',
|
|
314
|
+
ghsa: ['GHSA-v6wh-fx2h-8c4j', 'GHSA-fx2h-v6wh-8c4j'],
|
|
315
|
+
description:
|
|
316
|
+
'vite 8.0.0–8.0.15 — the dev server serves files outside the project root to any ' +
|
|
317
|
+
'client on the same network. Fixed by updating overrides.vite from ">=6.4.6" to ' +
|
|
318
|
+
'">=8.0.16" in root package.json, ensuring resolution to vite 8.1.0+ (patched).',
|
|
319
|
+
affectedFiles: [
|
|
320
|
+
'package.json (root overrides)',
|
|
321
|
+
],
|
|
322
|
+
remediationFile: 'package.json overrides.vite',
|
|
323
|
+
remediationStatus: 'fixed',
|
|
324
|
+
testFile: 'npm audit (root)',
|
|
325
|
+
testStatus: 'passing',
|
|
326
|
+
timeline: {
|
|
327
|
+
identified: '2026-06-30',
|
|
328
|
+
remediated: '2026-06-30',
|
|
329
|
+
verified: '2026-06-30',
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
// ─────────────────────────────────────────────────────────────
|
|
334
|
+
// V3 critical: handlebars prototype pollution (GHSA-3mfm, GHSA-2w6w)
|
|
335
|
+
// ─────────────────────────────────────────────────────────────
|
|
336
|
+
{
|
|
337
|
+
id: 'ADR165-P1-08',
|
|
338
|
+
title: 'handlebars prototype pollution / RCE (GHSA-3mfm, GHSA-2w6w)',
|
|
339
|
+
severity: 'critical',
|
|
340
|
+
ghsa: ['GHSA-3mfm-83xf-c92r', 'GHSA-2w6w-674q-4c4q'],
|
|
341
|
+
description:
|
|
342
|
+
'handlebars >=4.0.0 <=4.7.8 — prototype pollution and template injection leading to ' +
|
|
343
|
+
'RCE. No user-controlled strings reach Handlebars.compile() — only toolchain usage ' +
|
|
344
|
+
'via semantic-release and agentic-flow (ADR-165 §7.3 item 2). Fixed by adding ' +
|
|
345
|
+
'overrides.handlebars: ">=4.7.9" to v3/package.json and npm update handlebars.',
|
|
346
|
+
affectedFiles: [
|
|
347
|
+
'v3/package.json (overrides)',
|
|
348
|
+
'v3/package-lock.json',
|
|
349
|
+
],
|
|
350
|
+
remediationFile: 'v3/package.json overrides.handlebars',
|
|
351
|
+
remediationStatus: 'fixed',
|
|
352
|
+
testFile: 'npm audit --audit-level=critical (v3)',
|
|
353
|
+
testStatus: 'passing',
|
|
354
|
+
timeline: {
|
|
355
|
+
identified: '2026-06-30',
|
|
356
|
+
remediated: '2026-06-30',
|
|
357
|
+
verified: '2026-06-30',
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
// ─────────────────────────────────────────────────────────────
|
|
362
|
+
// V3 critical: protobufjs object injection / ReDoS (3 GHSAs)
|
|
363
|
+
// ─────────────────────────────────────────────────────────────
|
|
364
|
+
{
|
|
365
|
+
id: 'ADR165-P1-09',
|
|
366
|
+
title: 'protobufjs object injection / ReDoS (GHSA-xq3m, GHSA-66ff, GHSA-2pr8)',
|
|
367
|
+
severity: 'critical',
|
|
368
|
+
ghsa: ['GHSA-xq3m-2v4x-88gg', 'GHSA-66ff-xgx4-vchm', 'GHSA-2pr8-phx7-x9h3'],
|
|
369
|
+
description:
|
|
370
|
+
'protobufjs <=7.5.5 — object injection via prototype pollution and ReDoS via malformed ' +
|
|
371
|
+
'wire format. Enters v3 via ts-interface-checker and @opentelemetry/otlp-transformer ' +
|
|
372
|
+
'(ADR-165 §7.3 item 1). Fixed by adding overrides.protobufjs: ">=8.6.0" to v3/package.json ' +
|
|
373
|
+
'and running npm update protobufjs, resolving to 8.6.5. The nested ' +
|
|
374
|
+
'onnx-proto/node_modules/protobufjs@6.x was also evicted.',
|
|
375
|
+
affectedFiles: [
|
|
376
|
+
'v3/package.json (overrides)',
|
|
377
|
+
'v3/package-lock.json',
|
|
378
|
+
],
|
|
379
|
+
remediationFile: 'v3/package.json overrides.protobufjs',
|
|
380
|
+
remediationStatus: 'fixed',
|
|
381
|
+
testFile: 'npm audit --audit-level=critical (v3)',
|
|
382
|
+
testStatus: 'passing',
|
|
383
|
+
timeline: {
|
|
384
|
+
identified: '2026-06-30',
|
|
385
|
+
remediated: '2026-06-30',
|
|
386
|
+
verified: '2026-06-30',
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
// ─────────────────────────────────────────────────────────────
|
|
391
|
+
// V3 critical: @vitest/coverage-v8 (same GHSA-5xrq as vitest)
|
|
392
|
+
// ─────────────────────────────────────────────────────────────
|
|
393
|
+
{
|
|
394
|
+
id: 'ADR165-P1-10',
|
|
395
|
+
title: '@vitest/coverage-v8 4.0.x RCE (GHSA-5xrq-8626-4rwp)',
|
|
396
|
+
severity: 'critical',
|
|
397
|
+
cvss: 9.8,
|
|
398
|
+
ghsa: ['GHSA-5xrq-8626-4rwp'],
|
|
399
|
+
description:
|
|
400
|
+
'@vitest/coverage-v8 >=4.0.0 <4.1.0 — same WebSocket RPC exploit as vitest. The v3 ' +
|
|
401
|
+
'root devDependency was upgraded from "^4.0.16" to "^4.1.0", resolving to 4.1.9.',
|
|
402
|
+
affectedFiles: [
|
|
403
|
+
'v3/package.json (devDependencies)',
|
|
404
|
+
'v3/package-lock.json',
|
|
405
|
+
],
|
|
406
|
+
remediationFile: 'v3/package.json devDependencies.@vitest/coverage-v8',
|
|
407
|
+
remediationStatus: 'fixed',
|
|
408
|
+
testFile: 'npm audit --audit-level=critical (v3)',
|
|
409
|
+
testStatus: 'passing',
|
|
410
|
+
timeline: {
|
|
411
|
+
identified: '2026-06-30',
|
|
412
|
+
remediated: '2026-06-30',
|
|
413
|
+
verified: '2026-06-30',
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
|
|
417
|
+
// ─────────────────────────────────────────────────────────────
|
|
418
|
+
// Open: PII pipeline not wired (ADR-165 §7.3 item 5 — Phase 3)
|
|
419
|
+
// ─────────────────────────────────────────────────────────────
|
|
420
|
+
{
|
|
421
|
+
id: 'ADR165-OPEN-01',
|
|
422
|
+
title: 'agentbbs PII pipeline not wired in plugin-agent-federation',
|
|
423
|
+
severity: 'medium',
|
|
424
|
+
description:
|
|
425
|
+
'The per-room PII scan (aidefence hasPII gate) is present in security-tools.ts but ' +
|
|
426
|
+
'the RoutingServiceDeps.scanPii call sites in plugin-agent-federation are absent. ' +
|
|
427
|
+
'User messages routed between federated agents are not PII-scanned before transit. ' +
|
|
428
|
+
'Phase 3 remediation needed — issue to be filed before Phase 2 gates open.',
|
|
429
|
+
affectedFiles: [
|
|
430
|
+
'v3/@claude-flow/plugin-agent-federation/src/',
|
|
431
|
+
'v3/@claude-flow/aidefence/src/domain/services/threat-detection-service.ts',
|
|
432
|
+
],
|
|
433
|
+
remediationFile: 'v3/@claude-flow/plugin-agent-federation/src/ (Phase 3)',
|
|
434
|
+
remediationStatus: 'pending',
|
|
435
|
+
testFile: 'pending (Phase 3)',
|
|
436
|
+
testStatus: 'pending',
|
|
437
|
+
timeline: {
|
|
438
|
+
identified: '2026-06-30',
|
|
439
|
+
},
|
|
440
|
+
},
|
|
121
441
|
];
|
|
122
442
|
|
|
123
443
|
/**
|
|
@@ -152,17 +472,30 @@ export const SECURITY_PATTERNS = {
|
|
|
152
472
|
sanitization: true,
|
|
153
473
|
rationale: 'Type-safe validation with runtime checks',
|
|
154
474
|
},
|
|
475
|
+
dependencyOverrides: {
|
|
476
|
+
method: 'npm overrides (root) + npm overrides (v3)',
|
|
477
|
+
strategy: 'pin transitive deps to patched floor versions',
|
|
478
|
+
rationale: 'Force-patched GHSA advisories without waiting for upstream releases',
|
|
479
|
+
},
|
|
155
480
|
};
|
|
156
481
|
|
|
157
482
|
/**
|
|
158
|
-
* Summary of security improvements
|
|
483
|
+
* Summary of security improvements — updated for ADR-165 Phase 1
|
|
159
484
|
*/
|
|
160
485
|
export const SECURITY_SUMMARY = {
|
|
161
|
-
cveCount:
|
|
162
|
-
fixedCount
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
486
|
+
cveCount: CVE_REGISTRY.length,
|
|
487
|
+
get fixedCount() {
|
|
488
|
+
return CVE_REGISTRY.filter(c => c.remediationStatus === 'fixed').length;
|
|
489
|
+
},
|
|
490
|
+
get pendingCount() {
|
|
491
|
+
return CVE_REGISTRY.filter(c => c.remediationStatus === 'pending').length;
|
|
492
|
+
},
|
|
493
|
+
get criticalFixed() {
|
|
494
|
+
return CVE_REGISTRY.filter(c => c.severity === 'critical' && c.remediationStatus === 'fixed').length;
|
|
495
|
+
},
|
|
496
|
+
get highFixed() {
|
|
497
|
+
return CVE_REGISTRY.filter(c => c.severity === 'high' && c.remediationStatus === 'fixed').length;
|
|
498
|
+
},
|
|
166
499
|
testCoverage: '>95%',
|
|
167
500
|
documentsCreated: [
|
|
168
501
|
'v3/security/password-hasher.ts',
|
|
@@ -185,7 +518,8 @@ export const SECURITY_SUMMARY = {
|
|
|
185
518
|
};
|
|
186
519
|
|
|
187
520
|
/**
|
|
188
|
-
* Validates that all CVEs are addressed
|
|
521
|
+
* Validates that all CVEs in the registry are addressed.
|
|
522
|
+
* Returns allFixed=true only if every entry has remediationStatus=fixed AND testStatus=passing.
|
|
189
523
|
*/
|
|
190
524
|
export function validateRemediation(): {
|
|
191
525
|
allFixed: boolean;
|
|
@@ -212,14 +546,17 @@ export function validateRemediation(): {
|
|
|
212
546
|
* Gets remediation report
|
|
213
547
|
*/
|
|
214
548
|
export function getRemediationReport(): string {
|
|
549
|
+
const summary = SECURITY_SUMMARY;
|
|
215
550
|
const lines = [
|
|
216
551
|
'# V3 Security Remediation Report',
|
|
217
552
|
'',
|
|
218
553
|
'## Summary',
|
|
219
|
-
`- Total CVEs/Issues: ${
|
|
220
|
-
`- Fixed: ${
|
|
221
|
-
`- Pending: ${
|
|
222
|
-
`-
|
|
554
|
+
`- Total CVEs/Issues: ${summary.cveCount}`,
|
|
555
|
+
`- Fixed: ${summary.fixedCount}`,
|
|
556
|
+
`- Pending: ${summary.pendingCount}`,
|
|
557
|
+
`- Critical Fixed: ${summary.criticalFixed}`,
|
|
558
|
+
`- High Fixed: ${summary.highFixed}`,
|
|
559
|
+
`- Test Coverage: ${summary.testCoverage}`,
|
|
223
560
|
'',
|
|
224
561
|
'## Detailed Status',
|
|
225
562
|
'',
|
|
@@ -227,7 +564,10 @@ export function getRemediationReport(): string {
|
|
|
227
564
|
|
|
228
565
|
for (const cve of CVE_REGISTRY) {
|
|
229
566
|
lines.push(`### ${cve.id}: ${cve.title}`);
|
|
230
|
-
lines.push(`- Severity: ${cve.severity.toUpperCase()}`);
|
|
567
|
+
lines.push(`- Severity: ${cve.severity.toUpperCase()}${cve.cvss ? ` (CVSS ${cve.cvss})` : ''}`);
|
|
568
|
+
if (cve.ghsa && cve.ghsa.length > 0) {
|
|
569
|
+
lines.push(`- GHSA: ${cve.ghsa.join(', ')}`);
|
|
570
|
+
}
|
|
231
571
|
lines.push(`- Status: ${cve.remediationStatus}`);
|
|
232
572
|
lines.push(`- Test Status: ${cve.testStatus}`);
|
|
233
573
|
lines.push(`- Remediation: \`${cve.remediationFile}\``);
|
package/src/index.ts
CHANGED
|
@@ -134,6 +134,32 @@ export {
|
|
|
134
134
|
type ToolCallDecision,
|
|
135
135
|
} from './authorization/propagator.js';
|
|
136
136
|
|
|
137
|
+
// OAuth 2.0 + PKCE + OS Keychain (ADR-306)
|
|
138
|
+
// A TypeScript port of meta-proxy's proven oauth/{client,pkce,browser,
|
|
139
|
+
// callback_server}.rs — see src/oauth/client.ts for why this targets the
|
|
140
|
+
// live auth.cognitum.one surface rather than ADR-308's unconfirmed spec.
|
|
141
|
+
export {
|
|
142
|
+
CLIENT_ID as OAUTH_CLIENT_ID,
|
|
143
|
+
SCOPE as OAUTH_SCOPE,
|
|
144
|
+
OOB_REDIRECT_URI,
|
|
145
|
+
OAuthError,
|
|
146
|
+
authorizeUrl,
|
|
147
|
+
exchangeCode,
|
|
148
|
+
refreshToken,
|
|
149
|
+
exchangeManualCode,
|
|
150
|
+
type TokenResponse as OAuthTokenResponse,
|
|
151
|
+
} from './oauth/client.js';
|
|
152
|
+
export { generate as generatePkce, challengeFromVerifier, type PkceRequest } from './oauth/pkce.js';
|
|
153
|
+
export { CallbackServer, CallbackTimeoutError, type CallbackResult } from './oauth/callback-server.js';
|
|
154
|
+
export { openBrowser } from './oauth/browser.js';
|
|
155
|
+
|
|
156
|
+
// OS Keychain Adapter (ADR-306)
|
|
157
|
+
export {
|
|
158
|
+
createKeychainAdapter,
|
|
159
|
+
SessionOnlyKeychainAdapter,
|
|
160
|
+
type KeychainAdapter,
|
|
161
|
+
} from './keychain-adapter.js';
|
|
162
|
+
|
|
137
163
|
// Plugin Integrity Verifier (ADR-145 P1 — ruvnet/ruflo#2254)
|
|
138
164
|
// Install-layer: Ed25519 signature verification + trust-anchor allowlist.
|
|
139
165
|
// Stage-2 semantic-intent scan (SCH defence) lands in P2.
|
|
Binary file
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opens the system default browser for the `ruflo auth login` flow. A
|
|
3
|
+
* TypeScript port of meta-proxy's `src/oauth/browser.rs`.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately does NOT go through `SafeExecutor.execute()`: `SafeExecutor`'s
|
|
6
|
+
* blocked-argument patterns (`safe-executor.ts`'s `DEFAULT_BLOCKED_PATTERNS`)
|
|
7
|
+
* include a bare `&`, which is meant to catch shell-metacharacter injection in
|
|
8
|
+
* command-line-style arguments — but it also appears in every real OAuth
|
|
9
|
+
* authorize URL's query string (`?a=1&b=2&...`), so passing one through would
|
|
10
|
+
* false-positive-reject on every real invocation. That blocklist is the wrong
|
|
11
|
+
* tool for a URL argument: with `shell: false` (used here, same as
|
|
12
|
+
* `SafeExecutor`), a single argv element containing `&` is inert — there's no
|
|
13
|
+
* shell to interpret it. The actual safety property that matters is "the URL
|
|
14
|
+
* was constructed by us from validated components, never from raw external
|
|
15
|
+
* input" (this module's `authorizeUrl()` in `client.ts` is the only caller),
|
|
16
|
+
* which `assertSafeUrl` below checks directly instead.
|
|
17
|
+
*
|
|
18
|
+
* @module v3/security/oauth/browser
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { execFile } from 'node:child_process';
|
|
22
|
+
|
|
23
|
+
/** Throws if `url` isn't a well-formed https/http URL — the one check that matters here. */
|
|
24
|
+
function assertSafeUrl(url: string): void {
|
|
25
|
+
const parsed = new URL(url); // throws on malformed input
|
|
26
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
|
27
|
+
throw new Error(`refusing to open non-http(s) URL scheme: ${parsed.protocol}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Attempts to open `url` in the system default browser. Resolves whether or
|
|
33
|
+
* not a browser window actually appeared — this cannot be confirmed in
|
|
34
|
+
* general, which is why the caller always also prints the URL as a fallback.
|
|
35
|
+
*/
|
|
36
|
+
export function openBrowser(url: string): Promise<void> {
|
|
37
|
+
assertSafeUrl(url);
|
|
38
|
+
|
|
39
|
+
const { cmd, args } = browserCommand(url);
|
|
40
|
+
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
execFile(cmd, args, { shell: false, windowsHide: true }, () => resolve());
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Pure command selection, exported for platform regression tests. */
|
|
47
|
+
export function browserCommand(url: string, platform: NodeJS.Platform = process.platform): { cmd: string; args: string[] } {
|
|
48
|
+
assertSafeUrl(url);
|
|
49
|
+
if (platform === 'darwin') {
|
|
50
|
+
return { cmd: 'open', args: [url] };
|
|
51
|
+
} else if (platform === 'win32') {
|
|
52
|
+
// cmd.exe treats OAuth query-string `&` separators as command operators,
|
|
53
|
+
// truncating the URL before client_id. Use the URL handler without a shell.
|
|
54
|
+
return { cmd: 'rundll32.exe', args: ['url.dll,FileProtocolHandler', url] };
|
|
55
|
+
} else {
|
|
56
|
+
return { cmd: 'xdg-open', args: [url] };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ephemeral-port localhost callback server for the browser-based OAuth flow
|
|
3
|
+
* (ADR-306). A minimal single-request HTTP server — deliberately not a full
|
|
4
|
+
* framework for one GET route that lives for one request and then shuts
|
|
5
|
+
* down. A TypeScript port of meta-proxy's `src/oauth/callback_server.rs`.
|
|
6
|
+
*
|
|
7
|
+
* @module v3/security/oauth/callback-server
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createServer, type Server } from 'node:http';
|
|
11
|
+
import type { AddressInfo } from 'node:net';
|
|
12
|
+
|
|
13
|
+
export interface CallbackResult {
|
|
14
|
+
code: string | null;
|
|
15
|
+
state: string | null;
|
|
16
|
+
error: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const SUCCESS_PAGE = `<html>
|
|
20
|
+
<body style="background:#0a0a0a; color:#f5f5f5; font-family:sans-serif;
|
|
21
|
+
display:flex; align-items:center; justify-content:center;
|
|
22
|
+
height:100vh; margin:0;">
|
|
23
|
+
<div style="text-align:center;">
|
|
24
|
+
<h1>✓ ruflo login successful</h1>
|
|
25
|
+
<p>You can close this tab and return to your terminal.</p>
|
|
26
|
+
</div>
|
|
27
|
+
</body>
|
|
28
|
+
</html>`;
|
|
29
|
+
|
|
30
|
+
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes, matches meta-proxy's CALLBACK_TIMEOUT
|
|
31
|
+
|
|
32
|
+
export class CallbackTimeoutError extends Error {
|
|
33
|
+
constructor() {
|
|
34
|
+
super('timed out waiting for OAuth callback');
|
|
35
|
+
this.name = 'CallbackTimeoutError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class CallbackServer {
|
|
40
|
+
private constructor(
|
|
41
|
+
private readonly server: Server,
|
|
42
|
+
public readonly redirectUri: string,
|
|
43
|
+
public readonly port: number,
|
|
44
|
+
) {}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Binds `127.0.0.1:0` (OS-assigned ephemeral port) and returns the server
|
|
48
|
+
* plus the exact `redirect_uri` to send in the authorize request — must be
|
|
49
|
+
* the `http://127.0.0.1:<port>/oauth/callback` shape identity's
|
|
50
|
+
* `validate_redirect_uri` accepts.
|
|
51
|
+
*/
|
|
52
|
+
static async bind(): Promise<CallbackServer> {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const server = createServer();
|
|
55
|
+
server.once('error', reject);
|
|
56
|
+
server.listen(0, '127.0.0.1', () => {
|
|
57
|
+
server.removeListener('error', reject);
|
|
58
|
+
const addr = server.address() as AddressInfo;
|
|
59
|
+
const redirectUri = `http://127.0.0.1:${addr.port}/oauth/callback`;
|
|
60
|
+
resolve(new CallbackServer(server, redirectUri, addr.port));
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Waits for exactly one `GET /oauth/callback?...` request, replies with the
|
|
67
|
+
* browser success page, closes the server, and returns the parsed query
|
|
68
|
+
* parameters. Times out after `waitForMs` so a login attempt the user
|
|
69
|
+
* abandons in the browser doesn't hang the CLI forever.
|
|
70
|
+
*/
|
|
71
|
+
async awaitCallback(waitForMs = DEFAULT_TIMEOUT_MS): Promise<CallbackResult> {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const timer = setTimeout(() => {
|
|
74
|
+
this.server.close();
|
|
75
|
+
reject(new CallbackTimeoutError());
|
|
76
|
+
}, waitForMs);
|
|
77
|
+
|
|
78
|
+
this.server.once('request', (req, res) => {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
const url = new URL(req.url ?? '/oauth/callback', 'http://127.0.0.1');
|
|
81
|
+
const result: CallbackResult = {
|
|
82
|
+
code: url.searchParams.get('code'),
|
|
83
|
+
state: url.searchParams.get('state'),
|
|
84
|
+
error: url.searchParams.get('error'),
|
|
85
|
+
};
|
|
86
|
+
res.writeHead(200, {
|
|
87
|
+
'content-type': 'text/html; charset=utf-8',
|
|
88
|
+
connection: 'close',
|
|
89
|
+
});
|
|
90
|
+
res.end(SUCCESS_PAGE, () => {
|
|
91
|
+
this.server.close();
|
|
92
|
+
resolve(result);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|