@blamejs/core 0.13.43 → 0.13.44
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/lib/a2a.js +11 -11
- package/lib/agent-snapshot.js +1 -1
- package/lib/ai-capability.js +20 -20
- package/lib/ai-dp.js +17 -17
- package/lib/ai-input.js +3 -3
- package/lib/ai-pref.js +9 -9
- package/lib/ai-quota.js +17 -17
- package/lib/arg-parser.js +38 -38
- package/lib/audit-sign.js +4 -4
- package/lib/auth/acr-vocabulary.js +4 -4
- package/lib/auth/auth-time-tracker.js +1 -1
- package/lib/auth/elevation-grant.js +10 -10
- package/lib/auth/step-up-policy.js +12 -12
- package/lib/auth/step-up.js +15 -15
- package/lib/boot-gates.js +6 -6
- package/lib/budr.js +6 -6
- package/lib/content-credentials.js +13 -13
- package/lib/dark-patterns.js +15 -15
- package/lib/ddl-change-control.js +37 -37
- package/lib/dr-runbook.js +7 -7
- package/lib/fapi2.js +9 -9
- package/lib/fdx.js +7 -7
- package/lib/graphql-federation.js +2 -2
- package/lib/iab-mspa.js +5 -5
- package/lib/iab-tcf.js +18 -18
- package/lib/mcp.js +13 -13
- package/lib/middleware/require-step-up.js +3 -3
- package/lib/sec-cyber.js +3 -3
- package/lib/sse.js +14 -14
- package/lib/tcpa-10dlc.js +5 -5
- package/lib/tenant-quota.js +18 -18
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/boot-gates.js
CHANGED
|
@@ -66,7 +66,7 @@ var DEFAULT_EXIT_CODE = 1;
|
|
|
66
66
|
async function run(gates, opts) {
|
|
67
67
|
opts = opts || {};
|
|
68
68
|
if (!Array.isArray(gates) || gates.length === 0) {
|
|
69
|
-
throw new BootGatesError("
|
|
69
|
+
throw new BootGatesError("boot-gates/bad-input",
|
|
70
70
|
"b.bootGates.run: gates must be a non-empty array");
|
|
71
71
|
}
|
|
72
72
|
var log = typeof opts.log === "function" ? opts.log : function (msg) {
|
|
@@ -82,7 +82,7 @@ async function run(gates, opts) {
|
|
|
82
82
|
// throw rather than terminating the process — operators that wire
|
|
83
83
|
// bootGates from their daemon main() pass `exit: process.exit.bind(process)`.
|
|
84
84
|
var exit = typeof opts.exit === "function" ? opts.exit : function (code) {
|
|
85
|
-
var e = new BootGatesError("
|
|
85
|
+
var e = new BootGatesError("boot-gates/no-exit-wired",
|
|
86
86
|
"b.bootGates.run: gate failed (exitCode=" + code + ") but no opts.exit handler was supplied; " +
|
|
87
87
|
"operators wire opts.exit to process.exit.bind(process) in their daemon main()");
|
|
88
88
|
e.exitCode = code;
|
|
@@ -96,26 +96,26 @@ async function run(gates, opts) {
|
|
|
96
96
|
var gate = gates[i];
|
|
97
97
|
if (!gate || typeof gate.name !== "string" || gate.name.length === 0 ||
|
|
98
98
|
typeof gate.fn !== "function") {
|
|
99
|
-
throw new BootGatesError("
|
|
99
|
+
throw new BootGatesError("boot-gates/bad-gate",
|
|
100
100
|
"b.bootGates.run: gates[" + i + "] must be { name: string, fn: function }");
|
|
101
101
|
}
|
|
102
102
|
var timeoutMs = gate.timeoutMs || DEFAULT_GATE_TIMEOUT_MS;
|
|
103
103
|
if (typeof timeoutMs !== "number" || !isFinite(timeoutMs) || timeoutMs < 1) {
|
|
104
|
-
throw new BootGatesError("
|
|
104
|
+
throw new BootGatesError("boot-gates/bad-timeout",
|
|
105
105
|
"b.bootGates.run: gates[" + i + "].timeoutMs must be a positive finite number");
|
|
106
106
|
}
|
|
107
107
|
var gateT0 = Date.now();
|
|
108
108
|
var failure = null;
|
|
109
109
|
try {
|
|
110
110
|
await safeAsync.withTimeout(Promise.resolve().then(gate.fn), timeoutMs,
|
|
111
|
-
new BootGatesError("
|
|
111
|
+
new BootGatesError("boot-gates/timeout",
|
|
112
112
|
"b.bootGates.run: gate '" + gate.name + "' exceeded " + timeoutMs + "ms"));
|
|
113
113
|
} catch (err) {
|
|
114
114
|
failure = err;
|
|
115
115
|
}
|
|
116
116
|
if (overallTimeoutMs !== undefined &&
|
|
117
117
|
Date.now() - t0 > overallTimeoutMs && failure === null) {
|
|
118
|
-
failure = new BootGatesError("
|
|
118
|
+
failure = new BootGatesError("boot-gates/overall-timeout",
|
|
119
119
|
"b.bootGates.run: overall budget " + overallTimeoutMs + "ms exceeded after gate '" +
|
|
120
120
|
gate.name + "'");
|
|
121
121
|
}
|
package/lib/budr.js
CHANGED
|
@@ -78,31 +78,31 @@ var declarations = new Map();
|
|
|
78
78
|
*/
|
|
79
79
|
function declare(opts) {
|
|
80
80
|
if (!opts || typeof opts !== "object") {
|
|
81
|
-
throw BudrError.factory("
|
|
81
|
+
throw BudrError.factory("budr/bad-opts", "budr.declare: opts required");
|
|
82
82
|
}
|
|
83
83
|
if (typeof opts.service !== "string" || opts.service.length === 0 ||
|
|
84
84
|
opts.service.length > SERVICE_MAX || !SERVICE_RE.test(opts.service)) {
|
|
85
|
-
throw BudrError.factory("
|
|
85
|
+
throw BudrError.factory("budr/bad-service",
|
|
86
86
|
"budr.declare: service must match " + SERVICE_RE);
|
|
87
87
|
}
|
|
88
88
|
numericBounds.requirePositiveFiniteIntIfPresent(opts.rtoMs, "budr.declare: rtoMs", BudrError, "BAD_RTO");
|
|
89
89
|
numericBounds.requirePositiveFiniteIntIfPresent(opts.rpoMs, "budr.declare: rpoMs", BudrError, "BAD_RPO");
|
|
90
90
|
if (typeof opts.rtoMs !== "number" || typeof opts.rpoMs !== "number") {
|
|
91
|
-
throw BudrError.factory("
|
|
91
|
+
throw BudrError.factory("budr/bad-targets",
|
|
92
92
|
"budr.declare: rtoMs and rpoMs are required positive integer milliseconds");
|
|
93
93
|
}
|
|
94
94
|
if (opts.tier !== undefined && TIERS.indexOf(opts.tier) === -1) {
|
|
95
|
-
throw BudrError.factory("
|
|
95
|
+
throw BudrError.factory("budr/bad-tier",
|
|
96
96
|
"budr.declare: tier must be one of " + TIERS.join(", "));
|
|
97
97
|
}
|
|
98
98
|
if (opts.criticality !== undefined && CRITICALITIES.indexOf(opts.criticality) === -1) {
|
|
99
|
-
throw BudrError.factory("
|
|
99
|
+
throw BudrError.factory("budr/bad-criticality",
|
|
100
100
|
"budr.declare: criticality must be one of " + CRITICALITIES.join(", "));
|
|
101
101
|
}
|
|
102
102
|
validateOpts.optionalNonEmptyString(opts.owner,
|
|
103
103
|
"budr.declare: owner", BudrError, "BAD_OWNER");
|
|
104
104
|
if (opts.citations !== undefined && !Array.isArray(opts.citations)) {
|
|
105
|
-
throw BudrError.factory("
|
|
105
|
+
throw BudrError.factory("budr/bad-citations",
|
|
106
106
|
"budr.declare: citations must be an array of strings");
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -48,7 +48,7 @@ var REQUIRED_FIELDS = ["provider", "system", "systemVersion", "contentId"];
|
|
|
48
48
|
|
|
49
49
|
function _validateBuildOpts(opts) {
|
|
50
50
|
if (!opts || typeof opts !== "object") {
|
|
51
|
-
throw ContentCredentialsError.factory("
|
|
51
|
+
throw ContentCredentialsError.factory("content-credentials/bad-opts",
|
|
52
52
|
"contentCredentials.build: opts required");
|
|
53
53
|
}
|
|
54
54
|
for (var i = 0; i < REQUIRED_FIELDS.length; i += 1) {
|
|
@@ -57,32 +57,32 @@ function _validateBuildOpts(opts) {
|
|
|
57
57
|
"contentCredentials.build: " + f, ContentCredentialsError, "MISSING_" + f.toUpperCase());
|
|
58
58
|
}
|
|
59
59
|
if (opts.provider.length > STR_LEN_MAX) {
|
|
60
|
-
throw ContentCredentialsError.factory("
|
|
60
|
+
throw ContentCredentialsError.factory("content-credentials/bad-provider",
|
|
61
61
|
"provider exceeds " + STR_LEN_MAX + " chars");
|
|
62
62
|
}
|
|
63
63
|
if (opts.system.length > ID_LEN_MAX || !ID_RE.test(opts.system)) {
|
|
64
|
-
throw ContentCredentialsError.factory("
|
|
64
|
+
throw ContentCredentialsError.factory("content-credentials/bad-system",
|
|
65
65
|
"system must match " + ID_RE);
|
|
66
66
|
}
|
|
67
67
|
if (opts.systemVersion.length > 64 || !SEMVER_RE.test(opts.systemVersion)) { // allow:raw-byte-literal — semver length cap, not bytes
|
|
68
|
-
throw ContentCredentialsError.factory("
|
|
68
|
+
throw ContentCredentialsError.factory("content-credentials/bad-version",
|
|
69
69
|
"systemVersion must be semver");
|
|
70
70
|
}
|
|
71
71
|
if (opts.contentId.length > ID_LEN_MAX || !ID_RE.test(opts.contentId)) {
|
|
72
|
-
throw ContentCredentialsError.factory("
|
|
72
|
+
throw ContentCredentialsError.factory("content-credentials/bad-content-id",
|
|
73
73
|
"contentId must match " + ID_RE);
|
|
74
74
|
}
|
|
75
75
|
if (opts.contentType !== undefined) {
|
|
76
76
|
if (typeof opts.contentType !== "string" || opts.contentType.length === 0 ||
|
|
77
77
|
opts.contentType.length > ID_LEN_MAX || !/^[a-zA-Z]+\/[A-Za-z0-9._+-]+$/.test(opts.contentType)) {
|
|
78
|
-
throw ContentCredentialsError.factory("
|
|
78
|
+
throw ContentCredentialsError.factory("content-credentials/bad-content-type",
|
|
79
79
|
"contentType must be a valid IANA media type");
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
if (opts.contentSha3 !== undefined) {
|
|
83
83
|
if (typeof opts.contentSha3 !== "string" || opts.contentSha3.length !== SHA3_HEX_LEN ||
|
|
84
84
|
!/^[a-f0-9]+$/i.test(opts.contentSha3)) {
|
|
85
|
-
throw ContentCredentialsError.factory("
|
|
85
|
+
throw ContentCredentialsError.factory("content-credentials/bad-content-hash",
|
|
86
86
|
"contentSha3 must be lowercase hex SHA3-512 (" + SHA3_HEX_LEN + " chars)");
|
|
87
87
|
}
|
|
88
88
|
}
|
|
@@ -229,7 +229,7 @@ function required(opts) {
|
|
|
229
229
|
function sign(manifest, opts) {
|
|
230
230
|
opts = opts || {};
|
|
231
231
|
if (!manifest || typeof manifest !== "object") {
|
|
232
|
-
throw ContentCredentialsError.factory("
|
|
232
|
+
throw ContentCredentialsError.factory("content-credentials/bad-manifest",
|
|
233
233
|
"contentCredentials.sign: manifest required");
|
|
234
234
|
}
|
|
235
235
|
validateOpts.requireNonEmptyString(opts.privateKeyPem,
|
|
@@ -368,7 +368,7 @@ function _cborUint(n) {
|
|
|
368
368
|
if (n < 256) return Buffer.from([0x18, n]); // allow:raw-byte-literal — CBOR threshold
|
|
369
369
|
if (n < 65536) return Buffer.from([0x19, (n >> 8) & 0xFF, n & 0xFF]); // allow:raw-byte-literal — CBOR threshold
|
|
370
370
|
if (n < 4294967296) return Buffer.from([0x1A, (n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF]); // allow:raw-byte-literal — CBOR threshold
|
|
371
|
-
throw ContentCredentialsError.factory("
|
|
371
|
+
throw ContentCredentialsError.factory("content-credentials/cbor-overflow", "cbor uint too large: " + n);
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
function _cborNint(n) {
|
|
@@ -397,13 +397,13 @@ function _cborArrayHeader(n) {
|
|
|
397
397
|
if (n < 24) return Buffer.from([0x80 | n]); // allow:raw-byte-literal — CBOR threshold
|
|
398
398
|
if (n < 256) return Buffer.from([0x98, n]); // allow:raw-byte-literal — CBOR threshold
|
|
399
399
|
if (n < 65536) return Buffer.from([0x99, (n >> 8) & 0xFF, n & 0xFF]); // allow:raw-byte-literal — CBOR threshold
|
|
400
|
-
throw ContentCredentialsError.factory("
|
|
400
|
+
throw ContentCredentialsError.factory("content-credentials/cbor-overflow", "cbor array too large: " + n);
|
|
401
401
|
}
|
|
402
402
|
|
|
403
403
|
function _cborMapHeader(n) {
|
|
404
404
|
if (n < 24) return Buffer.from([0xA0 | n]); // allow:raw-byte-literal — CBOR threshold
|
|
405
405
|
if (n < 256) return Buffer.from([0xB8, n]); // allow:raw-byte-literal — CBOR threshold
|
|
406
|
-
throw ContentCredentialsError.factory("
|
|
406
|
+
throw ContentCredentialsError.factory("content-credentials/cbor-overflow", "cbor map too large: " + n);
|
|
407
407
|
}
|
|
408
408
|
|
|
409
409
|
function _cborTag(tag) {
|
|
@@ -454,14 +454,14 @@ function _cborTag(tag) {
|
|
|
454
454
|
function signCose(manifest, opts) {
|
|
455
455
|
opts = opts || {};
|
|
456
456
|
if (!manifest || typeof manifest !== "object") {
|
|
457
|
-
throw ContentCredentialsError.factory("
|
|
457
|
+
throw ContentCredentialsError.factory("content-credentials/bad-manifest",
|
|
458
458
|
"contentCredentials.signCose: manifest required");
|
|
459
459
|
}
|
|
460
460
|
validateOpts.requireNonEmptyString(opts.privateKeyPem,
|
|
461
461
|
"contentCredentials.signCose: privateKeyPem", ContentCredentialsError, "BAD_KEY");
|
|
462
462
|
var algName = (opts.alg || "ml-dsa-87").toLowerCase();
|
|
463
463
|
if (!(algName in COSE_ALGS)) {
|
|
464
|
-
throw ContentCredentialsError.factory("
|
|
464
|
+
throw ContentCredentialsError.factory("content-credentials/bad-alg",
|
|
465
465
|
"contentCredentials.signCose: alg '" + algName +
|
|
466
466
|
"' not in COSE alg registry. Known: " + Object.keys(COSE_ALGS).join(", "));
|
|
467
467
|
}
|
package/lib/dark-patterns.js
CHANGED
|
@@ -64,47 +64,47 @@ var POSTURES = {
|
|
|
64
64
|
|
|
65
65
|
function _validateFlowOpts(opts, label, errorClass) {
|
|
66
66
|
if (!opts || typeof opts !== "object") {
|
|
67
|
-
throw errorClass.factory("
|
|
67
|
+
throw errorClass.factory("dark-patterns/bad-opts",
|
|
68
68
|
"darkPatterns.record" + label + ": opts required");
|
|
69
69
|
}
|
|
70
70
|
if (CHANNELS.indexOf(opts.channel) === -1) {
|
|
71
|
-
throw errorClass.factory("
|
|
71
|
+
throw errorClass.factory("dark-patterns/bad-channel",
|
|
72
72
|
"darkPatterns: channel must be one of " + CHANNELS.join(","));
|
|
73
73
|
}
|
|
74
74
|
if (typeof opts.clickCount !== "number" || !isFinite(opts.clickCount) ||
|
|
75
75
|
opts.clickCount < 1 || opts.clickCount > 50 ||
|
|
76
76
|
Math.floor(opts.clickCount) !== opts.clickCount) {
|
|
77
|
-
throw errorClass.factory("
|
|
77
|
+
throw errorClass.factory("dark-patterns/bad-clicks",
|
|
78
78
|
"darkPatterns: clickCount must be integer 1..50");
|
|
79
79
|
}
|
|
80
80
|
if (!opts.cta || typeof opts.cta !== "object") {
|
|
81
|
-
throw errorClass.factory("
|
|
81
|
+
throw errorClass.factory("dark-patterns/bad-cta",
|
|
82
82
|
"darkPatterns: cta object required (text, fontWeight, contrastRatio)");
|
|
83
83
|
}
|
|
84
84
|
if (typeof opts.cta.text !== "string" || opts.cta.text.length === 0 ||
|
|
85
85
|
opts.cta.text.length > STR_LEN_MAX) {
|
|
86
|
-
throw errorClass.factory("
|
|
86
|
+
throw errorClass.factory("dark-patterns/bad-cta-text",
|
|
87
87
|
"darkPatterns: cta.text must be 1-256 char string");
|
|
88
88
|
}
|
|
89
89
|
if (typeof opts.cta.fontWeight !== "number" || opts.cta.fontWeight < 100 ||
|
|
90
90
|
opts.cta.fontWeight > FONT_WEIGHT_MAX) {
|
|
91
|
-
throw errorClass.factory("
|
|
91
|
+
throw errorClass.factory("dark-patterns/bad-font-weight",
|
|
92
92
|
"darkPatterns: cta.fontWeight must be 100..1000");
|
|
93
93
|
}
|
|
94
94
|
if (typeof opts.cta.contrastRatio !== "number" ||
|
|
95
95
|
opts.cta.contrastRatio < 1 || opts.cta.contrastRatio > 21) {
|
|
96
|
-
throw errorClass.factory("
|
|
96
|
+
throw errorClass.factory("dark-patterns/bad-contrast",
|
|
97
97
|
"darkPatterns: cta.contrastRatio must be 1..21");
|
|
98
98
|
}
|
|
99
99
|
if (typeof opts.confirmations !== "number" ||
|
|
100
100
|
opts.confirmations < 0 || opts.confirmations > 10 ||
|
|
101
101
|
Math.floor(opts.confirmations) !== opts.confirmations) {
|
|
102
|
-
throw errorClass.factory("
|
|
102
|
+
throw errorClass.factory("dark-patterns/bad-confirmations",
|
|
103
103
|
"darkPatterns: confirmations must be integer 0..10");
|
|
104
104
|
}
|
|
105
105
|
if (typeof opts.resourceId !== "string" || opts.resourceId.length === 0 ||
|
|
106
106
|
opts.resourceId.length > STR_LEN_MAX) {
|
|
107
|
-
throw errorClass.factory("
|
|
107
|
+
throw errorClass.factory("dark-patterns/bad-resource-id",
|
|
108
108
|
"darkPatterns: resourceId must be 1-256 char string");
|
|
109
109
|
}
|
|
110
110
|
}
|
|
@@ -252,21 +252,21 @@ function assertParity(signup, cancel, opts) {
|
|
|
252
252
|
opts = opts || {};
|
|
253
253
|
var errorClass = opts.errorClass || DarkPatternsError;
|
|
254
254
|
if (!signup || signup.kind !== "signup") {
|
|
255
|
-
throw errorClass.factory("
|
|
255
|
+
throw errorClass.factory("dark-patterns/bad-signup-flow",
|
|
256
256
|
"darkPatterns.assertParity: signup must be a recorded signup flow");
|
|
257
257
|
}
|
|
258
258
|
if (!cancel || cancel.kind !== "cancel") {
|
|
259
|
-
throw errorClass.factory("
|
|
259
|
+
throw errorClass.factory("dark-patterns/bad-cancel-flow",
|
|
260
260
|
"darkPatterns.assertParity: cancel must be a recorded cancel flow");
|
|
261
261
|
}
|
|
262
262
|
if (signup.resourceId !== cancel.resourceId) {
|
|
263
|
-
throw errorClass.factory("
|
|
263
|
+
throw errorClass.factory("dark-patterns/resource-mismatch",
|
|
264
264
|
"darkPatterns.assertParity: resourceId differs between flows");
|
|
265
265
|
}
|
|
266
266
|
var postureName = opts.posture || "ftc-2024";
|
|
267
267
|
var posture = POSTURES[postureName];
|
|
268
268
|
if (!posture) {
|
|
269
|
-
throw errorClass.factory("
|
|
269
|
+
throw errorClass.factory("dark-patterns/bad-posture",
|
|
270
270
|
"darkPatterns.assertParity: unknown posture " + postureName);
|
|
271
271
|
}
|
|
272
272
|
|
|
@@ -427,11 +427,11 @@ function middleware(opts) {
|
|
|
427
427
|
opts = opts || {};
|
|
428
428
|
var errorClass = opts.errorClass || DarkPatternsError;
|
|
429
429
|
if (typeof opts.lookupAttestation !== "function") {
|
|
430
|
-
throw errorClass.factory("
|
|
430
|
+
throw errorClass.factory("dark-patterns/bad-opts",
|
|
431
431
|
"darkPatterns.middleware: lookupAttestation function required");
|
|
432
432
|
}
|
|
433
433
|
if (typeof opts.resourceIdFromReq !== "function") {
|
|
434
|
-
throw errorClass.factory("
|
|
434
|
+
throw errorClass.factory("dark-patterns/bad-opts",
|
|
435
435
|
"darkPatterns.middleware: resourceIdFromReq function required");
|
|
436
436
|
}
|
|
437
437
|
|
|
@@ -59,7 +59,7 @@ var DAY_NAMES = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
|
|
59
59
|
|
|
60
60
|
function _parseWindowSpec(spec) {
|
|
61
61
|
if (typeof spec !== "string" || spec.length === 0) {
|
|
62
|
-
throw new DdlChangeControlError("
|
|
62
|
+
throw new DdlChangeControlError("ddl-change-control/bad-window",
|
|
63
63
|
"windowSpec must be a non-empty string");
|
|
64
64
|
}
|
|
65
65
|
var trimmed = spec.trim();
|
|
@@ -68,11 +68,11 @@ function _parseWindowSpec(spec) {
|
|
|
68
68
|
}
|
|
69
69
|
var parts = trimmed.split(/\s+/);
|
|
70
70
|
if (parts.length !== 3) {
|
|
71
|
-
throw new DdlChangeControlError("
|
|
71
|
+
throw new DdlChangeControlError("ddl-change-control/bad-window",
|
|
72
72
|
"windowSpec must be 'always' or '<days> <HH:MM-HH:MM> UTC' - got " + JSON.stringify(spec));
|
|
73
73
|
}
|
|
74
74
|
if (parts[2].toUpperCase() !== "UTC") {
|
|
75
|
-
throw new DdlChangeControlError("
|
|
75
|
+
throw new DdlChangeControlError("ddl-change-control/bad-window",
|
|
76
76
|
"windowSpec timezone must be UTC - got " + parts[2]);
|
|
77
77
|
}
|
|
78
78
|
var days = new Set();
|
|
@@ -82,13 +82,13 @@ function _parseWindowSpec(spec) {
|
|
|
82
82
|
if (dp.indexOf("-") !== -1) {
|
|
83
83
|
var range = dp.split("-");
|
|
84
84
|
if (range.length !== 2) {
|
|
85
|
-
throw new DdlChangeControlError("
|
|
85
|
+
throw new DdlChangeControlError("ddl-change-control/bad-window",
|
|
86
86
|
"windowSpec day-range must be 'A-B' - got " + dp);
|
|
87
87
|
}
|
|
88
88
|
var lo = DAY_NAMES.indexOf(range[0]);
|
|
89
89
|
var hi = DAY_NAMES.indexOf(range[1]);
|
|
90
90
|
if (lo === -1 || hi === -1) {
|
|
91
|
-
throw new DdlChangeControlError("
|
|
91
|
+
throw new DdlChangeControlError("ddl-change-control/bad-window",
|
|
92
92
|
"windowSpec unknown day in range " + dp);
|
|
93
93
|
}
|
|
94
94
|
if (lo <= hi) {
|
|
@@ -100,7 +100,7 @@ function _parseWindowSpec(spec) {
|
|
|
100
100
|
} else {
|
|
101
101
|
var idx = DAY_NAMES.indexOf(dp);
|
|
102
102
|
if (idx === -1) {
|
|
103
|
-
throw new DdlChangeControlError("
|
|
103
|
+
throw new DdlChangeControlError("ddl-change-control/bad-window",
|
|
104
104
|
"windowSpec unknown day '" + dp + "'");
|
|
105
105
|
}
|
|
106
106
|
days.add(idx);
|
|
@@ -108,13 +108,13 @@ function _parseWindowSpec(spec) {
|
|
|
108
108
|
}
|
|
109
109
|
var hourParts = parts[1].split("-");
|
|
110
110
|
if (hourParts.length !== 2) {
|
|
111
|
-
throw new DdlChangeControlError("
|
|
111
|
+
throw new DdlChangeControlError("ddl-change-control/bad-window",
|
|
112
112
|
"windowSpec hour-range must be 'HH:MM-HH:MM' - got " + parts[1]);
|
|
113
113
|
}
|
|
114
114
|
var startMin = _parseHHMM(hourParts[0]);
|
|
115
115
|
var endMin = _parseHHMM(hourParts[1]);
|
|
116
116
|
if (startMin >= endMin) {
|
|
117
|
-
throw new DdlChangeControlError("
|
|
117
|
+
throw new DdlChangeControlError("ddl-change-control/bad-window",
|
|
118
118
|
"windowSpec start must be < end - got " + parts[1]);
|
|
119
119
|
}
|
|
120
120
|
return { always: false, days: days, startMin: startMin, endMin: endMin };
|
|
@@ -123,13 +123,13 @@ function _parseWindowSpec(spec) {
|
|
|
123
123
|
function _parseHHMM(s) {
|
|
124
124
|
var m = /^(\d{2}):(\d{2})$/.exec(s);
|
|
125
125
|
if (!m) {
|
|
126
|
-
throw new DdlChangeControlError("
|
|
126
|
+
throw new DdlChangeControlError("ddl-change-control/bad-window",
|
|
127
127
|
"windowSpec time must be HH:MM - got " + s);
|
|
128
128
|
}
|
|
129
129
|
var hh = parseInt(m[1], 10);
|
|
130
130
|
var mm = parseInt(m[2], 10);
|
|
131
131
|
if (hh < 0 || hh > 23 || mm < 0 || mm > 59) {
|
|
132
|
-
throw new DdlChangeControlError("
|
|
132
|
+
throw new DdlChangeControlError("ddl-change-control/bad-window",
|
|
133
133
|
"windowSpec time out of range - got " + s);
|
|
134
134
|
}
|
|
135
135
|
return hh * 60 + mm; // allow:raw-time-literal — converting HH:MM to minute-of-day, not "60 seconds"
|
|
@@ -212,28 +212,28 @@ function create(opts) {
|
|
|
212
212
|
"verifyWith", "store", "now", "selfApproval",
|
|
213
213
|
], "ddlChangeControl.create");
|
|
214
214
|
validateOpts.auditShape(opts.audit, "ddlChangeControl",
|
|
215
|
-
DdlChangeControlError, "
|
|
215
|
+
DdlChangeControlError, "ddl-change-control/bad-audit");
|
|
216
216
|
validateOpts.optionalFunction(opts.signWith,
|
|
217
|
-
"ddlChangeControl: signWith", DdlChangeControlError, "
|
|
217
|
+
"ddlChangeControl: signWith", DdlChangeControlError, "ddl-change-control/bad-signer");
|
|
218
218
|
validateOpts.optionalFunction(opts.verifyWith,
|
|
219
|
-
"ddlChangeControl: verifyWith", DdlChangeControlError, "
|
|
219
|
+
"ddlChangeControl: verifyWith", DdlChangeControlError, "ddl-change-control/bad-verifier");
|
|
220
220
|
validateOpts.optionalFunction(opts.now,
|
|
221
|
-
"ddlChangeControl: now", DdlChangeControlError, "
|
|
221
|
+
"ddlChangeControl: now", DdlChangeControlError, "ddl-change-control/bad-now");
|
|
222
222
|
validateOpts.optionalNonEmptyString(opts.posture,
|
|
223
|
-
"ddlChangeControl: posture", DdlChangeControlError, "
|
|
223
|
+
"ddlChangeControl: posture", DdlChangeControlError, "ddl-change-control/bad-posture");
|
|
224
224
|
|
|
225
225
|
var approvers = 2;
|
|
226
226
|
if (opts.approvers !== undefined) {
|
|
227
227
|
if (typeof opts.approvers !== "number" || !isFinite(opts.approvers) ||
|
|
228
228
|
opts.approvers < 1) {
|
|
229
|
-
throw new DdlChangeControlError("
|
|
229
|
+
throw new DdlChangeControlError("ddl-change-control/bad-approvers",
|
|
230
230
|
"approvers must be a positive integer");
|
|
231
231
|
}
|
|
232
232
|
approvers = Math.floor(opts.approvers);
|
|
233
233
|
}
|
|
234
234
|
var posture = opts.posture || null;
|
|
235
235
|
if (posture && POSTURES_REQUIRING_CHANGE_CONTROL.indexOf(posture) !== -1 && approvers < 2) {
|
|
236
|
-
throw new DdlChangeControlError("
|
|
236
|
+
throw new DdlChangeControlError("ddl-change-control/insufficient-approvers",
|
|
237
237
|
"posture '" + posture + "' requires approvers >= 2 (SOX 404 / PCI-DSS 6.5)");
|
|
238
238
|
}
|
|
239
239
|
|
|
@@ -267,11 +267,11 @@ function create(opts) {
|
|
|
267
267
|
async function propose(sql, options) {
|
|
268
268
|
options = options || {};
|
|
269
269
|
if (typeof sql !== "string" || sql.length === 0) {
|
|
270
|
-
throw new DdlChangeControlError("
|
|
270
|
+
throw new DdlChangeControlError("ddl-change-control/bad-sql",
|
|
271
271
|
"propose: sql must be a non-empty string");
|
|
272
272
|
}
|
|
273
273
|
if (typeof options.proposer !== "string" || options.proposer.length === 0) {
|
|
274
|
-
throw new DdlChangeControlError("
|
|
274
|
+
throw new DdlChangeControlError("ddl-change-control/missing-proposer",
|
|
275
275
|
"propose: opts.proposer is required (non-empty string)");
|
|
276
276
|
}
|
|
277
277
|
var changeId = generateToken(C.BYTES.bytes(16));
|
|
@@ -311,37 +311,37 @@ function create(opts) {
|
|
|
311
311
|
async function approve(changeId, approver, options) {
|
|
312
312
|
options = options || {};
|
|
313
313
|
if (typeof changeId !== "string" || changeId.length === 0) {
|
|
314
|
-
throw new DdlChangeControlError("
|
|
314
|
+
throw new DdlChangeControlError("ddl-change-control/bad-id",
|
|
315
315
|
"approve: changeId must be a non-empty string");
|
|
316
316
|
}
|
|
317
317
|
if (typeof approver !== "string" || approver.length === 0) {
|
|
318
|
-
throw new DdlChangeControlError("
|
|
318
|
+
throw new DdlChangeControlError("ddl-change-control/missing-approver",
|
|
319
319
|
"approve: approver must be a non-empty string");
|
|
320
320
|
}
|
|
321
321
|
var change = store.get(changeId);
|
|
322
322
|
if (!change) {
|
|
323
|
-
throw new DdlChangeControlError("
|
|
323
|
+
throw new DdlChangeControlError("ddl-change-control/unknown-change",
|
|
324
324
|
"approve: unknown changeId '" + changeId + "'");
|
|
325
325
|
}
|
|
326
326
|
if (change.state === STATE_REJECTED) {
|
|
327
|
-
throw new DdlChangeControlError("
|
|
327
|
+
throw new DdlChangeControlError("ddl-change-control/already-rejected",
|
|
328
328
|
"approve: change '" + changeId + "' is already rejected");
|
|
329
329
|
}
|
|
330
330
|
if (change.state === STATE_APPLIED) {
|
|
331
|
-
throw new DdlChangeControlError("
|
|
331
|
+
throw new DdlChangeControlError("ddl-change-control/already-applied",
|
|
332
332
|
"approve: change '" + changeId + "' is already applied");
|
|
333
333
|
}
|
|
334
334
|
if (!selfApprovalAllowed && approver === change.proposer) {
|
|
335
335
|
_emit("ddl.change.apply_refused", {
|
|
336
336
|
changeId: changeId, reason: "self-approval-denied", actor: approver,
|
|
337
337
|
}, "denied");
|
|
338
|
-
throw new DdlChangeControlError("
|
|
338
|
+
throw new DdlChangeControlError("ddl-change-control/self-approval-denied",
|
|
339
339
|
"approve: proposer '" + approver + "' cannot approve their own change under posture '" +
|
|
340
340
|
(posture || "default") + "'");
|
|
341
341
|
}
|
|
342
342
|
for (var i = 0; i < change.approvals.length; i++) {
|
|
343
343
|
if (change.approvals[i].approver === approver) {
|
|
344
|
-
throw new DdlChangeControlError("
|
|
344
|
+
throw new DdlChangeControlError("ddl-change-control/duplicate-approval",
|
|
345
345
|
"approve: '" + approver + "' has already approved this change");
|
|
346
346
|
}
|
|
347
347
|
}
|
|
@@ -373,20 +373,20 @@ function create(opts) {
|
|
|
373
373
|
|
|
374
374
|
async function reject(changeId, reviewer, reason) {
|
|
375
375
|
if (typeof changeId !== "string" || changeId.length === 0) {
|
|
376
|
-
throw new DdlChangeControlError("
|
|
376
|
+
throw new DdlChangeControlError("ddl-change-control/bad-id",
|
|
377
377
|
"reject: changeId must be a non-empty string");
|
|
378
378
|
}
|
|
379
379
|
if (typeof reviewer !== "string" || reviewer.length === 0) {
|
|
380
|
-
throw new DdlChangeControlError("
|
|
380
|
+
throw new DdlChangeControlError("ddl-change-control/missing-reviewer",
|
|
381
381
|
"reject: reviewer must be a non-empty string");
|
|
382
382
|
}
|
|
383
383
|
var change = store.get(changeId);
|
|
384
384
|
if (!change) {
|
|
385
|
-
throw new DdlChangeControlError("
|
|
385
|
+
throw new DdlChangeControlError("ddl-change-control/unknown-change",
|
|
386
386
|
"reject: unknown changeId '" + changeId + "'");
|
|
387
387
|
}
|
|
388
388
|
if (change.state === STATE_APPLIED) {
|
|
389
|
-
throw new DdlChangeControlError("
|
|
389
|
+
throw new DdlChangeControlError("ddl-change-control/already-applied",
|
|
390
390
|
"reject: change '" + changeId + "' is already applied");
|
|
391
391
|
}
|
|
392
392
|
change.state = STATE_REJECTED;
|
|
@@ -422,20 +422,20 @@ function create(opts) {
|
|
|
422
422
|
|
|
423
423
|
async function applyApproved(changeId, runner) {
|
|
424
424
|
if (typeof runner !== "function") {
|
|
425
|
-
throw new DdlChangeControlError("
|
|
425
|
+
throw new DdlChangeControlError("ddl-change-control/bad-runner",
|
|
426
426
|
"applyApproved: runner must be an async function (sql) => result");
|
|
427
427
|
}
|
|
428
428
|
var change = store.get(changeId);
|
|
429
429
|
if (!change) {
|
|
430
|
-
throw new DdlChangeControlError("
|
|
430
|
+
throw new DdlChangeControlError("ddl-change-control/unknown-change",
|
|
431
431
|
"applyApproved: unknown changeId '" + changeId + "'");
|
|
432
432
|
}
|
|
433
433
|
if (change.state === STATE_APPLIED) {
|
|
434
|
-
throw new DdlChangeControlError("
|
|
434
|
+
throw new DdlChangeControlError("ddl-change-control/already-applied",
|
|
435
435
|
"applyApproved: change '" + changeId + "' is already applied");
|
|
436
436
|
}
|
|
437
437
|
if (change.state === STATE_REJECTED) {
|
|
438
|
-
throw new DdlChangeControlError("
|
|
438
|
+
throw new DdlChangeControlError("ddl-change-control/already-rejected",
|
|
439
439
|
"applyApproved: change '" + changeId + "' is rejected");
|
|
440
440
|
}
|
|
441
441
|
if (change.approvals.length < approvers) {
|
|
@@ -443,7 +443,7 @@ function create(opts) {
|
|
|
443
443
|
changeId: changeId,
|
|
444
444
|
reason: "insufficient-approvals: " + change.approvals.length + "/" + approvers,
|
|
445
445
|
}, "denied");
|
|
446
|
-
throw new DdlChangeControlError("
|
|
446
|
+
throw new DdlChangeControlError("ddl-change-control/insufficient-approvals",
|
|
447
447
|
"applyApproved: change '" + changeId + "' has " + change.approvals.length +
|
|
448
448
|
" approvals; threshold is " + approvers);
|
|
449
449
|
}
|
|
@@ -451,7 +451,7 @@ function create(opts) {
|
|
|
451
451
|
_emit("ddl.change.apply_refused", {
|
|
452
452
|
changeId: changeId, reason: "window-closed",
|
|
453
453
|
}, "denied");
|
|
454
|
-
throw new DdlChangeControlError("
|
|
454
|
+
throw new DdlChangeControlError("ddl-change-control/window-closed",
|
|
455
455
|
"applyApproved: change '" + changeId + "' refused - outside allowed window");
|
|
456
456
|
}
|
|
457
457
|
var currentHash = _hashSql(change.sql);
|
|
@@ -459,7 +459,7 @@ function create(opts) {
|
|
|
459
459
|
_emit("ddl.change.apply_refused", {
|
|
460
460
|
changeId: changeId, reason: "sql-tampered",
|
|
461
461
|
}, "denied");
|
|
462
|
-
throw new DdlChangeControlError("
|
|
462
|
+
throw new DdlChangeControlError("ddl-change-control/sql-tampered",
|
|
463
463
|
"applyApproved: stored SQL no longer matches its hash - refusing to apply");
|
|
464
464
|
}
|
|
465
465
|
var startedAt = now();
|
package/lib/dr-runbook.js
CHANGED
|
@@ -232,7 +232,7 @@ function _renderTest(posture) {
|
|
|
232
232
|
* placeholder otherwise so the runbook never silently drops a
|
|
233
233
|
* required section.
|
|
234
234
|
*
|
|
235
|
-
* Throws `DrRunbookError("
|
|
235
|
+
* Throws `DrRunbookError("dr-runbook/unknown-posture")` when `posture`
|
|
236
236
|
* is not in the supported list.
|
|
237
237
|
*
|
|
238
238
|
* @opts
|
|
@@ -272,22 +272,22 @@ async function emit(opts) {
|
|
|
272
272
|
], "drRunbook.emit");
|
|
273
273
|
|
|
274
274
|
validateOpts.requireNonEmptyString(opts.outDir,
|
|
275
|
-
"drRunbook.emit: outDir", DrRunbookError, "
|
|
275
|
+
"drRunbook.emit: outDir", DrRunbookError, "dr-runbook/no-outdir");
|
|
276
276
|
validateOpts.requireNonEmptyString(opts.posture,
|
|
277
|
-
"drRunbook.emit: posture", DrRunbookError, "
|
|
277
|
+
"drRunbook.emit: posture", DrRunbookError, "dr-runbook/no-posture");
|
|
278
278
|
if (!POSTURE_BLOCKS[opts.posture]) {
|
|
279
|
-
throw new DrRunbookError("
|
|
279
|
+
throw new DrRunbookError("dr-runbook/unknown-posture",
|
|
280
280
|
"drRunbook.emit: posture '" + opts.posture + "' not in supported list (" +
|
|
281
281
|
Object.keys(POSTURE_BLOCKS).join(", ") + ")");
|
|
282
282
|
}
|
|
283
283
|
if (opts.services !== undefined && !Array.isArray(opts.services)) {
|
|
284
|
-
throw new DrRunbookError("
|
|
284
|
+
throw new DrRunbookError("dr-runbook/bad-services",
|
|
285
285
|
"drRunbook.emit: services must be an array of {name, rtoMs, rpoMs}");
|
|
286
286
|
}
|
|
287
287
|
validateOpts.optionalPositiveFinite(opts.rtoMs,
|
|
288
|
-
"drRunbook.emit: rtoMs", DrRunbookError, "
|
|
288
|
+
"drRunbook.emit: rtoMs", DrRunbookError, "dr-runbook/bad-rto");
|
|
289
289
|
validateOpts.optionalPositiveFinite(opts.rpoMs,
|
|
290
|
-
"drRunbook.emit: rpoMs", DrRunbookError, "
|
|
290
|
+
"drRunbook.emit: rpoMs", DrRunbookError, "dr-runbook/bad-rpo");
|
|
291
291
|
|
|
292
292
|
var auditOn = opts.audit !== false;
|
|
293
293
|
var postureBlock = POSTURE_BLOCKS[opts.posture];
|
package/lib/fapi2.js
CHANGED
|
@@ -135,17 +135,17 @@ var SENDER_CONSTRAINTS = ["dpop", "mtls"];
|
|
|
135
135
|
*/
|
|
136
136
|
function assertConformance(opts) {
|
|
137
137
|
if (!opts || typeof opts !== "object") {
|
|
138
|
-
throw Fapi2Error.factory("
|
|
138
|
+
throw Fapi2Error.factory("fapi2/bad-opts",
|
|
139
139
|
"fapi2.assertConformance: opts required");
|
|
140
140
|
}
|
|
141
141
|
if (SENDER_CONSTRAINTS.indexOf(opts.senderConstraint) === -1) {
|
|
142
|
-
throw Fapi2Error.factory("
|
|
142
|
+
throw Fapi2Error.factory("fapi2/bad-sender-constraint",
|
|
143
143
|
"fapi2.assertConformance: senderConstraint must be 'dpop' or 'mtls'");
|
|
144
144
|
}
|
|
145
145
|
var parRequired = opts.parRequired !== false;
|
|
146
146
|
var pkceMethod = opts.pkceMethod || "S256";
|
|
147
147
|
if (pkceMethod !== "S256") {
|
|
148
|
-
throw Fapi2Error.factory("
|
|
148
|
+
throw Fapi2Error.factory("fapi2/bad-pkce",
|
|
149
149
|
"fapi2.assertConformance: PKCE method must be S256 (FAPI 2.0 §5.3.1.1) — got '" +
|
|
150
150
|
pkceMethod + "'");
|
|
151
151
|
}
|
|
@@ -224,17 +224,17 @@ function assertConformance(opts) {
|
|
|
224
224
|
*/
|
|
225
225
|
function assertOAuthConfig(oauthOpts) {
|
|
226
226
|
if (!oauthOpts || typeof oauthOpts !== "object") {
|
|
227
|
-
throw Fapi2Error.factory("
|
|
227
|
+
throw Fapi2Error.factory("fapi2/bad-oauth-opts",
|
|
228
228
|
"fapi2.assertOAuthConfig: oauth opts required");
|
|
229
229
|
}
|
|
230
230
|
// PKCE — refuse pkce: false (b.auth.oauth.create already does this,
|
|
231
231
|
// but check explicitly for FAPI clarity).
|
|
232
232
|
if (oauthOpts.pkce === false) {
|
|
233
|
-
throw Fapi2Error.factory("
|
|
233
|
+
throw Fapi2Error.factory("fapi2/pkce-disabled",
|
|
234
234
|
"fapi2.assertOAuthConfig: PKCE is disabled — FAPI 2.0 §5.3.1.1 mandates S256");
|
|
235
235
|
}
|
|
236
236
|
if (oauthOpts.pkceMethod && oauthOpts.pkceMethod !== "S256") {
|
|
237
|
-
throw Fapi2Error.factory("
|
|
237
|
+
throw Fapi2Error.factory("fapi2/pkce-not-s256",
|
|
238
238
|
"fapi2.assertOAuthConfig: PKCE method '" + oauthOpts.pkceMethod +
|
|
239
239
|
"' is not S256 (FAPI 2.0 §5.3.1.1)");
|
|
240
240
|
}
|
|
@@ -242,16 +242,16 @@ function assertOAuthConfig(oauthOpts) {
|
|
|
242
242
|
var hasDpop = oauthOpts.dpop === true || oauthOpts.senderConstraint === "dpop";
|
|
243
243
|
var hasMtls = oauthOpts.mtls === true || oauthOpts.senderConstraint === "mtls";
|
|
244
244
|
if (!hasDpop && !hasMtls) {
|
|
245
|
-
throw Fapi2Error.factory("
|
|
245
|
+
throw Fapi2Error.factory("fapi2/no-sender-constraint",
|
|
246
246
|
"fapi2.assertOAuthConfig: FAPI 2.0 §5.3.2.5 requires sender-constrained tokens via DPoP OR mTLS — neither declared");
|
|
247
247
|
}
|
|
248
248
|
if (hasDpop && hasMtls) {
|
|
249
|
-
throw Fapi2Error.factory("
|
|
249
|
+
throw Fapi2Error.factory("fapi2/both-sender-constraints",
|
|
250
250
|
"fapi2.assertOAuthConfig: declare exactly one of DPoP / mTLS — both creates over-binding ambiguity");
|
|
251
251
|
}
|
|
252
252
|
// PAR
|
|
253
253
|
if (oauthOpts.par === false) {
|
|
254
|
-
throw Fapi2Error.factory("
|
|
254
|
+
throw Fapi2Error.factory("fapi2/par-disabled",
|
|
255
255
|
"fapi2.assertOAuthConfig: PAR is disabled — FAPI 2.0 §5.3.2.2 mandates Pushed Authorization Requests");
|
|
256
256
|
}
|
|
257
257
|
}
|