@wrongstack/core 0.264.0 → 0.267.0
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/dist/{agent-bridge-D8sa1vtv.d.ts → agent-bridge-STJ3JwwK.d.ts} +1 -1
- package/dist/{agent-subagent-runner-c9DLkaas.d.ts → agent-subagent-runner-CzPGP3jA.d.ts} +131 -11
- package/dist/{brain-O1IdKPaK.d.ts → brain-Cdg77tVN.d.ts} +103 -2
- package/dist/{compactor-BBy0rCtB.d.ts → compactor-iMZ84CXq.d.ts} +19 -1
- package/dist/{config-Dz2F3H2K.d.ts → config-Du3pYYln.d.ts} +132 -13
- package/dist/{context-BGSpZNSE.d.ts → context-dT5Ueund.d.ts} +90 -12
- package/dist/coordination/index.d.ts +78 -22
- package/dist/coordination/index.js +695 -273
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-CXsDvOmP.d.ts → default-config-B0cj-Hry.d.ts} +11 -1
- package/dist/defaults/index.d.ts +28 -28
- package/dist/defaults/index.js +2327 -965
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +16 -16
- package/dist/execution/index.js +1500 -371
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +2 -2
- package/dist/execution/prompt-enhancer.js +1 -1
- package/dist/execution/prompt-enhancer.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{goal-preamble-DzjFuN3p.d.ts → goal-preamble-SulMTowG.d.ts} +33 -12
- package/dist/{goal-store-CxWmCGbH.d.ts → goal-store-CABDwdFE.d.ts} +1 -1
- package/dist/{index-CbLSI66_.d.ts → index-Bms0m4oy.d.ts} +5 -5
- package/dist/{index-CYIQrXVF.d.ts → index-DtCVWel4.d.ts} +8 -8
- package/dist/index-IEuxQd-E.d.ts +82 -0
- package/dist/index.d.ts +261 -57
- package/dist/index.js +4799 -2212
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +84 -9
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js +1 -1
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-DC4QRPUI.d.ts → mcp-servers-C2cBTxUR.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +104 -31
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-B_siPxqN.d.ts → models-registry-BqGZNJQ-.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CK5Jdj9K.d.ts → multi-agent-coordinator-B8R43uPz.d.ts} +1 -1
- package/dist/{null-fleet-bus-DgvD4SCO.d.ts → null-fleet-bus-CnXa5oTH.d.ts} +14 -9
- package/dist/observability/index.d.ts +2 -2
- package/dist/{parallel-eternal-engine-bK0JQBR_.d.ts → parallel-eternal-engine-DdNnw9BQ.d.ts} +11 -9
- package/dist/{path-resolver-BPEDlN38.d.ts → path-resolver-COIMLCQL.d.ts} +3 -3
- package/dist/{permission-4yvGmMRB.d.ts → permission-B75JAi3-.d.ts} +1 -1
- package/dist/{permission-policy-C6XpsBOy.d.ts → permission-policy-DlR9eJAM.d.ts} +2 -2
- package/dist/{pipeline-CXCeMz8J.d.ts → pipeline-BfD2k1rT.d.ts} +3 -3
- package/dist/{plan-templates-BvzRBkJc.d.ts → plan-templates-DSIKCXZN.d.ts} +32 -8
- package/dist/provider-model-resolve-BNRsNuJx.d.ts +107 -0
- package/dist/{provider-runner-C5aQpDWE.d.ts → provider-runner-CX7iIvox.d.ts} +3 -3
- package/dist/{retry-policy-CFhdtRzz.d.ts → retry-policy-BilV1ujH.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +286 -105
- package/dist/sdd/index.js.map +1 -1
- package/dist/secret-vault-BAKpgFw_.d.ts +57 -0
- package/dist/{secret-vault-CxiVLbt1.d.ts → secret-vault-gkvEZZfE.d.ts} +43 -4
- package/dist/security/index.d.ts +6 -68
- package/dist/security/index.js +296 -95
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-gIuhRTkN.d.ts → selector-Bc7eWtT3.d.ts} +1 -1
- package/dist/{session-event-bridge-DkvvrpDt.d.ts → session-event-bridge-D-araDEz.d.ts} +1 -1
- package/dist/{session-reader-KdfVwkKP.d.ts → session-reader-D7Dapswh.d.ts} +1 -1
- package/dist/storage/index.d.ts +112 -15
- package/dist/storage/index.js +491 -156
- package/dist/storage/index.js.map +1 -1
- package/dist/tools/index.d.ts +4 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/types/index.d.ts +21 -21
- package/dist/types/index.js +1523 -450
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +455 -407
- package/dist/utils/index.js +2191 -1203
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-CJjEwPXn.d.ts → wstack-paths-hOpNLmvf.d.ts} +2 -0
- package/package.json +1 -1
- package/skills/api-design/SKILL.md +1 -1
- package/skills/audit-log/SKILL.md +6 -6
- package/skills/bug-hunter/SKILL.md +5 -5
- package/skills/chimera/SKILL.md +4 -4
- package/skills/docker-deploy/SKILL.md +1 -1
- package/skills/git-flow/SKILL.md +3 -3
- package/skills/multi-agent/SKILL.md +3 -3
- package/skills/node-modern/SKILL.md +1 -0
- package/skills/observability/SKILL.md +2 -2
- package/skills/output-standards/SKILL.md +51 -28
- package/skills/refactor-planner/SKILL.md +3 -3
- package/skills/security-scanner/SKILL.md +4 -3
- package/skills/tech-stack/SKILL.md +1 -2
- package/dist/llm-selector-DzxuZnNz.d.ts +0 -58
- package/dist/secret-vault-BJDY28ev.d.ts +0 -25
package/dist/security/index.js
CHANGED
|
@@ -44,6 +44,27 @@ var PATTERNS = [
|
|
|
44
44
|
{ type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g },
|
|
45
45
|
{ type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g },
|
|
46
46
|
{ type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g },
|
|
47
|
+
// AI/ML provider keys — modern LLM services with well-known prefixes
|
|
48
|
+
{
|
|
49
|
+
type: "huggingface_token",
|
|
50
|
+
// HuggingFace tokens: hf_ followed by 34 alphanumeric chars
|
|
51
|
+
regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
type: "replicate_token",
|
|
55
|
+
// Replicate tokens: r8_ followed by 40+ alphanumeric chars
|
|
56
|
+
regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: "perplexity_key",
|
|
60
|
+
// Perplexity API keys: pplx- followed by 40+ alphanumeric chars
|
|
61
|
+
regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
type: "groq_key",
|
|
65
|
+
// Groq API keys: gsk_ followed by 40+ alphanumeric chars
|
|
66
|
+
regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
67
|
+
},
|
|
47
68
|
{
|
|
48
69
|
type: "bearer_token",
|
|
49
70
|
// Anchored with alternation instead of negative lookahead — avoids V8
|
|
@@ -77,6 +98,10 @@ function hasCredentialAnchors(text) {
|
|
|
77
98
|
text.includes("xox") || // Slack token (xoxa/xoxb/xoxp/xoxo/xoxs)
|
|
78
99
|
text.includes("Bearer ") || // Bearer token (space suffix reduces false positives)
|
|
79
100
|
text.includes("/bot") || // Telegram bot token (URL path pattern)
|
|
101
|
+
text.includes("hf_") || // HuggingFace token
|
|
102
|
+
text.includes("r8_") || // Replicate token
|
|
103
|
+
text.includes("pplx-") || // Perplexity API key
|
|
104
|
+
text.includes("gsk_") || // Groq API key
|
|
80
105
|
text.includes("_KEY=") || // High-entropy env vars: API_KEY=, SECRET_KEY=, ...
|
|
81
106
|
text.includes("_TOKEN=") || // ACCESS_TOKEN=, AUTH_TOKEN=, ...
|
|
82
107
|
text.includes("_SECRET=") || // API_SECRET=, CLIENT_SECRET=, ...
|
|
@@ -205,24 +230,68 @@ async function renameWithRetry(from, to) {
|
|
|
205
230
|
throw lastErr;
|
|
206
231
|
}
|
|
207
232
|
|
|
208
|
-
// src/utils/
|
|
209
|
-
|
|
210
|
-
|
|
233
|
+
// src/utils/deep-merge.ts
|
|
234
|
+
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
235
|
+
"__proto__",
|
|
236
|
+
"constructor",
|
|
237
|
+
"prototype",
|
|
238
|
+
"__defineGetter__",
|
|
239
|
+
"__defineSetter__",
|
|
240
|
+
"__lookupGetter__",
|
|
241
|
+
"__lookupSetter__"
|
|
242
|
+
]);
|
|
243
|
+
function isPrimitiveArray(a) {
|
|
244
|
+
return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
|
|
211
245
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
246
|
+
function deepMerge(base, patch, options = {}) {
|
|
247
|
+
const {
|
|
248
|
+
conflictResolution = "prefer-patch",
|
|
249
|
+
arrayMode = "replace",
|
|
250
|
+
protectProto = true,
|
|
251
|
+
onNonPrimitiveArrayReplace
|
|
252
|
+
} = options;
|
|
253
|
+
if (typeof base !== "object" || base === null) {
|
|
254
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
217
255
|
}
|
|
218
|
-
|
|
219
|
-
return
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
256
|
+
if (typeof patch !== "object" || patch === null) {
|
|
257
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
258
|
+
}
|
|
259
|
+
if (Array.isArray(base) && Array.isArray(patch)) {
|
|
260
|
+
if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
|
|
261
|
+
return [.../* @__PURE__ */ new Set([...base, ...patch])];
|
|
262
|
+
}
|
|
263
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
264
|
+
}
|
|
265
|
+
if (Array.isArray(base) || Array.isArray(patch)) {
|
|
266
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
225
267
|
}
|
|
268
|
+
const baseObj = base;
|
|
269
|
+
const patchObj = patch;
|
|
270
|
+
const out = { ...baseObj };
|
|
271
|
+
for (const [k, v] of Object.entries(patchObj)) {
|
|
272
|
+
if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
273
|
+
const existing = out[k];
|
|
274
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
275
|
+
out[k] = deepMerge(existing, v, options);
|
|
276
|
+
} else if (Array.isArray(v) && Array.isArray(existing)) {
|
|
277
|
+
if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
|
|
278
|
+
onNonPrimitiveArrayReplace(k, existing.length, v.length);
|
|
279
|
+
}
|
|
280
|
+
out[k] = deepMerge(existing, v, options);
|
|
281
|
+
} else if (v !== void 0) {
|
|
282
|
+
if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
|
|
283
|
+
const existingLen = Array.isArray(existing) ? existing.length : 0;
|
|
284
|
+
onNonPrimitiveArrayReplace(k, existingLen, v.length);
|
|
285
|
+
}
|
|
286
|
+
out[k] = v;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/utils/error.ts
|
|
293
|
+
function toErrorMessage(err) {
|
|
294
|
+
return err instanceof Error ? err.message : String(err);
|
|
226
295
|
}
|
|
227
296
|
|
|
228
297
|
// src/utils/expect-defined.ts
|
|
@@ -311,63 +380,19 @@ function matchAny(patterns, input) {
|
|
|
311
380
|
return patterns.some((p) => matchGlob(p, input));
|
|
312
381
|
}
|
|
313
382
|
|
|
314
|
-
// src/utils/
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
"prototype",
|
|
319
|
-
"__defineGetter__",
|
|
320
|
-
"__defineSetter__",
|
|
321
|
-
"__lookupGetter__",
|
|
322
|
-
"__lookupSetter__"
|
|
323
|
-
]);
|
|
324
|
-
function isPrimitiveArray(a) {
|
|
325
|
-
return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
|
|
326
|
-
}
|
|
327
|
-
function deepMerge(base, patch, options = {}) {
|
|
328
|
-
const {
|
|
329
|
-
conflictResolution = "prefer-patch",
|
|
330
|
-
arrayMode = "replace",
|
|
331
|
-
protectProto = true,
|
|
332
|
-
onNonPrimitiveArrayReplace
|
|
333
|
-
} = options;
|
|
334
|
-
if (typeof base !== "object" || base === null) {
|
|
335
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
336
|
-
}
|
|
337
|
-
if (typeof patch !== "object" || patch === null) {
|
|
338
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
339
|
-
}
|
|
340
|
-
if (Array.isArray(base) && Array.isArray(patch)) {
|
|
341
|
-
if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
|
|
342
|
-
return [.../* @__PURE__ */ new Set([...base, ...patch])];
|
|
343
|
-
}
|
|
344
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
345
|
-
}
|
|
346
|
-
if (Array.isArray(base) || Array.isArray(patch)) {
|
|
347
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
383
|
+
// src/utils/safe-json.ts
|
|
384
|
+
function safeParse(input, maxBytes = 5e6) {
|
|
385
|
+
if (input.length > maxBytes) {
|
|
386
|
+
return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
|
|
348
387
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
out[k] = deepMerge(existing, v, options);
|
|
357
|
-
} else if (Array.isArray(v) && Array.isArray(existing)) {
|
|
358
|
-
if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
|
|
359
|
-
onNonPrimitiveArrayReplace(k, existing.length, v.length);
|
|
360
|
-
}
|
|
361
|
-
out[k] = deepMerge(existing, v, options);
|
|
362
|
-
} else if (v !== void 0) {
|
|
363
|
-
if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
|
|
364
|
-
const existingLen = Array.isArray(existing) ? existing.length : 0;
|
|
365
|
-
onNonPrimitiveArrayReplace(k, existingLen, v.length);
|
|
366
|
-
}
|
|
367
|
-
out[k] = v;
|
|
368
|
-
}
|
|
388
|
+
try {
|
|
389
|
+
return { ok: true, value: JSON.parse(input) };
|
|
390
|
+
} catch (err) {
|
|
391
|
+
return {
|
|
392
|
+
ok: false,
|
|
393
|
+
error: toErrorMessage(err)
|
|
394
|
+
};
|
|
369
395
|
}
|
|
370
|
-
return out;
|
|
371
396
|
}
|
|
372
397
|
|
|
373
398
|
// src/types/errors.ts
|
|
@@ -470,7 +495,10 @@ var ConfigError = class extends WrongStackError {
|
|
|
470
495
|
};
|
|
471
496
|
|
|
472
497
|
// src/types/secret-vault.ts
|
|
473
|
-
var
|
|
498
|
+
var ENCRYPTED_PREFIX_PATTERN = /^enc:v(\d+):/;
|
|
499
|
+
function encryptedPrefixForVersion(version) {
|
|
500
|
+
return `enc:v${version}:`;
|
|
501
|
+
}
|
|
474
502
|
|
|
475
503
|
// src/security/secret-vault.ts
|
|
476
504
|
var KEY_BYTES = 32;
|
|
@@ -478,6 +506,8 @@ var IV_BYTES = 12;
|
|
|
478
506
|
var TAG_BYTES = 16;
|
|
479
507
|
var ALGO = "aes-256-gcm";
|
|
480
508
|
var KEY_FILE_MODE = 384;
|
|
509
|
+
var KEY_FILE_MAGIC = Buffer.from("WSKV", "ascii");
|
|
510
|
+
var VERSIONED_KEY_FILE_SIZE = KEY_FILE_MAGIC.length + 1 + KEY_BYTES;
|
|
481
511
|
function checkKeyFilePermissions(keyFile) {
|
|
482
512
|
if (process.platform === "win32") return;
|
|
483
513
|
try {
|
|
@@ -500,11 +530,17 @@ function checkKeyFilePermissions(keyFile) {
|
|
|
500
530
|
var DefaultSecretVault = class {
|
|
501
531
|
keyFile;
|
|
502
532
|
key;
|
|
533
|
+
_keyVersion = 1;
|
|
503
534
|
constructor(opts) {
|
|
504
535
|
this.keyFile = opts.keyFile;
|
|
505
536
|
}
|
|
537
|
+
/** Current key version. Starts at 1; incremented by rotateKey(). */
|
|
538
|
+
get keyVersion() {
|
|
539
|
+
if (!this.key) this.loadOrCreateKey();
|
|
540
|
+
return this._keyVersion;
|
|
541
|
+
}
|
|
506
542
|
isEncrypted(value) {
|
|
507
|
-
return typeof value === "string" &&
|
|
543
|
+
return typeof value === "string" && ENCRYPTED_PREFIX_PATTERN.test(value);
|
|
508
544
|
}
|
|
509
545
|
encrypt(plaintext) {
|
|
510
546
|
if (this.isEncrypted(plaintext)) return plaintext;
|
|
@@ -513,11 +549,20 @@ var DefaultSecretVault = class {
|
|
|
513
549
|
const cipher = createCipheriv(ALGO, key, iv);
|
|
514
550
|
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
515
551
|
const tag = cipher.getAuthTag();
|
|
516
|
-
|
|
552
|
+
const prefix = encryptedPrefixForVersion(this._keyVersion);
|
|
553
|
+
return `${prefix}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
|
|
517
554
|
}
|
|
518
555
|
decrypt(value) {
|
|
519
556
|
if (!this.isEncrypted(value)) return value;
|
|
520
|
-
const
|
|
557
|
+
const prefixMatch = value.match(ENCRYPTED_PREFIX_PATTERN);
|
|
558
|
+
if (!prefixMatch) {
|
|
559
|
+
throw new ConfigError({
|
|
560
|
+
message: "SecretVault: malformed encrypted value",
|
|
561
|
+
code: ERROR_CODES.CONFIG_PARSE_FAILED,
|
|
562
|
+
context: { field: "encrypted_value" }
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
const rest = value.slice(prefixMatch[0].length);
|
|
521
566
|
const parts = rest.split(":");
|
|
522
567
|
if (parts.length !== 3) {
|
|
523
568
|
throw new ConfigError({
|
|
@@ -546,20 +591,64 @@ var DefaultSecretVault = class {
|
|
|
546
591
|
const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
547
592
|
return pt.toString("utf8");
|
|
548
593
|
}
|
|
594
|
+
/**
|
|
595
|
+
* Generate a new encryption key, write it to disk, and increment the key version.
|
|
596
|
+
* After rotation, encrypt() emits the new version prefix (e.g. enc:v2:).
|
|
597
|
+
* The caller must re-encrypt existing config values (see rotateConfigKeys()).
|
|
598
|
+
*/
|
|
599
|
+
rotateKey() {
|
|
600
|
+
const oldVersion = this._keyVersion;
|
|
601
|
+
const newKey = randomBytes(KEY_BYTES);
|
|
602
|
+
const newVersion = oldVersion + 1;
|
|
603
|
+
const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
|
|
604
|
+
KEY_FILE_MAGIC.copy(keyFileBuf, 0);
|
|
605
|
+
keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
|
|
606
|
+
newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
|
|
607
|
+
fs2.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
|
|
608
|
+
fs2.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
|
|
609
|
+
checkKeyFilePermissions(this.keyFile);
|
|
610
|
+
this.key = newKey;
|
|
611
|
+
this._keyVersion = newVersion;
|
|
612
|
+
return { oldVersion, newVersion };
|
|
613
|
+
}
|
|
549
614
|
loadOrCreateKey() {
|
|
550
615
|
if (this.key) return this.key;
|
|
551
616
|
try {
|
|
552
617
|
const buf = fs2.readFileSync(this.keyFile);
|
|
553
|
-
if (buf.length
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
618
|
+
if (buf.length === KEY_BYTES) {
|
|
619
|
+
this.key = buf;
|
|
620
|
+
this._keyVersion = 1;
|
|
621
|
+
checkKeyFilePermissions(this.keyFile);
|
|
622
|
+
return this.key;
|
|
623
|
+
}
|
|
624
|
+
if (buf.length === VERSIONED_KEY_FILE_SIZE) {
|
|
625
|
+
const magic = buf.subarray(0, KEY_FILE_MAGIC.length);
|
|
626
|
+
if (!magic.equals(KEY_FILE_MAGIC)) {
|
|
627
|
+
throw new ConfigError({
|
|
628
|
+
message: `SecretVault: key file ${this.keyFile} has invalid magic header`,
|
|
629
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
630
|
+
context: { keyFile: this.keyFile }
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
const version = buf[KEY_FILE_MAGIC.length];
|
|
634
|
+
const key2 = buf.subarray(KEY_FILE_MAGIC.length + 1);
|
|
635
|
+
if (key2.length !== KEY_BYTES) {
|
|
636
|
+
throw new ConfigError({
|
|
637
|
+
message: `SecretVault: key file ${this.keyFile} has wrong key size (${key2.length} bytes, expected ${KEY_BYTES})`,
|
|
638
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
639
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: key2.length }
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
this.key = Buffer.from(key2);
|
|
643
|
+
this._keyVersion = version;
|
|
644
|
+
checkKeyFilePermissions(this.keyFile);
|
|
645
|
+
return this.key;
|
|
559
646
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
647
|
+
throw new ConfigError({
|
|
648
|
+
message: `SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES} for v1 or ${VERSIONED_KEY_FILE_SIZE} for v2+). Remove it manually to generate a new key.`,
|
|
649
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
650
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
|
|
651
|
+
});
|
|
563
652
|
} catch (err) {
|
|
564
653
|
if (err.code !== "ENOENT") throw err;
|
|
565
654
|
}
|
|
@@ -570,18 +659,36 @@ var DefaultSecretVault = class {
|
|
|
570
659
|
} catch (err) {
|
|
571
660
|
if (err.code !== "EEXIST") throw err;
|
|
572
661
|
const buf = fs2.readFileSync(this.keyFile);
|
|
573
|
-
if (buf.length
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
});
|
|
662
|
+
if (buf.length === KEY_BYTES) {
|
|
663
|
+
this.key = buf;
|
|
664
|
+
this._keyVersion = 1;
|
|
665
|
+
checkKeyFilePermissions(this.keyFile);
|
|
666
|
+
return this.key;
|
|
579
667
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
668
|
+
if (buf.length === VERSIONED_KEY_FILE_SIZE) {
|
|
669
|
+
const magic = buf.subarray(0, KEY_FILE_MAGIC.length);
|
|
670
|
+
if (!magic.equals(KEY_FILE_MAGIC)) {
|
|
671
|
+
throw new ConfigError({
|
|
672
|
+
message: `SecretVault: key file ${this.keyFile} has invalid magic header`,
|
|
673
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
674
|
+
context: { keyFile: this.keyFile }
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
const version = buf[KEY_FILE_MAGIC.length];
|
|
678
|
+
const winnerKey = buf.subarray(KEY_FILE_MAGIC.length + 1);
|
|
679
|
+
this.key = Buffer.from(winnerKey);
|
|
680
|
+
this._keyVersion = version;
|
|
681
|
+
checkKeyFilePermissions(this.keyFile);
|
|
682
|
+
return this.key;
|
|
683
|
+
}
|
|
684
|
+
throw new ConfigError({
|
|
685
|
+
message: `SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES} for v1 or ${VERSIONED_KEY_FILE_SIZE} for v2+). Remove it manually to generate a new key.`,
|
|
686
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
687
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
|
|
688
|
+
});
|
|
583
689
|
}
|
|
584
690
|
this.key = key;
|
|
691
|
+
this._keyVersion = 1;
|
|
585
692
|
return key;
|
|
586
693
|
}
|
|
587
694
|
};
|
|
@@ -662,6 +769,89 @@ async function migratePlaintextSecrets(configPath, vault, logger) {
|
|
|
662
769
|
);
|
|
663
770
|
return { migrated: counter.n, file: configPath };
|
|
664
771
|
}
|
|
772
|
+
async function rotateConfigKeys(configPath, vault, logger) {
|
|
773
|
+
const log = logger?.info ?? (() => {
|
|
774
|
+
});
|
|
775
|
+
const warn = logger?.warn ?? ((msg) => console.warn(msg));
|
|
776
|
+
let raw;
|
|
777
|
+
try {
|
|
778
|
+
raw = await fs.readFile(configPath, "utf8");
|
|
779
|
+
} catch {
|
|
780
|
+
const { oldVersion: oldVersion2, newVersion: newVersion2 } = vault.rotateKey();
|
|
781
|
+
log(`[secret-vault] Key rotated (v${oldVersion2} \u2192 v${newVersion2}) \u2014 no config file to re-encrypt`);
|
|
782
|
+
return { rotated: 0, oldVersion: oldVersion2, newVersion: newVersion2, file: configPath };
|
|
783
|
+
}
|
|
784
|
+
let parsed;
|
|
785
|
+
try {
|
|
786
|
+
parsed = JSON.parse(raw);
|
|
787
|
+
} catch {
|
|
788
|
+
warn(`[secret-vault] Config file ${configPath} is not valid JSON \u2014 skipping rotation`);
|
|
789
|
+
return { rotated: 0, oldVersion: vault.keyVersion, newVersion: vault.keyVersion, file: configPath };
|
|
790
|
+
}
|
|
791
|
+
const counter = { n: 0, failed: [] };
|
|
792
|
+
const decrypted = walkDecryptCount(parsed, vault, counter);
|
|
793
|
+
if (counter.failed.length > 0) {
|
|
794
|
+
throw new Error(
|
|
795
|
+
`[secret-vault] Aborting key rotation: ${counter.failed.length} field(s) could not be decrypted with the current key and would be permanently lost on rotation: ${counter.failed.join(", ")}. Restore or remove these fields before rotating.`
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
if (counter.n === 0) {
|
|
799
|
+
const { oldVersion: oldVersion2, newVersion: newVersion2 } = vault.rotateKey();
|
|
800
|
+
log(`[secret-vault] Key rotated (v${oldVersion2} \u2192 v${newVersion2}) \u2014 no encrypted fields to re-encrypt`);
|
|
801
|
+
return { rotated: 0, oldVersion: oldVersion2, newVersion: newVersion2, file: configPath };
|
|
802
|
+
}
|
|
803
|
+
const { oldVersion, newVersion } = vault.rotateKey();
|
|
804
|
+
const reencrypted = walkReencrypt(decrypted, vault);
|
|
805
|
+
await atomicWrite(configPath, JSON.stringify(reencrypted, null, 2), { mode: 384 });
|
|
806
|
+
await restrictFilePermissions(configPath, { warn });
|
|
807
|
+
log(`[secret-vault] Key rotated (v${oldVersion} \u2192 v${newVersion}) \u2014 re-encrypted ${counter.n} field(s)`);
|
|
808
|
+
return { rotated: counter.n, oldVersion, newVersion, file: configPath };
|
|
809
|
+
}
|
|
810
|
+
function walkDecryptCount(node, vault, counter, pathPrefix = "") {
|
|
811
|
+
if (node === null || node === void 0) return node;
|
|
812
|
+
if (typeof node !== "object") return node;
|
|
813
|
+
if (Array.isArray(node)) {
|
|
814
|
+
return node.map(
|
|
815
|
+
(item, i) => walkDecryptCount(item, vault, counter, `${pathPrefix}[${i}]`)
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
819
|
+
for (const [k, v] of Object.entries(node)) {
|
|
820
|
+
const keyPath = pathPrefix ? `${pathPrefix}.${k}` : k;
|
|
821
|
+
if (typeof v === "string" && vault.isEncrypted(v)) {
|
|
822
|
+
try {
|
|
823
|
+
out[k] = vault.decrypt(v);
|
|
824
|
+
counter.n++;
|
|
825
|
+
} catch {
|
|
826
|
+
counter.failed.push(keyPath);
|
|
827
|
+
out[k] = v;
|
|
828
|
+
}
|
|
829
|
+
} else if (typeof v === "object" && v !== null) {
|
|
830
|
+
out[k] = walkDecryptCount(v, vault, counter, keyPath);
|
|
831
|
+
} else {
|
|
832
|
+
out[k] = v;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return out;
|
|
836
|
+
}
|
|
837
|
+
function walkReencrypt(node, vault) {
|
|
838
|
+
if (node === null || node === void 0) return node;
|
|
839
|
+
if (typeof node !== "object") return node;
|
|
840
|
+
if (Array.isArray(node)) {
|
|
841
|
+
return node.map((item) => walkReencrypt(item, vault));
|
|
842
|
+
}
|
|
843
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
844
|
+
for (const [k, v] of Object.entries(node)) {
|
|
845
|
+
if (typeof v === "string" && isSecretField(k) && v.length > 0 && !vault.isEncrypted(v)) {
|
|
846
|
+
out[k] = vault.encrypt(v);
|
|
847
|
+
} else if (typeof v === "object" && v !== null) {
|
|
848
|
+
out[k] = walkReencrypt(v, vault);
|
|
849
|
+
} else {
|
|
850
|
+
out[k] = v;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return out;
|
|
854
|
+
}
|
|
665
855
|
async function restrictFilePermissions(filePath, opts) {
|
|
666
856
|
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
667
857
|
if (process.platform === "win32") {
|
|
@@ -749,6 +939,14 @@ var DANGEROUS_FOR_SUBAGENTS = [
|
|
|
749
939
|
ToolCapabilities.CONFIG_MUTATE,
|
|
750
940
|
ToolCapabilities.PACKAGE_INSTALL
|
|
751
941
|
];
|
|
942
|
+
[
|
|
943
|
+
ToolCapabilities.FS_READ,
|
|
944
|
+
ToolCapabilities.FS_WRITE,
|
|
945
|
+
ToolCapabilities.NET_OUTBOUND,
|
|
946
|
+
ToolCapabilities.SHELL_ARBITRARY,
|
|
947
|
+
ToolCapabilities.SHELL_RESTRICTED,
|
|
948
|
+
ToolCapabilities.PACKAGE_INSTALL
|
|
949
|
+
];
|
|
752
950
|
function hasDangerousCapabilityForSubagents(toolOrCaps) {
|
|
753
951
|
if (!toolOrCaps) return false;
|
|
754
952
|
const input = toolOrCaps;
|
|
@@ -1199,9 +1397,12 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
1199
1397
|
const caps = tool.capabilities ?? [];
|
|
1200
1398
|
const hasAllowedCap = caps.some((c) => this.allowedCapabilities.includes(c));
|
|
1201
1399
|
const isMcp = _AutoApprovePermissionPolicy.isMcpTool(tool.name);
|
|
1202
|
-
const
|
|
1400
|
+
const dangerousNotAllowed = getDangerousCapabilities(tool).filter(
|
|
1401
|
+
(c) => !this.allowedCapabilities.includes(c)
|
|
1402
|
+
);
|
|
1403
|
+
const blocked = tool.permission === "deny" || isMcp || !hasAllowedCap || dangerousNotAllowed.length > 0;
|
|
1203
1404
|
if (blocked) {
|
|
1204
|
-
const reason = isMcp ? `MCP tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : tool.permission === "deny" ? "tool default deny" : `tool lacks allowed capability (has: ${caps.join(", ") || "none"}, allowed: ${this.allowedCapabilities.join(", ")})`;
|
|
1405
|
+
const reason = isMcp ? `MCP tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : tool.permission === "deny" ? "tool default deny" : dangerousNotAllowed.length > 0 ? `tool requires un-granted dangerous capability (needs: ${dangerousNotAllowed.join(", ")}, allowed: ${this.allowedCapabilities.join(", ")})` : `tool lacks allowed capability (has: ${caps.join(", ") || "none"}, allowed: ${this.allowedCapabilities.join(", ")})`;
|
|
1205
1406
|
return {
|
|
1206
1407
|
permission: "deny",
|
|
1207
1408
|
source: "subagent_guard",
|
|
@@ -1222,6 +1423,6 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
1222
1423
|
}
|
|
1223
1424
|
};
|
|
1224
1425
|
|
|
1225
|
-
export { AutoApprovePermissionPolicy, DANGEROUS_FOR_SUBAGENTS, DefaultPermissionPolicy, DefaultSecretScrubber, DefaultSecretVault, ToolCapabilities, decryptConfigSecrets, encryptConfigSecrets, getDangerousCapabilities, hasCapability, hasDangerousCapabilityForSubagents, isSecretField, migratePlaintextSecrets, rewriteConfigEncrypted };
|
|
1426
|
+
export { AutoApprovePermissionPolicy, DANGEROUS_FOR_SUBAGENTS, DefaultPermissionPolicy, DefaultSecretScrubber, DefaultSecretVault, ToolCapabilities, decryptConfigSecrets, encryptConfigSecrets, getDangerousCapabilities, hasCapability, hasDangerousCapabilityForSubagents, isSecretField, migratePlaintextSecrets, rewriteConfigEncrypted, rotateConfigKeys };
|
|
1226
1427
|
//# sourceMappingURL=index.js.map
|
|
1227
1428
|
//# sourceMappingURL=index.js.map
|