@blamejs/core 0.7.24 → 0.7.38
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 +28 -0
- package/index.js +4 -0
- package/lib/asn1-der.js +274 -0
- package/lib/audit.js +2 -0
- package/lib/compliance.js +114 -0
- package/lib/constants.js +8 -0
- package/lib/crypto.js +92 -0
- package/lib/dora.js +347 -0
- package/lib/framework-error.js +22 -0
- package/lib/gate-contract.js +20 -4
- package/lib/mail-auth.js +661 -0
- package/lib/mail-dkim.js +309 -4
- package/lib/mail.js +8 -0
- package/lib/network-smtp-policy.js +551 -0
- package/lib/network-tls.js +965 -0
- package/lib/network.js +7 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/lib/dora.js
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.dora — DORA Article 17 ICT-related incident-reporting workflow.
|
|
4
|
+
*
|
|
5
|
+
* Digital Operational Resilience Act (Regulation (EU) 2022/2554)
|
|
6
|
+
* Article 17 requires every "financial entity" subject to DORA to
|
|
7
|
+
* classify, document, and report ICT-related incidents according to
|
|
8
|
+
* the harmonized RTS template (Commission Delegated Regulation
|
|
9
|
+
* 2024/1772). This primitive is the framework hook — operators wire
|
|
10
|
+
* it into their incident-management workflow; the framework owns the
|
|
11
|
+
* classification rubric, the three-stage report shape (initial /
|
|
12
|
+
* intermediate / final), and the audit-chain integration.
|
|
13
|
+
*
|
|
14
|
+
* var dora = b.dora.create({ audit: b.audit });
|
|
15
|
+
*
|
|
16
|
+
* var classification = dora.classify({
|
|
17
|
+
* dataAffected: "phi" | "financial" | "personal" | "operational" | "none",
|
|
18
|
+
* systemsAffected: ["payments-gateway", "core-ledger"],
|
|
19
|
+
* durationMs: C.TIME.hours(4),
|
|
20
|
+
* severityIndicator: "critical" | "high" | "medium" | "low",
|
|
21
|
+
* economicImpact: { eur: 50000 },
|
|
22
|
+
* affectedClients: 1200,
|
|
23
|
+
* geographicScope: ["DE", "FR"],
|
|
24
|
+
* reputationalImpact: "media" | "internal" | "none",
|
|
25
|
+
* });
|
|
26
|
+
* // → { classification: "major" | "significant" | "minor",
|
|
27
|
+
* // mustReport: true|false, mustReportInitialBy: ms-since-detection,
|
|
28
|
+
* // reasons: [...] }
|
|
29
|
+
*
|
|
30
|
+
* var initial = dora.report({
|
|
31
|
+
* incidentId: "INC-2026-0042",
|
|
32
|
+
* classification: "major",
|
|
33
|
+
* stage: "initial",
|
|
34
|
+
* detectedAt: Date.now() - C.TIME.minutes(60),
|
|
35
|
+
* description: "Payment-gateway outage — 2-hour customer-facing impact",
|
|
36
|
+
* causeKnown: false,
|
|
37
|
+
* mitigationStarted: true,
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* // 72h after detection: intermediate update
|
|
41
|
+
* dora.report(Object.assign({}, initial, { stage: "intermediate", ... }));
|
|
42
|
+
* // 1 month later (or upon closure): final report
|
|
43
|
+
* dora.report(Object.assign({}, initial, { stage: "final", rootCause: "...", ... }));
|
|
44
|
+
*
|
|
45
|
+
* Audit posture (audit namespace "dora"):
|
|
46
|
+
* - dora.incident.classified — every classify() call
|
|
47
|
+
* - dora.incident.reported — every report() submission
|
|
48
|
+
* - dora.incident.draftFinal — every draftFinalReport() generation
|
|
49
|
+
*
|
|
50
|
+
* The primitive does NOT submit to ESAs / national supervisors — that
|
|
51
|
+
* step is operator-side (channel + credentials are operator-specific).
|
|
52
|
+
* The primitive produces the RTS-template-shaped record that the
|
|
53
|
+
* operator's submission code drops into the regulator's API.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
var lazyRequire = require("./lazy-require");
|
|
57
|
+
var validateOpts = require("./validate-opts");
|
|
58
|
+
var C = require("./constants");
|
|
59
|
+
var { DoraError } = require("./framework-error");
|
|
60
|
+
|
|
61
|
+
var audit = lazyRequire(function () { return require("./audit"); });
|
|
62
|
+
var observability = lazyRequire(function () { return require("./observability"); });
|
|
63
|
+
|
|
64
|
+
// ---- Classification thresholds (RTS 2024/1772 Articles 1-12) ----
|
|
65
|
+
|
|
66
|
+
// Article 1 — major incident criteria. An incident classifies as
|
|
67
|
+
// "major" when ANY of the following thresholds is met:
|
|
68
|
+
// - Critical/severe data integrity / confidentiality / availability
|
|
69
|
+
// impact
|
|
70
|
+
// - >= 100k clients affected OR >= 10% of clients
|
|
71
|
+
// - Economic impact >= 100k EUR
|
|
72
|
+
// - Cross-border (>= 2 EU member states) impact
|
|
73
|
+
// - Critical-process disruption >= 8h
|
|
74
|
+
// - Reputational impact (media coverage)
|
|
75
|
+
var MAJOR_INCIDENT_THRESHOLDS = Object.freeze({
|
|
76
|
+
affectedClientsAbsolute: 100000, // allow:raw-byte-literal — RTS 2024/1772 Art. 1(1)(a) regulator-fixed cap (100k clients)
|
|
77
|
+
affectedClientsPercentile: 0.10, // RTS Art. 1(1)(a) — 10% client base
|
|
78
|
+
economicImpactEur: 100000, // allow:raw-byte-literal — RTS 2024/1772 Art. 1(1)(c) regulator-fixed cap (100k EUR)
|
|
79
|
+
geographicMemberStates: 2, // RTS Art. 1(1)(d) — 2+ member states
|
|
80
|
+
durationCriticalProcessMs: C.TIME.hours(8), // RTS Art. 1(1)(e) — 8h
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Article 8 — significant incident (one threshold below major).
|
|
84
|
+
// Default threshold values per ESA guidelines.
|
|
85
|
+
var SIGNIFICANT_INCIDENT_THRESHOLDS = Object.freeze({
|
|
86
|
+
affectedClientsAbsolute: 10000, // allow:raw-byte-literal — ESA-guideline regulator-fixed cap (10k clients)
|
|
87
|
+
affectedClientsPercentile: 0.01, // 1% client base
|
|
88
|
+
economicImpactEur: 10000, // allow:raw-byte-literal — ESA-guideline regulator-fixed cap (10k EUR)
|
|
89
|
+
durationCriticalProcessMs: C.TIME.hours(2), // 2h
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Article 19 — initial report deadline: 24h from "first awareness".
|
|
93
|
+
// Article 19(4) — intermediate update: within 72h of initial.
|
|
94
|
+
// Article 19(6) — final report: within 1 month of initial.
|
|
95
|
+
var INITIAL_REPORT_DEADLINE_MS = C.TIME.hours(24);
|
|
96
|
+
var INTERMEDIATE_REPORT_DEADLINE_MS = C.TIME.hours(72);
|
|
97
|
+
var FINAL_REPORT_DEADLINE_MS = C.TIME.days(30);
|
|
98
|
+
|
|
99
|
+
var VALID_DATA_AFFECTED = ["phi", "financial", "personal", "operational", "none"];
|
|
100
|
+
var VALID_SEVERITY = ["critical", "high", "medium", "low"];
|
|
101
|
+
var VALID_REPUTATIONAL = ["media", "internal", "none"];
|
|
102
|
+
var VALID_STAGES = ["initial", "intermediate", "final"];
|
|
103
|
+
var VALID_CLASSIFICATIONS = ["major", "significant", "minor"];
|
|
104
|
+
|
|
105
|
+
// ---- Classification rubric ----
|
|
106
|
+
|
|
107
|
+
function _classifyImpl(input) {
|
|
108
|
+
var reasons = [];
|
|
109
|
+
var hitsMajor = 0;
|
|
110
|
+
var hitsSignificant = 0;
|
|
111
|
+
|
|
112
|
+
// 1. Severity indicator — critical alone qualifies as major.
|
|
113
|
+
if (input.severityIndicator === "critical") {
|
|
114
|
+
hitsMajor += 1;
|
|
115
|
+
reasons.push("severity-critical");
|
|
116
|
+
} else if (input.severityIndicator === "high") {
|
|
117
|
+
hitsSignificant += 1;
|
|
118
|
+
reasons.push("severity-high");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 2. Affected clients (absolute).
|
|
122
|
+
if (typeof input.affectedClients === "number" && input.affectedClients > 0) {
|
|
123
|
+
if (input.affectedClients >= MAJOR_INCIDENT_THRESHOLDS.affectedClientsAbsolute) {
|
|
124
|
+
hitsMajor += 1;
|
|
125
|
+
reasons.push("clients-major-absolute");
|
|
126
|
+
} else if (input.affectedClients >= SIGNIFICANT_INCIDENT_THRESHOLDS.affectedClientsAbsolute) {
|
|
127
|
+
hitsSignificant += 1;
|
|
128
|
+
reasons.push("clients-significant-absolute");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 3. Economic impact.
|
|
133
|
+
if (input.economicImpact && typeof input.economicImpact.eur === "number") {
|
|
134
|
+
if (input.economicImpact.eur >= MAJOR_INCIDENT_THRESHOLDS.economicImpactEur) {
|
|
135
|
+
hitsMajor += 1;
|
|
136
|
+
reasons.push("economic-major");
|
|
137
|
+
} else if (input.economicImpact.eur >= SIGNIFICANT_INCIDENT_THRESHOLDS.economicImpactEur) {
|
|
138
|
+
hitsSignificant += 1;
|
|
139
|
+
reasons.push("economic-significant");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 4. Geographic scope (cross-border).
|
|
144
|
+
if (Array.isArray(input.geographicScope) &&
|
|
145
|
+
input.geographicScope.length >= MAJOR_INCIDENT_THRESHOLDS.geographicMemberStates) {
|
|
146
|
+
hitsMajor += 1;
|
|
147
|
+
reasons.push("geographic-cross-border");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 5. Duration (critical-process disruption).
|
|
151
|
+
if (typeof input.durationMs === "number" && input.durationMs > 0) {
|
|
152
|
+
if (input.durationMs >= MAJOR_INCIDENT_THRESHOLDS.durationCriticalProcessMs) {
|
|
153
|
+
hitsMajor += 1;
|
|
154
|
+
reasons.push("duration-major");
|
|
155
|
+
} else if (input.durationMs >= SIGNIFICANT_INCIDENT_THRESHOLDS.durationCriticalProcessMs) {
|
|
156
|
+
hitsSignificant += 1;
|
|
157
|
+
reasons.push("duration-significant");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 6. Reputational impact (Article 1(1)(f)).
|
|
162
|
+
if (input.reputationalImpact === "media") {
|
|
163
|
+
hitsMajor += 1;
|
|
164
|
+
reasons.push("reputational-media");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 7. Sensitive data classes — phi / financial → at minimum significant.
|
|
168
|
+
if (input.dataAffected === "phi" || input.dataAffected === "financial") {
|
|
169
|
+
hitsSignificant += 1;
|
|
170
|
+
reasons.push("data-sensitive-" + input.dataAffected);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
var classification;
|
|
174
|
+
if (hitsMajor >= 1) {
|
|
175
|
+
classification = "major";
|
|
176
|
+
} else if (hitsSignificant >= 1) {
|
|
177
|
+
classification = "significant";
|
|
178
|
+
} else {
|
|
179
|
+
classification = "minor";
|
|
180
|
+
}
|
|
181
|
+
var mustReport = classification !== "minor";
|
|
182
|
+
return {
|
|
183
|
+
classification: classification,
|
|
184
|
+
mustReport: mustReport,
|
|
185
|
+
mustReportInitialByMs: mustReport ? INITIAL_REPORT_DEADLINE_MS : null,
|
|
186
|
+
reasons: reasons,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ---- Report-shape validators (RTS 2024/1772 templates) ----
|
|
191
|
+
|
|
192
|
+
function _validateReportInput(input) {
|
|
193
|
+
if (!input || typeof input !== "object") {
|
|
194
|
+
throw new DoraError("dora/bad-report",
|
|
195
|
+
"report: input must be an object");
|
|
196
|
+
}
|
|
197
|
+
if (typeof input.incidentId !== "string" || input.incidentId.length === 0) {
|
|
198
|
+
throw new DoraError("dora/missing-incident-id",
|
|
199
|
+
"report: incidentId is required (non-empty string)");
|
|
200
|
+
}
|
|
201
|
+
if (VALID_CLASSIFICATIONS.indexOf(input.classification) === -1) {
|
|
202
|
+
throw new DoraError("dora/bad-classification",
|
|
203
|
+
"report: classification must be one of " +
|
|
204
|
+
VALID_CLASSIFICATIONS.join(", ") + ", got " + JSON.stringify(input.classification));
|
|
205
|
+
}
|
|
206
|
+
if (VALID_STAGES.indexOf(input.stage) === -1) {
|
|
207
|
+
throw new DoraError("dora/bad-stage",
|
|
208
|
+
"report: stage must be one of " + VALID_STAGES.join(", ") +
|
|
209
|
+
", got " + JSON.stringify(input.stage));
|
|
210
|
+
}
|
|
211
|
+
if (typeof input.detectedAt !== "number" || !isFinite(input.detectedAt) || input.detectedAt <= 0) {
|
|
212
|
+
throw new DoraError("dora/bad-detected-at",
|
|
213
|
+
"report: detectedAt must be a positive ms-since-epoch number");
|
|
214
|
+
}
|
|
215
|
+
if (typeof input.description !== "string" || input.description.length === 0) {
|
|
216
|
+
throw new DoraError("dora/missing-description",
|
|
217
|
+
"report: description is required");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ---- Public surface ----
|
|
222
|
+
|
|
223
|
+
function create(opts) {
|
|
224
|
+
opts = opts || {};
|
|
225
|
+
validateOpts(opts, ["audit", "observability"], "dora.create");
|
|
226
|
+
var auditOn = opts.audit !== false;
|
|
227
|
+
|
|
228
|
+
function _emit(action, info) {
|
|
229
|
+
if (!auditOn) return;
|
|
230
|
+
try {
|
|
231
|
+
audit().safeEmit({
|
|
232
|
+
action: action,
|
|
233
|
+
outcome: info.outcome || "success",
|
|
234
|
+
metadata: info.metadata || {},
|
|
235
|
+
});
|
|
236
|
+
} catch (_e) { /* audit best-effort */ }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function classify(input) {
|
|
240
|
+
if (!input || typeof input !== "object") {
|
|
241
|
+
throw new DoraError("dora/bad-classify-input",
|
|
242
|
+
"classify: input must be an object");
|
|
243
|
+
}
|
|
244
|
+
if (input.dataAffected !== undefined &&
|
|
245
|
+
VALID_DATA_AFFECTED.indexOf(input.dataAffected) === -1) {
|
|
246
|
+
throw new DoraError("dora/bad-data-affected",
|
|
247
|
+
"classify: dataAffected must be one of " +
|
|
248
|
+
VALID_DATA_AFFECTED.join(", "));
|
|
249
|
+
}
|
|
250
|
+
if (input.severityIndicator !== undefined &&
|
|
251
|
+
VALID_SEVERITY.indexOf(input.severityIndicator) === -1) {
|
|
252
|
+
throw new DoraError("dora/bad-severity",
|
|
253
|
+
"classify: severityIndicator must be one of " + VALID_SEVERITY.join(", "));
|
|
254
|
+
}
|
|
255
|
+
if (input.reputationalImpact !== undefined &&
|
|
256
|
+
VALID_REPUTATIONAL.indexOf(input.reputationalImpact) === -1) {
|
|
257
|
+
throw new DoraError("dora/bad-reputational",
|
|
258
|
+
"classify: reputationalImpact must be one of " + VALID_REPUTATIONAL.join(", "));
|
|
259
|
+
}
|
|
260
|
+
var rv = _classifyImpl(input);
|
|
261
|
+
_emit("dora.incident.classified", {
|
|
262
|
+
metadata: {
|
|
263
|
+
classification: rv.classification,
|
|
264
|
+
mustReport: rv.mustReport,
|
|
265
|
+
reasons: rv.reasons,
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
return rv;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function report(input) {
|
|
272
|
+
_validateReportInput(input);
|
|
273
|
+
var record = {
|
|
274
|
+
incidentId: input.incidentId,
|
|
275
|
+
classification: input.classification,
|
|
276
|
+
stage: input.stage,
|
|
277
|
+
detectedAt: input.detectedAt,
|
|
278
|
+
reportedAt: Date.now(),
|
|
279
|
+
description: input.description,
|
|
280
|
+
causeKnown: input.causeKnown !== undefined ? !!input.causeKnown : null,
|
|
281
|
+
rootCause: input.rootCause || null,
|
|
282
|
+
mitigationStarted: input.mitigationStarted !== undefined ? !!input.mitigationStarted : null,
|
|
283
|
+
systemsAffected: input.systemsAffected || [],
|
|
284
|
+
affectedClients: input.affectedClients || null,
|
|
285
|
+
economicImpact: input.economicImpact || null,
|
|
286
|
+
geographicScope: input.geographicScope || [],
|
|
287
|
+
durationMs: input.durationMs || null,
|
|
288
|
+
reputationalImpact: input.reputationalImpact || null,
|
|
289
|
+
contactPoint: input.contactPoint || null,
|
|
290
|
+
// Article 19 deadline — operator-side scheduler uses this.
|
|
291
|
+
nextStageDueAt: null,
|
|
292
|
+
};
|
|
293
|
+
if (input.stage === "initial") {
|
|
294
|
+
record.nextStageDueAt = input.detectedAt + INTERMEDIATE_REPORT_DEADLINE_MS;
|
|
295
|
+
} else if (input.stage === "intermediate") {
|
|
296
|
+
record.nextStageDueAt = input.detectedAt + FINAL_REPORT_DEADLINE_MS;
|
|
297
|
+
}
|
|
298
|
+
_emit("dora.incident.reported", {
|
|
299
|
+
metadata: {
|
|
300
|
+
incidentId: record.incidentId,
|
|
301
|
+
classification: record.classification,
|
|
302
|
+
stage: record.stage,
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
try { observability().count("dora.incident.reported", 1, {
|
|
306
|
+
classification: record.classification, stage: record.stage,
|
|
307
|
+
}); } catch (_e) { /* obs best-effort */ }
|
|
308
|
+
return record;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function draftFinalReport(initialOrIntermediate) {
|
|
312
|
+
if (!initialOrIntermediate || typeof initialOrIntermediate !== "object") {
|
|
313
|
+
throw new DoraError("dora/bad-draft-input",
|
|
314
|
+
"draftFinalReport: input must be a prior report record");
|
|
315
|
+
}
|
|
316
|
+
var draft = Object.assign({}, initialOrIntermediate, {
|
|
317
|
+
stage: "final",
|
|
318
|
+
reportedAt: Date.now(),
|
|
319
|
+
// RTS Article 19(6) final-report shape — operator must fill before
|
|
320
|
+
// submission.
|
|
321
|
+
rootCause: initialOrIntermediate.rootCause || null,
|
|
322
|
+
remediationActions: [],
|
|
323
|
+
lessonsLearned: "",
|
|
324
|
+
preventiveMeasures: [],
|
|
325
|
+
});
|
|
326
|
+
_emit("dora.incident.draftFinal", {
|
|
327
|
+
metadata: { incidentId: draft.incidentId },
|
|
328
|
+
});
|
|
329
|
+
return draft;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
classify: classify,
|
|
334
|
+
report: report,
|
|
335
|
+
draftFinalReport: draftFinalReport,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
module.exports = {
|
|
340
|
+
create: create,
|
|
341
|
+
MAJOR_INCIDENT_THRESHOLDS: MAJOR_INCIDENT_THRESHOLDS,
|
|
342
|
+
SIGNIFICANT_INCIDENT_THRESHOLDS: SIGNIFICANT_INCIDENT_THRESHOLDS,
|
|
343
|
+
INITIAL_REPORT_DEADLINE_MS: INITIAL_REPORT_DEADLINE_MS,
|
|
344
|
+
INTERMEDIATE_REPORT_DEADLINE_MS: INTERMEDIATE_REPORT_DEADLINE_MS,
|
|
345
|
+
FINAL_REPORT_DEADLINE_MS: FINAL_REPORT_DEADLINE_MS,
|
|
346
|
+
DoraError: DoraError,
|
|
347
|
+
};
|
package/lib/framework-error.js
CHANGED
|
@@ -249,6 +249,24 @@ var GuardMarkdownError = defineClass("GuardMarkdownError", { alwaysPermane
|
|
|
249
249
|
// in addresses, bidi/null/control chars in headers + addresses, header-
|
|
250
250
|
// folding smuggling, BOM injection. alwaysPermanent.
|
|
251
251
|
var GuardEmailError = defineClass("GuardEmailError", { alwaysPermanent: true });
|
|
252
|
+
// DoraError covers DORA Article 17 incident-reporting workflow errors
|
|
253
|
+
// (classification refusal, report-shape validation, ESA-template
|
|
254
|
+
// generation, audit-chain integration). Permanent — these are
|
|
255
|
+
// configuration / submission errors, not transient.
|
|
256
|
+
var DoraError = defineClass("DoraError", { alwaysPermanent: true });
|
|
257
|
+
// ComplianceError covers compliance-coordinator misuse: unknown
|
|
258
|
+
// posture name, runtime-switch refusal, assertion failures.
|
|
259
|
+
// Permanent — these are configuration errors, not transient.
|
|
260
|
+
var ComplianceError = defineClass("ComplianceError", { alwaysPermanent: true });
|
|
261
|
+
// SmtpPolicyError covers MTA-STS / DANE / TLS-RPT misuse: bad-policy
|
|
262
|
+
// shape, fetch failures, TLSA-record format errors, missing records.
|
|
263
|
+
// Permanent — these are policy / DNS configuration errors, not
|
|
264
|
+
// transient.
|
|
265
|
+
var SmtpPolicyError = defineClass("SmtpPolicyError", { alwaysPermanent: true });
|
|
266
|
+
// MailAuthError covers SPF / DKIM-verify / DMARC / ARC misuse: bad
|
|
267
|
+
// record shape, fetch failures, missing keys, alignment issues.
|
|
268
|
+
// Permanent — DNS-config / message-shape errors, not transient.
|
|
269
|
+
var MailAuthError = defineClass("MailAuthError", { alwaysPermanent: true });
|
|
252
270
|
|
|
253
271
|
module.exports = {
|
|
254
272
|
FrameworkError: FrameworkError,
|
|
@@ -290,4 +308,8 @@ module.exports = {
|
|
|
290
308
|
GuardXmlError: GuardXmlError,
|
|
291
309
|
GuardMarkdownError: GuardMarkdownError,
|
|
292
310
|
GuardEmailError: GuardEmailError,
|
|
311
|
+
DoraError: DoraError,
|
|
312
|
+
ComplianceError: ComplianceError,
|
|
313
|
+
SmtpPolicyError: SmtpPolicyError,
|
|
314
|
+
MailAuthError: MailAuthError,
|
|
293
315
|
};
|
package/lib/gate-contract.js
CHANGED
|
@@ -66,6 +66,7 @@ var validateOpts = require("./validate-opts");
|
|
|
66
66
|
var { GateContractError } = require("./framework-error");
|
|
67
67
|
|
|
68
68
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
69
|
+
var compliance = lazyRequire(function () { return require("./compliance"); });
|
|
69
70
|
|
|
70
71
|
// Forensic-id token width (bytes); 64 bits is enough for cross-gate
|
|
71
72
|
// correlation in a single request scope.
|
|
@@ -804,12 +805,27 @@ function resolveProfileAndPosture(opts, cfg) {
|
|
|
804
805
|
}
|
|
805
806
|
overlay = cfg.profiles[opts.profile];
|
|
806
807
|
}
|
|
807
|
-
|
|
808
|
-
|
|
808
|
+
// Compliance-posture resolution — operator-supplied opt wins; if not
|
|
809
|
+
// given, fall back to the global posture set via b.compliance.set().
|
|
810
|
+
// The fallback IS the value-add of the top-level coordinator: every
|
|
811
|
+
// primitive with a compliancePosture opt picks up the deployment's
|
|
812
|
+
// declared posture without per-call wiring.
|
|
813
|
+
var posture = opts.compliancePosture;
|
|
814
|
+
if (typeof posture !== "string") {
|
|
815
|
+
var globalPosture;
|
|
816
|
+
try { globalPosture = compliance().current(); }
|
|
817
|
+
catch (_e) { globalPosture = null; }
|
|
818
|
+
if (typeof globalPosture === "string" &&
|
|
819
|
+
cfg.compliancePostures && cfg.compliancePostures[globalPosture]) {
|
|
820
|
+
posture = globalPosture;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
if (typeof posture === "string") {
|
|
824
|
+
if (!cfg.compliancePostures || !cfg.compliancePostures[posture]) {
|
|
809
825
|
throw ErrorClass.factory(prefix + ".bad-posture",
|
|
810
|
-
"unknown compliancePosture " + JSON.stringify(
|
|
826
|
+
"unknown compliancePosture " + JSON.stringify(posture));
|
|
811
827
|
}
|
|
812
|
-
overlay = Object.assign({}, overlay, cfg.compliancePostures[
|
|
828
|
+
overlay = Object.assign({}, overlay, cfg.compliancePostures[posture]);
|
|
813
829
|
}
|
|
814
830
|
return Object.assign({}, cfg.defaults || {}, overlay, opts);
|
|
815
831
|
}
|