@a2a-exposed/control-core 0.8.33 → 0.8.35
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/index.js
CHANGED
|
@@ -307,7 +307,33 @@ var MAX_WAKE_PLAIN = (() => {
|
|
|
307
307
|
}
|
|
308
308
|
return n;
|
|
309
309
|
})();
|
|
310
|
-
var MAX_WAKE_SEALED = 3 + 16 + 1 + Math.ceil((MAX_WAKE_PLAIN * 3 + 16) * 4 / 3);
|
|
310
|
+
var MAX_WAKE_SEALED = 3 + 64 + 1 + 16 + 1 + Math.ceil((MAX_WAKE_PLAIN * 3 + 16) * 4 / 3);
|
|
311
|
+
function tenantSecretsKeyring(input) {
|
|
312
|
+
if (!input) return null;
|
|
313
|
+
if (!input.trimStart().startsWith("{")) return { active: "", keys: { "": input }, legacy: true };
|
|
314
|
+
if (input.length > 65536) return null;
|
|
315
|
+
try {
|
|
316
|
+
const value = JSON.parse(input);
|
|
317
|
+
if (!value || value.v !== 2 || typeof value.active !== "string" || !value.keys || typeof value.keys !== "object" || Array.isArray(value.keys) || Object.keys(value).some((name) => !["v", "active", "keys"].includes(name))) return null;
|
|
318
|
+
const entries = Object.entries(value.keys);
|
|
319
|
+
if (entries.length < 1 || entries.length > 2 || !Object.hasOwn(value.keys, value.active)) return null;
|
|
320
|
+
for (const [kid, key] of entries) {
|
|
321
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/.test(kid) || typeof key !== "string" || !key.trim() || key.length > 4096) return null;
|
|
322
|
+
}
|
|
323
|
+
return { active: value.active, keys: value.keys, legacy: false };
|
|
324
|
+
} catch {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function tenantSecretsMaterials(input) {
|
|
329
|
+
const ring = tenantSecretsKeyring(input);
|
|
330
|
+
if (!ring) return [];
|
|
331
|
+
return [ring.keys[ring.active], ...Object.entries(ring.keys).filter(([kid]) => kid !== ring.active).map(([, key]) => key)];
|
|
332
|
+
}
|
|
333
|
+
function unreadable() {
|
|
334
|
+
console.error(JSON.stringify({ event: "wake_secrets_unreadable", level: "error" }));
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
311
337
|
function b64(bytes2) {
|
|
312
338
|
let raw = "";
|
|
313
339
|
for (let i = 0; i < bytes2.length; i += 32768) raw += String.fromCharCode(...bytes2.subarray(i, i + 32768));
|
|
@@ -336,47 +362,68 @@ function payloadOf(secrets) {
|
|
|
336
362
|
return payload;
|
|
337
363
|
}
|
|
338
364
|
async function sealWakeSecrets(ikm, tenantId, secrets) {
|
|
339
|
-
const
|
|
365
|
+
const ring = tenantSecretsKeyring(ikm);
|
|
366
|
+
const key = ring ? await derive(ring.keys[ring.active], tenantId) : null;
|
|
340
367
|
const payload = payloadOf(secrets);
|
|
341
368
|
if (!key || !payload) return null;
|
|
342
369
|
const json = JSON.stringify(payload);
|
|
343
370
|
const encoded = new TextEncoder().encode(json);
|
|
344
371
|
if (json.length > MAX_WAKE_PLAIN || encoded.length > MAX_WAKE_PLAIN * 3) return null;
|
|
372
|
+
const prefix = ring.legacy ? "v1" : `v2.${ring.active}`;
|
|
373
|
+
const aad = ring.legacy ? tenantId : `${prefix}\0${tenantId}`;
|
|
345
374
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
346
375
|
const ct = new Uint8Array(await crypto.subtle.encrypt(
|
|
347
|
-
{ name: "AES-GCM", iv, additionalData: new TextEncoder().encode(
|
|
376
|
+
{ name: "AES-GCM", iv, additionalData: new TextEncoder().encode(aad) },
|
|
348
377
|
key,
|
|
349
378
|
encoded
|
|
350
379
|
));
|
|
351
|
-
const sealed =
|
|
380
|
+
const sealed = `${prefix}.${b64(iv)}.${b64(ct)}`;
|
|
352
381
|
return sealed.length <= MAX_WAKE_SEALED ? sealed : null;
|
|
353
382
|
}
|
|
354
383
|
async function openWakeSecrets(ikm, tenantId, sealed) {
|
|
355
384
|
if (sealed == null || sealed === "") return {};
|
|
356
|
-
if (sealed.length > MAX_WAKE_SEALED) return
|
|
357
|
-
const
|
|
358
|
-
if (!
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const
|
|
373
|
-
if (
|
|
374
|
-
|
|
385
|
+
if (typeof sealed !== "string" || sealed.length > MAX_WAKE_SEALED) return unreadable();
|
|
386
|
+
const ring = tenantSecretsKeyring(ikm);
|
|
387
|
+
if (!ring || !tenantId) return unreadable();
|
|
388
|
+
const parts = sealed.split(".");
|
|
389
|
+
const version = parts[0];
|
|
390
|
+
const kid = version === "v2" ? parts[1] : "";
|
|
391
|
+
if (version !== "v1" && version !== "v2") return unreadable();
|
|
392
|
+
if (parts.length !== (version === "v2" ? 4 : 3)) return unreadable();
|
|
393
|
+
const [iv, ct] = parts.slice(version === "v2" ? 2 : 1);
|
|
394
|
+
if (!iv || !ct || version === "v2" && (ring.legacy || !Object.hasOwn(ring.keys, kid))) return unreadable();
|
|
395
|
+
const materials = version === "v2" ? [ring.keys[kid]] : tenantSecretsMaterials(ikm);
|
|
396
|
+
const aad = version === "v2" ? `v2.${kid}\0${tenantId}` : tenantId;
|
|
397
|
+
for (const material of materials) {
|
|
398
|
+
try {
|
|
399
|
+
const key = await derive(material, tenantId);
|
|
400
|
+
if (!key) continue;
|
|
401
|
+
const ivBytes = unb64(iv);
|
|
402
|
+
if (ivBytes.length !== 12) return unreadable();
|
|
403
|
+
const plain = new TextDecoder().decode(await crypto.subtle.decrypt(
|
|
404
|
+
{ name: "AES-GCM", iv: ivBytes, additionalData: new TextEncoder().encode(aad) },
|
|
405
|
+
key,
|
|
406
|
+
unb64(ct)
|
|
407
|
+
));
|
|
408
|
+
if (plain.length > MAX_WAKE_PLAIN) return unreadable();
|
|
409
|
+
const parsed = JSON.parse(plain);
|
|
410
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return unreadable();
|
|
411
|
+
const out = {};
|
|
412
|
+
for (const name of WAKE_SECRET_KEYS) {
|
|
413
|
+
const value = parsed[name];
|
|
414
|
+
if (typeof value !== "string" || !value || value.length > MAX_WAKE_VALUE) continue;
|
|
415
|
+
out[name] = value;
|
|
416
|
+
}
|
|
417
|
+
return out;
|
|
418
|
+
} catch {
|
|
375
419
|
}
|
|
376
|
-
return out;
|
|
377
|
-
} catch {
|
|
378
|
-
return null;
|
|
379
420
|
}
|
|
421
|
+
return unreadable();
|
|
422
|
+
}
|
|
423
|
+
async function rewrapWakeSecrets(ikm, tenantId, sealed) {
|
|
424
|
+
if (!sealed) return null;
|
|
425
|
+
const secrets = await openWakeSecrets(ikm, tenantId, sealed);
|
|
426
|
+
return secrets ? sealWakeSecrets(ikm, tenantId, secrets) : null;
|
|
380
427
|
}
|
|
381
428
|
|
|
382
429
|
// src/tenants/create.ts
|
|
@@ -3192,8 +3239,12 @@ var BASIC_RE = /(basic\s+)(\S+)/gi;
|
|
|
3192
3239
|
var PEM_RE = /-----BEGIN (?:[A-Z0-9]+ )?PRIVATE KEY-----[\s\S]*?(?:-----END (?:[A-Z0-9]+ )?PRIVATE KEY-----|$)/g;
|
|
3193
3240
|
var API_KEY_RE = /(?:(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|\bAKIA[0-9A-Z]{16}\b|\bsk_(?:live|test)_[A-Za-z0-9]{10,}|\brk_(?:live|test)_[A-Za-z0-9]{10,}|\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}|\bxox[baprs]-[A-Za-z0-9-]{10,}|\bAIza[0-9A-Za-z_-]{35}\b)/g;
|
|
3194
3241
|
var JWT_RE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g;
|
|
3195
|
-
var AUTH_RE = /\b((?:proxy-)?authorization)(\s*[:=]\s*)([^\n]+)/gi;
|
|
3242
|
+
var AUTH_RE = /\b((?:proxy-)?authorization)(\s*[:=]\s*)([^\r\n]+)/gi;
|
|
3196
3243
|
var QUERY_RE = /(^|[?&#\s])([^?&#\s="'<>]+)(\s*=\s*)("(?:\\.|[^"\\])*(?:"|$)|'(?:\\.|[^'\\])*(?:'|$)|(?:\\(?!["'])|[^&#\s"'<>\\])*)/g;
|
|
3244
|
+
var HEADER_RE = /\b([A-Za-z][A-Za-z0-9_-]*)([ \t]*:[ \t]*)/g;
|
|
3245
|
+
var CONFIG_LINE_RE = /^([ \t]*)([A-Z_][A-Z0-9_]*)([ \t]*=[ \t]*)([^\r\n]*)/gm;
|
|
3246
|
+
var URL_PATH_RE = /(\bhttps?:\/\/[^/?#\s"'<>]+)([^?#\s"'<>]*)/gi;
|
|
3247
|
+
var QUERY_SECRET = /* @__PURE__ */ new Set(["key", "sig", "code", "codeverifier"]);
|
|
3197
3248
|
var ASSIGN_RE = /\b([A-Za-z_][A-Za-z0-9_]*)(\s*=\s*)/g;
|
|
3198
3249
|
var SECRET_EXACT = /* @__PURE__ */ new Set([
|
|
3199
3250
|
"authorization",
|
|
@@ -3203,6 +3254,8 @@ var SECRET_EXACT = /* @__PURE__ */ new Set([
|
|
|
3203
3254
|
"password",
|
|
3204
3255
|
"passwd",
|
|
3205
3256
|
"secret",
|
|
3257
|
+
"secrets",
|
|
3258
|
+
"signature",
|
|
3206
3259
|
"token",
|
|
3207
3260
|
"apikey",
|
|
3208
3261
|
"accesstoken",
|
|
@@ -3216,9 +3269,12 @@ var SECRET_EXACT = /* @__PURE__ */ new Set([
|
|
|
3216
3269
|
"bearer",
|
|
3217
3270
|
"webhookkey",
|
|
3218
3271
|
"accessclientid",
|
|
3219
|
-
"accessclientsecret"
|
|
3272
|
+
"accessclientsecret",
|
|
3273
|
+
"xhubsignature256"
|
|
3220
3274
|
]);
|
|
3221
3275
|
var SECRET_SUFFIX = [
|
|
3276
|
+
"secrets",
|
|
3277
|
+
"signature",
|
|
3222
3278
|
"authorization",
|
|
3223
3279
|
"token",
|
|
3224
3280
|
"secret",
|
|
@@ -3273,6 +3329,38 @@ function unquotedValueEnd(text, start) {
|
|
|
3273
3329
|
while (i < text.length && !/\s/.test(text[i])) i += 1;
|
|
3274
3330
|
return i;
|
|
3275
3331
|
}
|
|
3332
|
+
function jsonStringSpans(text) {
|
|
3333
|
+
const strings = [];
|
|
3334
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
3335
|
+
if (text[i] !== '"') continue;
|
|
3336
|
+
const end = quotedValueEnd(text, i);
|
|
3337
|
+
try {
|
|
3338
|
+
JSON.parse(text.slice(i, end));
|
|
3339
|
+
strings.push([i, end]);
|
|
3340
|
+
} catch {
|
|
3341
|
+
}
|
|
3342
|
+
i = end - 1;
|
|
3343
|
+
}
|
|
3344
|
+
return strings;
|
|
3345
|
+
}
|
|
3346
|
+
function redactHeaderLines(text) {
|
|
3347
|
+
const strings = jsonStringSpans(text);
|
|
3348
|
+
const pattern = new RegExp(HEADER_RE.source, "g");
|
|
3349
|
+
let out = "";
|
|
3350
|
+
let last = 0;
|
|
3351
|
+
let match;
|
|
3352
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
3353
|
+
if (!secretName(match[1]) || strings.some(([start2, end2]) => match.index >= start2 && match.index < end2)) continue;
|
|
3354
|
+
const start = pattern.lastIndex;
|
|
3355
|
+
let end = start;
|
|
3356
|
+
while (end < text.length && !/[\r\n]/.test(text[end])) end += 1;
|
|
3357
|
+
if (end === start) continue;
|
|
3358
|
+
out += text.slice(last, start) + REDACTED;
|
|
3359
|
+
last = end;
|
|
3360
|
+
pattern.lastIndex = end;
|
|
3361
|
+
}
|
|
3362
|
+
return out + text.slice(last);
|
|
3363
|
+
}
|
|
3276
3364
|
function redactAssignments(text) {
|
|
3277
3365
|
const pattern = new RegExp(ASSIGN_RE.source, "g");
|
|
3278
3366
|
const parameters = Array.from(text.matchAll(new RegExp(QUERY_RE.source, "g")));
|
|
@@ -3377,19 +3465,43 @@ function redactNested(value, depth, cache = /* @__PURE__ */ new Map()) {
|
|
|
3377
3465
|
return REDACTED;
|
|
3378
3466
|
}
|
|
3379
3467
|
}
|
|
3468
|
+
function redactEnvironmentLines(text) {
|
|
3469
|
+
const pattern = new RegExp(CONFIG_LINE_RE.source, "gm");
|
|
3470
|
+
let out = "";
|
|
3471
|
+
let last = 0;
|
|
3472
|
+
let match;
|
|
3473
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
3474
|
+
const [, indent, name, sep, item] = match;
|
|
3475
|
+
const structured = item.startsWith("{") || item.startsWith("[");
|
|
3476
|
+
const parameterList = /^[^\s<>]*[&#][^?&#\s="'<>]+[ \t]*=/.test(item) && !structured;
|
|
3477
|
+
if (!secretName(name) || !item || /^["']/.test(item) || parameterList || /\b[A-Za-z_][A-Za-z0-9_]*\s*=\s*$/.test(item)) continue;
|
|
3478
|
+
const start = match.index + indent.length + name.length + sep.length;
|
|
3479
|
+
const end = structured ? jsonValueEnd(text, start) : pattern.lastIndex;
|
|
3480
|
+
out += text.slice(last, start) + REDACTED;
|
|
3481
|
+
last = end;
|
|
3482
|
+
pattern.lastIndex = end;
|
|
3483
|
+
}
|
|
3484
|
+
return out + text.slice(last);
|
|
3485
|
+
}
|
|
3380
3486
|
function redactText(value, depth, cache) {
|
|
3381
3487
|
let text = redactJsonFields(value, depth, cache);
|
|
3382
3488
|
text = replaceAll(text, PEM_RE, REDACTED);
|
|
3489
|
+
text = redactEnvironmentLines(text);
|
|
3383
3490
|
text = replaceAll(text, JWT_RE, REDACTED);
|
|
3384
3491
|
text = replaceAll(text, API_KEY_RE, REDACTED);
|
|
3385
3492
|
text = replaceAll(text, TOKEN_RE, REDACTED);
|
|
3386
3493
|
text = replaceAll(text, BEARER_RE, `$1${REDACTED}`);
|
|
3387
3494
|
text = replaceAll(text, BASIC_RE, `$1${REDACTED}`);
|
|
3388
|
-
text =
|
|
3495
|
+
text = redactHeaderLines(text);
|
|
3496
|
+
const authStrings = jsonStringSpans(text);
|
|
3497
|
+
text = text.replace(AUTH_RE, (all, name, sep, rest, offset) => {
|
|
3498
|
+
if (authStrings.some(([start, end]) => offset >= start && offset < end)) return all;
|
|
3389
3499
|
if (rest.trim().length < 8 && !/bearer|basic/i.test(rest)) return `${name}${sep}${rest}`;
|
|
3390
3500
|
return `${name}${sep}${REDACTED}`;
|
|
3391
3501
|
});
|
|
3392
3502
|
text = replaceAll(text, USERINFO_RE, `$1${REDACTED}@`);
|
|
3503
|
+
text = text.replace(/(\/webhooks\/)\d+\/[^/?#\s"'<>]+/gi, `$1${REDACTED}/${REDACTED}`).replace(/(\/(?:hooks|webhooks)\/)[^/?#\s"'<>]+/gi, `$1${REDACTED}`).replace(/\/bot[0-9]+:[^/?#\s"'<>]+/gi, `/bot${REDACTED}`);
|
|
3504
|
+
text = replaceAll(text, URL_PATH_RE, (_all, origin, path) => origin + path.replace(/\/[^/]{16,}/g, `/${REDACTED}`));
|
|
3393
3505
|
const cleanParameter = (all, sep, name, assignment, item) => {
|
|
3394
3506
|
let decoded = name;
|
|
3395
3507
|
while (true) {
|
|
@@ -3408,7 +3520,7 @@ function redactText(value, depth, cache) {
|
|
|
3408
3520
|
setup = /^[A-Za-z0-9_-]{16,}/.test(candidate);
|
|
3409
3521
|
}
|
|
3410
3522
|
if (!item) return all;
|
|
3411
|
-
if (!secretName(decoded) && !setup) {
|
|
3523
|
+
if (!secretName(decoded) && !QUERY_SECRET.has(compactName(decoded)) && !setup) {
|
|
3412
3524
|
const quote = item[0];
|
|
3413
3525
|
const wrapped = (quote === '"' || quote === "'") && item.endsWith(quote) && item.length > 1;
|
|
3414
3526
|
const cleaned = wrapped ? quote + redactNested(item.slice(1, -1), depth + 1, cache) + quote : redactNested(item, depth + 1, cache);
|
|
@@ -3416,17 +3528,7 @@ function redactText(value, depth, cache) {
|
|
|
3416
3528
|
}
|
|
3417
3529
|
return `${sep}${name}${assignment}${REDACTED}`;
|
|
3418
3530
|
};
|
|
3419
|
-
const strings =
|
|
3420
|
-
for (let i = 0; i < text.length; i += 1) {
|
|
3421
|
-
if (text[i] !== '"') continue;
|
|
3422
|
-
const end = quotedValueEnd(text, i);
|
|
3423
|
-
try {
|
|
3424
|
-
JSON.parse(text.slice(i, end));
|
|
3425
|
-
strings.push([i, end]);
|
|
3426
|
-
} catch {
|
|
3427
|
-
}
|
|
3428
|
-
i = end - 1;
|
|
3429
|
-
}
|
|
3531
|
+
const strings = jsonStringSpans(text);
|
|
3430
3532
|
const parameters = new RegExp(QUERY_RE.source, "g");
|
|
3431
3533
|
let out = "";
|
|
3432
3534
|
let last = 0;
|
|
@@ -3478,6 +3580,17 @@ function walk(value, seen) {
|
|
|
3478
3580
|
return value.map((item) => walk(item, seen));
|
|
3479
3581
|
}
|
|
3480
3582
|
const proto = Object.getPrototypeOf(value);
|
|
3583
|
+
if (value instanceof Error) {
|
|
3584
|
+
const out2 = new Error();
|
|
3585
|
+
Object.setPrototypeOf(out2, proto);
|
|
3586
|
+
delete out2.stack;
|
|
3587
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
3588
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
3589
|
+
const item = "value" in descriptor && !(typeof key === "string" && secretName(key)) ? walk(descriptor.value, seen) : REDACTED;
|
|
3590
|
+
Object.defineProperty(out2, key, { value: item, enumerable: descriptor.enumerable, configurable: true, writable: true });
|
|
3591
|
+
}
|
|
3592
|
+
return out2;
|
|
3593
|
+
}
|
|
3481
3594
|
if (proto !== Object.prototype && proto !== null) return value;
|
|
3482
3595
|
const record = value;
|
|
3483
3596
|
const hidden = /* @__PURE__ */ new Set();
|
|
@@ -3606,6 +3719,7 @@ export {
|
|
|
3606
3719
|
resultList,
|
|
3607
3720
|
resultObject,
|
|
3608
3721
|
resumeFields,
|
|
3722
|
+
rewrapWakeSecrets,
|
|
3609
3723
|
rotateOwnerToken,
|
|
3610
3724
|
routes,
|
|
3611
3725
|
runInboxStep,
|
|
@@ -6,7 +6,7 @@ export { rotateOwnerToken } from "../tenants/rotate.js";
|
|
|
6
6
|
export type { RotateResult } from "../tenants/rotate.js";
|
|
7
7
|
export { WAKE_PRESETS, configFields, saveWakeSettings, tenantForAccount, wakeHeaderProblem, wakeSettingsOf, wakeUrlProblem, } from "../tenants/wake.js";
|
|
8
8
|
export type { WakeSave, WakeSaveInput, WakeSettings } from "../tenants/wake.js";
|
|
9
|
-
export { MAX_WAKE_PLAIN, MAX_WAKE_SEALED, MAX_WAKE_VALUE, WAKE_SECRET_KEYS, openWakeSecrets, sealWakeSecrets, } from "../tenants/wake-secrets.js";
|
|
9
|
+
export { MAX_WAKE_PLAIN, MAX_WAKE_SEALED, MAX_WAKE_VALUE, WAKE_SECRET_KEYS, openWakeSecrets, rewrapWakeSecrets, sealWakeSecrets, } from "../tenants/wake-secrets.js";
|
|
10
10
|
export type { WakeSecretKey, WakeSecrets } from "../tenants/wake-secrets.js";
|
|
11
11
|
export { RESERVED_NAMES, TENANT_NAME, tenantNameProblem } from "../tenants/reserved.js";
|
|
12
12
|
export { httpsOrigin, newApprovalClient } from "../tenants/approval.js";
|
|
@@ -32,7 +32,7 @@ export interface ControlEnv extends PlaneEnv {
|
|
|
32
32
|
SIGNUP_ALLOWLIST?: string;
|
|
33
33
|
/** Default agents per account. Unset or empty: no cap, unless that account sets agent_limit. An explicit non-negative integer sets the cap. */
|
|
34
34
|
FREE_AGENT_LIMIT?: string;
|
|
35
|
-
/** Same
|
|
35
|
+
/** Same raw key or JSON rotation keyring as the data-plane Worker. Seals tenant secrets. Unset: a new key cannot be saved. */
|
|
36
36
|
TENANT_SECRETS_KEY?: string;
|
|
37
37
|
/**
|
|
38
38
|
* Fine-grained token with Issues write on FEEDBACK_REPO. A secret.
|
|
@@ -9,17 +9,29 @@ export declare const MAX_WAKE_VALUE = 4096;
|
|
|
9
9
|
* and every known field may be present. `openWakeSecrets` accepts this and nothing larger.
|
|
10
10
|
*/
|
|
11
11
|
export declare const MAX_WAKE_PLAIN: number;
|
|
12
|
-
/**
|
|
12
|
+
/** Longest envelope: `v2.<64-char kid>.<iv>.<ciphertext>`. */
|
|
13
13
|
export declare const MAX_WAKE_SEALED: number;
|
|
14
|
+
/** A bounded keyring lives in the existing secret binding, never in public config. */
|
|
15
|
+
export interface TenantSecretsKeyring {
|
|
16
|
+
active: string;
|
|
17
|
+
keys: Record<string, string>;
|
|
18
|
+
legacy: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Raw keys preserve v1 writes for a reader-first rollout. JSON keyrings enable v2 writes. */
|
|
21
|
+
export declare function tenantSecretsKeyring(input: string): TenantSecretsKeyring | null;
|
|
22
|
+
/** Current material first, then the single read-only rotation key. No caller may log these values. */
|
|
23
|
+
export declare function tenantSecretsMaterials(input: string): string[];
|
|
14
24
|
/**
|
|
15
25
|
* Config written next to a sealed blob. The sealed fields stay in `secrets_enc`.
|
|
16
26
|
* With no ciphertext, the config is returned unchanged.
|
|
17
27
|
*/
|
|
18
28
|
export declare function omitSealedSecrets<T extends Record<string, unknown>>(config: T, secretsEnc: string | null | undefined): T;
|
|
19
|
-
/** `
|
|
29
|
+
/** `v2.<kid>.<iv>.<ciphertext>` for keyrings; raw keys keep v1 writes until rollout completes. */
|
|
20
30
|
export declare function sealWakeSecrets(ikm: string, tenantId: string, secrets: WakeSecrets): Promise<string | null>;
|
|
21
31
|
/**
|
|
22
32
|
* The sealed fields, `{}` when there is nothing to open, or null when the blob cannot be read.
|
|
23
33
|
* A null result must not be treated as "no secrets": the caller keeps the previous ciphertext.
|
|
24
34
|
*/
|
|
25
35
|
export declare function openWakeSecrets(ikm: string, tenantId: string, sealed: string | null | undefined): Promise<WakeSecrets | null>;
|
|
36
|
+
/** Pure maintenance helper. The caller must CAS the original blob and queue the new tenant config. */
|
|
37
|
+
export declare function rewrapWakeSecrets(ikm: string, tenantId: string, sealed: string): Promise<string | null>;
|