@blamejs/core 0.9.24 → 0.9.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 +885 -871
- package/index.js +32 -1
- package/lib/agent-audit.js +45 -0
- package/lib/agent-event-bus.js +336 -0
- package/lib/agent-idempotency.js +2 -8
- package/lib/agent-orchestrator.js +2 -8
- package/lib/agent-posture-chain.js +208 -0
- package/lib/agent-saga.js +191 -0
- package/lib/agent-snapshot.js +346 -0
- package/lib/agent-stream.js +2 -8
- package/lib/agent-tenant.js +308 -0
- package/lib/agent-trace.js +218 -0
- package/lib/guard-all.js +1 -0
- package/lib/guard-dsn.js +379 -0
- package/lib/guard-envelope.js +294 -0
- package/lib/guard-event-bus-payload.js +217 -0
- package/lib/guard-event-bus-topic.js +150 -0
- package/lib/guard-posture-chain.js +201 -0
- package/lib/guard-saga-config.js +157 -0
- package/lib/guard-smtp-command.js +484 -0
- package/lib/guard-snapshot-envelope.js +168 -0
- package/lib/guard-tenant-id.js +138 -0
- package/lib/guard-trace-context.js +172 -0
- package/lib/ip-utils.js +102 -0
- package/lib/mail-auth.js +4 -35
- package/lib/mail-greylist.js +448 -0
- package/lib/mail-helo.js +473 -0
- package/lib/mail-rbl.js +392 -0
- package/lib/mail.js +2 -1
- package/lib/network-dns-resolver.js +500 -0
- package/lib/network.js +1 -0
- package/lib/redis-client.js +2 -1
- package/lib/safe-dns.js +665 -0
- package/lib/tracing.js +36 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.guardPostureChain
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Posture Chain
|
|
6
|
+
* @order 442
|
|
7
|
+
*
|
|
8
|
+
* @intro
|
|
9
|
+
* Validates cross-boundary posture-chain envelopes. The envelope
|
|
10
|
+
* carries the set of compliance regimes the call is operating
|
|
11
|
+
* under (`postureSet: ["hipaa", "pci-dss"]`), the hop trail
|
|
12
|
+
* (`chainTrail: ["api-gateway", "mail-agent", "audit"]`), per-hop
|
|
13
|
+
* timestamps, and hop count. Refuses:
|
|
14
|
+
*
|
|
15
|
+
* - oversized trail (default hop cap = 16; defends infinite
|
|
16
|
+
* recursion across agent delegation)
|
|
17
|
+
* - non-ASCII hop names (operator-greppable in audit logs)
|
|
18
|
+
* - duplicate hop in trail (recursion guard)
|
|
19
|
+
* - missing or non-monotonic enteredAt timestamps
|
|
20
|
+
* - posture set contains non-string entries OR duplicates
|
|
21
|
+
*
|
|
22
|
+
* @card
|
|
23
|
+
* Validates cross-boundary posture envelopes. Hop trail caps,
|
|
24
|
+
* ASCII-only hop names, monotonic timestamps, set-shape
|
|
25
|
+
* posture-regime entries.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
var { defineClass } = require("./framework-error");
|
|
29
|
+
|
|
30
|
+
var GuardPostureChainError = defineClass("GuardPostureChainError", { alwaysPermanent: true });
|
|
31
|
+
|
|
32
|
+
var DEFAULT_PROFILE = "strict";
|
|
33
|
+
|
|
34
|
+
var PROFILES = Object.freeze({
|
|
35
|
+
strict: { maxHops: 16, maxHopBytes: 64, maxRegimes: 8 }, // allow:raw-byte-literal
|
|
36
|
+
balanced: { maxHops: 32, maxHopBytes: 128, maxRegimes: 16 }, // allow:raw-byte-literal
|
|
37
|
+
permissive: { maxHops: 128, maxHopBytes: 256, maxRegimes: 64 }, // allow:raw-byte-literal
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
var COMPLIANCE_POSTURES = Object.freeze({
|
|
41
|
+
hipaa: "strict",
|
|
42
|
+
"pci-dss": "strict",
|
|
43
|
+
gdpr: "strict",
|
|
44
|
+
soc2: "strict",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @primitive b.guardPostureChain.validate
|
|
49
|
+
* @signature b.guardPostureChain.validate(envelope, opts?)
|
|
50
|
+
* @since 0.9.28
|
|
51
|
+
* @status stable
|
|
52
|
+
* @related b.agent.postureChain.create
|
|
53
|
+
*
|
|
54
|
+
* Validate a posture-chain envelope. Returns the envelope on success;
|
|
55
|
+
* throws on refusal.
|
|
56
|
+
*
|
|
57
|
+
* @opts
|
|
58
|
+
* profile: "strict" | "balanced" | "permissive",
|
|
59
|
+
* posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* b.guardPostureChain.validate({
|
|
63
|
+
* postureSet: ["hipaa"],
|
|
64
|
+
* chainTrail: ["api-gateway", "mail-agent"],
|
|
65
|
+
* enteredAt: [1700000000000, 1700000000100],
|
|
66
|
+
* hopCount: 2,
|
|
67
|
+
* });
|
|
68
|
+
*/
|
|
69
|
+
function validate(envelope, opts) {
|
|
70
|
+
opts = opts || {};
|
|
71
|
+
var profile = PROFILES[_resolveProfile(opts)];
|
|
72
|
+
if (!envelope || typeof envelope !== "object") {
|
|
73
|
+
throw new GuardPostureChainError("posture-chain/bad-input",
|
|
74
|
+
"guardPostureChain.validate: envelope required");
|
|
75
|
+
}
|
|
76
|
+
// postureSet — array of distinct ASCII regime names
|
|
77
|
+
if (!Array.isArray(envelope.postureSet)) {
|
|
78
|
+
throw new GuardPostureChainError("posture-chain/bad-posture-set",
|
|
79
|
+
"guardPostureChain.validate: postureSet must be an array");
|
|
80
|
+
}
|
|
81
|
+
if (envelope.postureSet.length > profile.maxRegimes) {
|
|
82
|
+
throw new GuardPostureChainError("posture-chain/too-many-regimes",
|
|
83
|
+
"guardPostureChain.validate: " + envelope.postureSet.length +
|
|
84
|
+
" regimes exceeds maxRegimes=" + profile.maxRegimes);
|
|
85
|
+
}
|
|
86
|
+
var regSeen = Object.create(null);
|
|
87
|
+
for (var r = 0; r < envelope.postureSet.length; r += 1) {
|
|
88
|
+
var regime = envelope.postureSet[r];
|
|
89
|
+
if (typeof regime !== "string" || regime.length === 0) {
|
|
90
|
+
throw new GuardPostureChainError("posture-chain/bad-regime",
|
|
91
|
+
"guardPostureChain.validate: postureSet[" + r + "] must be a non-empty string");
|
|
92
|
+
}
|
|
93
|
+
if (regSeen[regime]) {
|
|
94
|
+
throw new GuardPostureChainError("posture-chain/duplicate-regime",
|
|
95
|
+
"guardPostureChain.validate: duplicate regime '" + regime + "' in postureSet");
|
|
96
|
+
}
|
|
97
|
+
regSeen[regime] = true;
|
|
98
|
+
}
|
|
99
|
+
// chainTrail — bounded hop list
|
|
100
|
+
if (!Array.isArray(envelope.chainTrail)) {
|
|
101
|
+
throw new GuardPostureChainError("posture-chain/bad-trail",
|
|
102
|
+
"guardPostureChain.validate: chainTrail must be an array");
|
|
103
|
+
}
|
|
104
|
+
if (envelope.chainTrail.length > profile.maxHops) {
|
|
105
|
+
throw new GuardPostureChainError("posture-chain/hop-limit-exceeded",
|
|
106
|
+
"guardPostureChain.validate: " + envelope.chainTrail.length +
|
|
107
|
+
" hops exceeds maxHops=" + profile.maxHops);
|
|
108
|
+
}
|
|
109
|
+
var hopSeen = Object.create(null);
|
|
110
|
+
for (var h = 0; h < envelope.chainTrail.length; h += 1) {
|
|
111
|
+
var hop = envelope.chainTrail[h];
|
|
112
|
+
if (typeof hop !== "string" || hop.length === 0) {
|
|
113
|
+
throw new GuardPostureChainError("posture-chain/bad-hop",
|
|
114
|
+
"guardPostureChain.validate: chainTrail[" + h + "] must be a non-empty string");
|
|
115
|
+
}
|
|
116
|
+
if (Buffer.byteLength(hop, "utf8") > profile.maxHopBytes) {
|
|
117
|
+
throw new GuardPostureChainError("posture-chain/hop-name-too-long",
|
|
118
|
+
"guardPostureChain.validate: chainTrail[" + h + "] exceeds maxHopBytes=" + profile.maxHopBytes);
|
|
119
|
+
}
|
|
120
|
+
for (var hi = 0; hi < hop.length; hi += 1) {
|
|
121
|
+
var hc = hop.charCodeAt(hi);
|
|
122
|
+
if (hc > 0x7F) { // allow:raw-byte-literal — ASCII-only
|
|
123
|
+
throw new GuardPostureChainError("posture-chain/non-ascii-hop",
|
|
124
|
+
"guardPostureChain.validate: chainTrail[" + h + "] has non-ASCII codepoint");
|
|
125
|
+
}
|
|
126
|
+
if (hc < 0x20 || hc === 0x7F) { // allow:raw-byte-literal — C0/DEL
|
|
127
|
+
throw new GuardPostureChainError("posture-chain/bad-hop-char",
|
|
128
|
+
"guardPostureChain.validate: chainTrail[" + h + "] has forbidden char 0x" + hc.toString(16));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (hopSeen[hop]) {
|
|
132
|
+
throw new GuardPostureChainError("posture-chain/duplicate-hop",
|
|
133
|
+
"guardPostureChain.validate: duplicate hop '" + hop + "' in chainTrail");
|
|
134
|
+
}
|
|
135
|
+
hopSeen[hop] = true;
|
|
136
|
+
}
|
|
137
|
+
// enteredAt timestamps (optional but if present must match length + be monotonic)
|
|
138
|
+
if (typeof envelope.enteredAt !== "undefined") {
|
|
139
|
+
if (!Array.isArray(envelope.enteredAt)) {
|
|
140
|
+
throw new GuardPostureChainError("posture-chain/bad-entered-at",
|
|
141
|
+
"guardPostureChain.validate: enteredAt must be an array of timestamps");
|
|
142
|
+
}
|
|
143
|
+
if (envelope.enteredAt.length !== envelope.chainTrail.length) {
|
|
144
|
+
throw new GuardPostureChainError("posture-chain/entered-at-length-mismatch",
|
|
145
|
+
"guardPostureChain.validate: enteredAt length must equal chainTrail length");
|
|
146
|
+
}
|
|
147
|
+
var prevT = -Infinity;
|
|
148
|
+
for (var t = 0; t < envelope.enteredAt.length; t += 1) {
|
|
149
|
+
var ts = envelope.enteredAt[t];
|
|
150
|
+
if (typeof ts !== "number" || !isFinite(ts) || ts < 0) {
|
|
151
|
+
throw new GuardPostureChainError("posture-chain/bad-timestamp",
|
|
152
|
+
"guardPostureChain.validate: enteredAt[" + t + "] must be a finite non-negative number");
|
|
153
|
+
}
|
|
154
|
+
if (ts < prevT) {
|
|
155
|
+
throw new GuardPostureChainError("posture-chain/non-monotonic-timestamps",
|
|
156
|
+
"guardPostureChain.validate: enteredAt[" + t + "] < enteredAt[" + (t - 1) + "]");
|
|
157
|
+
}
|
|
158
|
+
prevT = ts;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return envelope;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @primitive b.guardPostureChain.compliancePosture
|
|
166
|
+
* @signature b.guardPostureChain.compliancePosture(posture)
|
|
167
|
+
* @since 0.9.28
|
|
168
|
+
* @status stable
|
|
169
|
+
*
|
|
170
|
+
* Return the effective profile for a given compliance posture name.
|
|
171
|
+
* Returns `null` for unknown posture names so operator typos surface
|
|
172
|
+
* here instead of silently falling through to the default profile.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* b.guardPostureChain.compliancePosture("hipaa"); // → "strict"
|
|
176
|
+
*/
|
|
177
|
+
function compliancePosture(posture) {
|
|
178
|
+
return COMPLIANCE_POSTURES[posture] || null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function _resolveProfile(opts) {
|
|
182
|
+
if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
|
|
183
|
+
return COMPLIANCE_POSTURES[opts.posture];
|
|
184
|
+
}
|
|
185
|
+
var p = opts.profile || DEFAULT_PROFILE;
|
|
186
|
+
if (!PROFILES[p]) {
|
|
187
|
+
throw new GuardPostureChainError("posture-chain/bad-profile",
|
|
188
|
+
"guardPostureChain: unknown profile '" + p + "'");
|
|
189
|
+
}
|
|
190
|
+
return p;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
validate: validate,
|
|
195
|
+
compliancePosture: compliancePosture,
|
|
196
|
+
PROFILES: PROFILES,
|
|
197
|
+
COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
|
|
198
|
+
GuardPostureChainError: GuardPostureChainError,
|
|
199
|
+
NAME: "postureChain",
|
|
200
|
+
KIND: "posture-chain",
|
|
201
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.guardSagaConfig
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Saga Config
|
|
6
|
+
* @order 441
|
|
7
|
+
*
|
|
8
|
+
* @intro
|
|
9
|
+
* Saga-creation config validator. Refuses empty steps array,
|
|
10
|
+
* duplicate step names, non-ASCII saga name, non-async-function
|
|
11
|
+
* run/compensate fields, oversized step count.
|
|
12
|
+
*
|
|
13
|
+
* @card
|
|
14
|
+
* Validates `b.agent.saga.create()` opts. Step-list shape, name
|
|
15
|
+
* uniqueness, run/compensate function checks.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
var { defineClass } = require("./framework-error");
|
|
19
|
+
|
|
20
|
+
var GuardSagaConfigError = defineClass("GuardSagaConfigError", { alwaysPermanent: true });
|
|
21
|
+
|
|
22
|
+
var DEFAULT_PROFILE = "strict";
|
|
23
|
+
|
|
24
|
+
var PROFILES = Object.freeze({
|
|
25
|
+
strict: { maxSteps: 32, maxNameBytes: 64 }, // allow:raw-byte-literal
|
|
26
|
+
balanced: { maxSteps: 128, maxNameBytes: 128 }, // allow:raw-byte-literal
|
|
27
|
+
permissive: { maxSteps: 512, maxNameBytes: 256 }, // allow:raw-byte-literal
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
var COMPLIANCE_POSTURES = Object.freeze({
|
|
31
|
+
hipaa: "strict",
|
|
32
|
+
"pci-dss": "strict",
|
|
33
|
+
gdpr: "strict",
|
|
34
|
+
soc2: "strict",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @primitive b.guardSagaConfig.validate
|
|
39
|
+
* @signature b.guardSagaConfig.validate(config, opts?)
|
|
40
|
+
* @since 0.9.27
|
|
41
|
+
* @status stable
|
|
42
|
+
* @related b.agent.saga.create
|
|
43
|
+
*
|
|
44
|
+
* Validate saga config. Returns config on success; throws on refusal.
|
|
45
|
+
*
|
|
46
|
+
* @opts
|
|
47
|
+
* profile: "strict" | "balanced" | "permissive",
|
|
48
|
+
* posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* b.guardSagaConfig.validate({
|
|
52
|
+
* name: "mail.send",
|
|
53
|
+
* steps: [
|
|
54
|
+
* { name: "sign", run: async () => {}, compensate: async () => {} },
|
|
55
|
+
* ],
|
|
56
|
+
* });
|
|
57
|
+
*/
|
|
58
|
+
function validate(config, opts) {
|
|
59
|
+
opts = opts || {};
|
|
60
|
+
var profile = PROFILES[_resolveProfile(opts)];
|
|
61
|
+
if (!config || typeof config !== "object") {
|
|
62
|
+
throw new GuardSagaConfigError("saga-config/bad-input",
|
|
63
|
+
"guardSagaConfig.validate: config required");
|
|
64
|
+
}
|
|
65
|
+
if (typeof config.name !== "string" || config.name.length === 0) {
|
|
66
|
+
throw new GuardSagaConfigError("saga-config/bad-name",
|
|
67
|
+
"guardSagaConfig.validate: name required");
|
|
68
|
+
}
|
|
69
|
+
if (Buffer.byteLength(config.name, "utf8") > profile.maxNameBytes) {
|
|
70
|
+
throw new GuardSagaConfigError("saga-config/name-too-long",
|
|
71
|
+
"guardSagaConfig.validate: name exceeds maxNameBytes=" + profile.maxNameBytes);
|
|
72
|
+
}
|
|
73
|
+
for (var i = 0; i < config.name.length; i += 1) {
|
|
74
|
+
var c = config.name.charCodeAt(i);
|
|
75
|
+
if (c > 0x7F) { // allow:raw-byte-literal — ASCII-only
|
|
76
|
+
throw new GuardSagaConfigError("saga-config/non-ascii-name",
|
|
77
|
+
"guardSagaConfig.validate: name has non-ASCII codepoint at offset " + i);
|
|
78
|
+
}
|
|
79
|
+
if (c < 0x20 || c === 0x7F) { // allow:raw-byte-literal — C0/DEL
|
|
80
|
+
throw new GuardSagaConfigError("saga-config/bad-name-char",
|
|
81
|
+
"guardSagaConfig.validate: name has forbidden char 0x" + c.toString(16));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!Array.isArray(config.steps) || config.steps.length === 0) {
|
|
85
|
+
throw new GuardSagaConfigError("saga-config/no-steps",
|
|
86
|
+
"guardSagaConfig.validate: steps must be a non-empty array");
|
|
87
|
+
}
|
|
88
|
+
if (config.steps.length > profile.maxSteps) {
|
|
89
|
+
throw new GuardSagaConfigError("saga-config/too-many-steps",
|
|
90
|
+
"guardSagaConfig.validate: " + config.steps.length + " steps exceeds " + profile.maxSteps);
|
|
91
|
+
}
|
|
92
|
+
var seenNames = Object.create(null);
|
|
93
|
+
for (var s = 0; s < config.steps.length; s += 1) {
|
|
94
|
+
var step = config.steps[s];
|
|
95
|
+
if (!step || typeof step !== "object") {
|
|
96
|
+
throw new GuardSagaConfigError("saga-config/bad-step",
|
|
97
|
+
"guardSagaConfig.validate: steps[" + s + "] must be an object");
|
|
98
|
+
}
|
|
99
|
+
if (typeof step.name !== "string" || step.name.length === 0) {
|
|
100
|
+
throw new GuardSagaConfigError("saga-config/bad-step-name",
|
|
101
|
+
"guardSagaConfig.validate: steps[" + s + "].name required");
|
|
102
|
+
}
|
|
103
|
+
if (seenNames[step.name]) {
|
|
104
|
+
throw new GuardSagaConfigError("saga-config/duplicate-step-name",
|
|
105
|
+
"guardSagaConfig.validate: duplicate step name '" + step.name + "'");
|
|
106
|
+
}
|
|
107
|
+
seenNames[step.name] = true;
|
|
108
|
+
if (typeof step.run !== "function") {
|
|
109
|
+
throw new GuardSagaConfigError("saga-config/bad-step-run",
|
|
110
|
+
"guardSagaConfig.validate: steps[" + s + "].run must be a function");
|
|
111
|
+
}
|
|
112
|
+
if (typeof step.compensate !== "undefined" && typeof step.compensate !== "function") {
|
|
113
|
+
throw new GuardSagaConfigError("saga-config/bad-step-compensate",
|
|
114
|
+
"guardSagaConfig.validate: steps[" + s + "].compensate must be a function (or omitted)");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return config;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @primitive b.guardSagaConfig.compliancePosture
|
|
122
|
+
* @signature b.guardSagaConfig.compliancePosture(posture)
|
|
123
|
+
* @since 0.9.27
|
|
124
|
+
* @status stable
|
|
125
|
+
*
|
|
126
|
+
* Return the effective profile for a given compliance posture name.
|
|
127
|
+
* Returns `null` for unknown posture names so operator typos surface
|
|
128
|
+
* here instead of silently falling through to the default profile.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* b.guardSagaConfig.compliancePosture("hipaa"); // → "strict"
|
|
132
|
+
*/
|
|
133
|
+
function compliancePosture(posture) {
|
|
134
|
+
return COMPLIANCE_POSTURES[posture] || null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function _resolveProfile(opts) {
|
|
138
|
+
if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
|
|
139
|
+
return COMPLIANCE_POSTURES[opts.posture];
|
|
140
|
+
}
|
|
141
|
+
var p = opts.profile || DEFAULT_PROFILE;
|
|
142
|
+
if (!PROFILES[p]) {
|
|
143
|
+
throw new GuardSagaConfigError("saga-config/bad-profile",
|
|
144
|
+
"guardSagaConfig: unknown profile '" + p + "'");
|
|
145
|
+
}
|
|
146
|
+
return p;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
validate: validate,
|
|
151
|
+
compliancePosture: compliancePosture,
|
|
152
|
+
PROFILES: PROFILES,
|
|
153
|
+
COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
|
|
154
|
+
GuardSagaConfigError: GuardSagaConfigError,
|
|
155
|
+
NAME: "sagaConfig",
|
|
156
|
+
KIND: "saga-config",
|
|
157
|
+
};
|