@blamejs/core 0.14.11 → 0.14.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/CHANGELOG.md +2 -0
- package/README.md +1 -1
- package/lib/agent-idempotency.js +113 -0
- package/lib/agent-orchestrator.js +108 -0
- package/lib/agent-snapshot.js +137 -0
- package/lib/agent-tenant.js +193 -17
- package/lib/archive-wrap.js +234 -1
- package/lib/archive.js +1 -0
- package/lib/cluster.js +186 -14
- package/lib/crypto-field.js +5 -0
- package/lib/db.js +15 -0
- package/lib/validate-opts.js +24 -0
- package/lib/vault/rotate.js +175 -15
- package/lib/vault-aad.js +84 -33
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/vault-aad.js
CHANGED
|
@@ -147,13 +147,17 @@ function buildContextAad(parts) {
|
|
|
147
147
|
// 32 bytes). Constant-domain prefix prevents key collision with other
|
|
148
148
|
// uses of the vault root.
|
|
149
149
|
|
|
150
|
-
function _deriveKey(aadBytes) {
|
|
151
|
-
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
// a
|
|
156
|
-
//
|
|
150
|
+
function _deriveKey(aadBytes, rootKeysJson) {
|
|
151
|
+
// rootKeysJson lets the vault-key rotation pipeline derive the per-row
|
|
152
|
+
// key under a SPECIFIC vault root (old or new keypair) within one
|
|
153
|
+
// process; when omitted it uses the live singleton. The keys JSON
|
|
154
|
+
// includes the active keypair PEMs — hashing the whole serialized form
|
|
155
|
+
// gives a stable per-vault root secret. Rotating vault keys produces a
|
|
156
|
+
// different root, so prior AAD-sealed values must be re-sealed (the
|
|
157
|
+
// rotation pipeline walks them via sealRoot/unsealRoot/resealRoot).
|
|
158
|
+
var keysJson = (typeof rootKeysJson === "string" && rootKeysJson.length > 0)
|
|
159
|
+
? rootKeysJson
|
|
160
|
+
: vault().getKeysJson();
|
|
157
161
|
var rootHash = bCrypto().sha3Hash(keysJson);
|
|
158
162
|
var prefix = Buffer.from("vault.aad/v1/", "utf8");
|
|
159
163
|
var rootBuf = Buffer.from(rootHash, "hex");
|
|
@@ -161,7 +165,7 @@ function _deriveKey(aadBytes) {
|
|
|
161
165
|
return bCrypto().kdf(input, C.BYTES.bytes(32));
|
|
162
166
|
}
|
|
163
167
|
|
|
164
|
-
function
|
|
168
|
+
function _seal(plaintext, aadParts, rootKeysJson, suppressAudit) {
|
|
165
169
|
if (plaintext == null) {
|
|
166
170
|
throw new VaultAadError("vault-aad/bad-input",
|
|
167
171
|
"seal: plaintext is required (use null/undefined-stripping at the call site)");
|
|
@@ -176,26 +180,32 @@ function seal(plaintext, aadParts) {
|
|
|
176
180
|
"seal: value is already AAD-sealed (refuses to double-seal)");
|
|
177
181
|
}
|
|
178
182
|
var aadBytes = _canonicalize(aadParts);
|
|
179
|
-
var key = _deriveKey(aadBytes);
|
|
183
|
+
var key = _deriveKey(aadBytes, rootKeysJson);
|
|
180
184
|
var ptBuf = Buffer.from(plaintext, "utf8");
|
|
181
185
|
var packed = bCrypto().encryptPacked(ptBuf, key, aadBytes);
|
|
182
186
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
187
|
+
if (!suppressAudit) {
|
|
188
|
+
try {
|
|
189
|
+
audit().safeEmit({
|
|
190
|
+
action: "vault.aad.sealed",
|
|
191
|
+
outcome: "success",
|
|
192
|
+
actor: null,
|
|
193
|
+
metadata: {
|
|
194
|
+
aadKeys: Object.keys(aadParts).sort(), // allow:bare-canonicalize-walk — audit-emit metadata, not for signing
|
|
195
|
+
bytes: ptBuf.length,
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
} catch (_e) { /* drop-silent */ }
|
|
199
|
+
}
|
|
194
200
|
|
|
195
201
|
return AAD_PREFIX + packed.toString("base64");
|
|
196
202
|
}
|
|
197
203
|
|
|
198
|
-
function
|
|
204
|
+
function seal(plaintext, aadParts) {
|
|
205
|
+
return _seal(plaintext, aadParts, undefined, false);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function _unseal(value, aadParts, rootKeysJson, suppressAudit) {
|
|
199
209
|
if (value == null || typeof value !== "string") {
|
|
200
210
|
throw new VaultAadError("vault-aad/bad-input",
|
|
201
211
|
"unseal: value must be a non-empty string");
|
|
@@ -205,7 +215,7 @@ function unseal(value, aadParts) {
|
|
|
205
215
|
"unseal: value is not AAD-sealed (missing " + JSON.stringify(AAD_PREFIX) + " prefix)");
|
|
206
216
|
}
|
|
207
217
|
var aadBytes = _canonicalize(aadParts);
|
|
208
|
-
var key = _deriveKey(aadBytes);
|
|
218
|
+
var key = _deriveKey(aadBytes, rootKeysJson);
|
|
209
219
|
var packed;
|
|
210
220
|
try { packed = Buffer.from(value.slice(AAD_PREFIX.length), "base64"); }
|
|
211
221
|
catch (e) {
|
|
@@ -215,17 +225,19 @@ function unseal(value, aadParts) {
|
|
|
215
225
|
var pt;
|
|
216
226
|
try { pt = bCrypto().decryptPacked(packed, key, aadBytes); }
|
|
217
227
|
catch (e) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
228
|
+
if (!suppressAudit) {
|
|
229
|
+
try {
|
|
230
|
+
audit().safeEmit({
|
|
231
|
+
action: "vault.aad.unseal_failed",
|
|
232
|
+
outcome: "denied",
|
|
233
|
+
actor: null,
|
|
234
|
+
metadata: {
|
|
235
|
+
aadKeys: Object.keys(aadParts).sort(), // allow:bare-canonicalize-walk — audit-emit metadata, not for signing
|
|
236
|
+
reason: e.message,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
} catch (_e) { /* drop-silent */ }
|
|
240
|
+
}
|
|
229
241
|
throw new VaultAadError("vault-aad/aead-mismatch",
|
|
230
242
|
"unseal: AEAD authentication failed — value may have been tampered, " +
|
|
231
243
|
"copied from a different row, or sealed under different AAD");
|
|
@@ -233,6 +245,10 @@ function unseal(value, aadParts) {
|
|
|
233
245
|
return pt.toString("utf8");
|
|
234
246
|
}
|
|
235
247
|
|
|
248
|
+
function unseal(value, aadParts) {
|
|
249
|
+
return _unseal(value, aadParts, undefined, false);
|
|
250
|
+
}
|
|
251
|
+
|
|
236
252
|
function isAadSealed(value) {
|
|
237
253
|
return typeof value === "string" && value.indexOf(AAD_PREFIX) === 0;
|
|
238
254
|
}
|
|
@@ -246,10 +262,45 @@ function reseal(value, fromAad, toAad) {
|
|
|
246
262
|
return seal(plaintext, toAad);
|
|
247
263
|
}
|
|
248
264
|
|
|
265
|
+
// ---- explicit-root variants (vault-key rotation pipeline) ----
|
|
266
|
+
//
|
|
267
|
+
// The rotation pipeline must decrypt a cell under the OLD vault root and
|
|
268
|
+
// re-encrypt it under the NEW root within one process — the live-singleton
|
|
269
|
+
// _deriveKey cannot straddle two keypairs. These take the serialized vault
|
|
270
|
+
// keys JSON (b.vault.getKeysJson() output) for a specific root; the AAD
|
|
271
|
+
// tuple is unchanged, only the root differs. Per-cell audit is suppressed
|
|
272
|
+
// (the rotation pipeline has its own progress + verify reporting).
|
|
273
|
+
|
|
274
|
+
function sealRoot(plaintext, aadParts, rootKeysJson) {
|
|
275
|
+
if (typeof rootKeysJson !== "string" || rootKeysJson.length === 0) {
|
|
276
|
+
throw new VaultAadError("vault-aad/bad-root", "sealRoot: rootKeysJson (vault keys JSON) is required");
|
|
277
|
+
}
|
|
278
|
+
return _seal(plaintext, aadParts, rootKeysJson, true);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function unsealRoot(value, aadParts, rootKeysJson) {
|
|
282
|
+
if (typeof rootKeysJson !== "string" || rootKeysJson.length === 0) {
|
|
283
|
+
throw new VaultAadError("vault-aad/bad-root", "unsealRoot: rootKeysJson (vault keys JSON) is required");
|
|
284
|
+
}
|
|
285
|
+
return _unseal(value, aadParts, rootKeysJson, true);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Re-seal a value from the old root to the new root under the SAME AAD
|
|
289
|
+
// tuple: authenticate under the old root (throws aead-mismatch if the
|
|
290
|
+
// value was not sealed under oldRootJson + aadParts), then re-encrypt
|
|
291
|
+
// under the new root. The rotation pipeline composes this per cell.
|
|
292
|
+
function resealRoot(value, aadParts, oldRootJson, newRootJson) {
|
|
293
|
+
var plaintext = unsealRoot(value, aadParts, oldRootJson);
|
|
294
|
+
return sealRoot(plaintext, aadParts, newRootJson);
|
|
295
|
+
}
|
|
296
|
+
|
|
249
297
|
module.exports = {
|
|
250
298
|
seal: seal,
|
|
251
299
|
unseal: unseal,
|
|
252
300
|
reseal: reseal,
|
|
301
|
+
sealRoot: sealRoot,
|
|
302
|
+
unsealRoot: unsealRoot,
|
|
303
|
+
resealRoot: resealRoot,
|
|
253
304
|
isAadSealed: isAadSealed,
|
|
254
305
|
buildColumnAad: buildColumnAad,
|
|
255
306
|
buildContextAad: buildContextAad,
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:f81dd931-ce62-498a-ac6f-d9ac5b0be399",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-31T18:05:40.945Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.14.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.14.12",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.14.
|
|
25
|
+
"version": "0.14.12",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.14.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.14.12",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.14.
|
|
57
|
+
"ref": "@blamejs/core@0.14.12",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|