@blamejs/core 0.14.19 → 0.14.21
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 +4 -0
- package/README.md +1 -1
- package/lib/auth/oauth.js +736 -1
- package/lib/auth/oid4vci.js +124 -5
- package/lib/auth/oid4vp.js +14 -4
- package/lib/auth/sd-jwt-vc-holder.js +46 -1
- package/lib/break-glass.js +1 -2
- package/lib/config.js +28 -31
- package/lib/crypto-field.js +274 -17
- package/lib/dora.js +8 -5
- package/lib/dsr.js +2 -2
- package/lib/flag-evaluation-context.js +7 -0
- package/lib/guard-html-wcag-aria.js +4 -2
- package/lib/guard-html-wcag-forms.js +4 -2
- package/lib/guard-html-wcag-tables.js +4 -2
- package/lib/guard-html-wcag-tagwalk.js +20 -0
- package/lib/guard-html-wcag.js +1 -1
- package/lib/honeytoken.js +27 -20
- package/lib/mail-auth.js +333 -0
- package/lib/mail-deploy.js +1 -1
- package/lib/mail-send-deliver.js +13 -4
- package/lib/middleware/api-encrypt.js +140 -13
- package/lib/middleware/asyncapi-serve.js +3 -0
- package/lib/middleware/csp-report.js +13 -9
- package/lib/middleware/fetch-metadata.js +115 -14
- package/lib/middleware/openapi-serve.js +3 -0
- package/lib/middleware/scim-server.js +297 -19
- package/lib/middleware/security-headers.js +47 -0
- package/lib/middleware/security-txt.js +1 -2
- package/lib/middleware/trace-log-correlation.js +1 -2
- package/lib/network-smtp-policy.js +4 -4
- package/lib/object-store/sigv4-bucket-ops.js +11 -2
- package/lib/observability-tracer.js +1 -1
- package/lib/observability.js +39 -1
- package/lib/problem-details.js +56 -11
- package/lib/pubsub-cluster.js +16 -3
- package/lib/queue-sqs.js +20 -2
- package/lib/redis-client.js +32 -4
- package/lib/safe-redirect.js +16 -2
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/problem-details.js
CHANGED
|
@@ -132,19 +132,28 @@ function getBase() {
|
|
|
132
132
|
* - `status` (recommended) must be an integer 100..599.
|
|
133
133
|
* - `detail` (optional) must be a string when given.
|
|
134
134
|
* - `instance` (optional) must be a URI reference string when given.
|
|
135
|
-
* - Extensions: every additional key whose name is NOT in
|
|
135
|
+
* - Extensions: every additional top-level key whose name is NOT in
|
|
136
136
|
* `RESERVED_FIELDS` is preserved at the top level. Reserved-name
|
|
137
|
-
* collisions throw `problem-details/reserved-extension
|
|
137
|
+
* collisions throw `problem-details/reserved-extension`;
|
|
138
|
+
* prototype-pollution-shaped top-level keys throw the same.
|
|
139
|
+
* - `extensions`: a plain object whose keys are spread as top-level
|
|
140
|
+
* sibling members (RFC 9457 §3.2) — the literal `extensions`
|
|
141
|
+
* member is never emitted. Keys colliding with `RESERVED_FIELDS`
|
|
142
|
+
* are ignored (reserved fields can't be overridden by an
|
|
143
|
+
* extension); prototype-pollution-shaped keys are dropped
|
|
144
|
+
* silently. When the same name appears both as a direct top-level
|
|
145
|
+
* key and inside `extensions`, the direct top-level key wins.
|
|
138
146
|
*
|
|
139
147
|
* Returns a frozen plain object suitable for `JSON.stringify`.
|
|
140
148
|
*
|
|
141
149
|
* @opts
|
|
142
|
-
* type:
|
|
143
|
-
* title:
|
|
144
|
-
* status:
|
|
145
|
-
* detail:
|
|
146
|
-
* instance:
|
|
147
|
-
*
|
|
150
|
+
* type: string, // problem-type URI reference (default "about:blank")
|
|
151
|
+
* title: string, // short summary
|
|
152
|
+
* status: number, // integer 100..599
|
|
153
|
+
* detail: string, // human-readable explanation
|
|
154
|
+
* instance: string, // URI reference for this specific occurrence
|
|
155
|
+
* extensions: object, // keys spread as top-level siblings (§3.2); direct top-level key wins on collision
|
|
156
|
+
* ...extensions // additional top-level keys preserved as-is
|
|
148
157
|
*
|
|
149
158
|
* @example
|
|
150
159
|
* var p = b.problemDetails.create({
|
|
@@ -213,15 +222,44 @@ function create(opts) {
|
|
|
213
222
|
|
|
214
223
|
// Extensions — every additional key. §3.2 endorses sibling
|
|
215
224
|
// extensions as long as their names don't collide with reserved.
|
|
225
|
+
// The `extensions` key itself is NOT emitted as a literal nested
|
|
226
|
+
// member: a plain-object value is spread so each of its keys lands
|
|
227
|
+
// as a top-level sibling, subject to the same reserved / poisoned
|
|
228
|
+
// guards as direct top-level keys. A direct top-level extension key
|
|
229
|
+
// wins over the same name nested under `extensions`.
|
|
216
230
|
var keys = Object.keys(opts);
|
|
217
|
-
|
|
218
|
-
|
|
231
|
+
var directKeys = Object.create(null);
|
|
232
|
+
var i, k;
|
|
233
|
+
for (i = 0; i < keys.length; i += 1) {
|
|
234
|
+
k = keys[i];
|
|
235
|
+
if (k === "extensions") continue;
|
|
219
236
|
if (RESERVED_FIELDS.indexOf(k) !== -1) continue;
|
|
220
237
|
if (POISONED_KEYS.indexOf(k) !== -1) {
|
|
221
238
|
throw new ProblemDetailsError("problem-details/reserved-extension",
|
|
222
239
|
"create: extension key '" + k + "' refused (prototype-pollution shape)", true);
|
|
223
240
|
}
|
|
224
241
|
out[k] = opts[k];
|
|
242
|
+
directKeys[k] = true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Spread `extensions` (RFC 9457 §3.2 sibling members). Reserved
|
|
246
|
+
// names can't be overridden by an extension key; poisoned keys are
|
|
247
|
+
// dropped silently (an inbound extension map is a less-trusted shape
|
|
248
|
+
// than a hand-authored top-level key — a direct poisoned key still
|
|
249
|
+
// throws). A direct top-level key already present wins.
|
|
250
|
+
if (opts.extensions !== undefined && opts.extensions !== null) {
|
|
251
|
+
if (typeof opts.extensions !== "object" || Array.isArray(opts.extensions)) {
|
|
252
|
+
throw new ProblemDetailsError("problem-details/bad-extensions",
|
|
253
|
+
"create: extensions must be a plain object when provided", true);
|
|
254
|
+
}
|
|
255
|
+
var extKeys = Object.keys(opts.extensions);
|
|
256
|
+
for (i = 0; i < extKeys.length; i += 1) {
|
|
257
|
+
k = extKeys[i];
|
|
258
|
+
if (RESERVED_FIELDS.indexOf(k) !== -1) continue;
|
|
259
|
+
if (POISONED_KEYS.indexOf(k) !== -1) continue;
|
|
260
|
+
if (directKeys[k]) continue;
|
|
261
|
+
out[k] = opts.extensions[k];
|
|
262
|
+
}
|
|
225
263
|
}
|
|
226
264
|
|
|
227
265
|
return Object.freeze(out);
|
|
@@ -386,13 +424,20 @@ function respond(res, problem, req) {
|
|
|
386
424
|
* `Cache-Control: no-store` are written; status code defaults to
|
|
387
425
|
* 500 when omitted.
|
|
388
426
|
*
|
|
427
|
+
* `extensions` keys are spread as top-level sibling members (RFC 9457
|
|
428
|
+
* §3.2) via `create` — the literal `extensions` member is never
|
|
429
|
+
* emitted. Keys colliding with the reserved `type` / `title` /
|
|
430
|
+
* `status` / `detail` / `instance` are ignored; prototype-pollution-
|
|
431
|
+
* shaped keys are dropped. A direct top-level key wins over the same
|
|
432
|
+
* name nested under `extensions`.
|
|
433
|
+
*
|
|
389
434
|
* @opts
|
|
390
435
|
* status: number, // HTTP status code (100..599); default 500
|
|
391
436
|
* title: string, // operator-supplied short title
|
|
392
437
|
* detail: string, // operator-supplied human-readable explanation
|
|
393
438
|
* type: string, // problem-type URI (defaults to "about:blank")
|
|
394
439
|
* instance: string, // optional per-occurrence URI
|
|
395
|
-
* extensions: object, //
|
|
440
|
+
* extensions: object, // keys spread as top-level siblings (§3.2); direct top-level key wins on collision
|
|
396
441
|
*
|
|
397
442
|
* @example
|
|
398
443
|
* // Migrating from inline JSON-error shape:
|
package/lib/pubsub-cluster.js
CHANGED
|
@@ -24,18 +24,31 @@
|
|
|
24
24
|
var clusterStorage = require("./cluster-storage");
|
|
25
25
|
var C = require("./constants");
|
|
26
26
|
var lazyRequire = require("./lazy-require");
|
|
27
|
+
var validateOpts = require("./validate-opts");
|
|
28
|
+
var { defineClass } = require("./framework-error");
|
|
27
29
|
|
|
28
30
|
var logger = lazyRequire(function () { return require("./log").boot("pubsub-cluster"); });
|
|
29
31
|
|
|
32
|
+
var PubsubError = defineClass("PubsubError");
|
|
33
|
+
|
|
30
34
|
var DEFAULT_POLL_INTERVAL_MS = 100;
|
|
31
35
|
var DEFAULT_RETENTION_MS = C.TIME.minutes(1);
|
|
32
36
|
var DEFAULT_PRUNE_EVERY_MS = C.TIME.minutes(5);
|
|
33
37
|
|
|
34
38
|
function create(opts) {
|
|
35
39
|
var clusterInstance = opts.cluster;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
// Config-time: a typo (NaN-coercing string / negative / fractional) must
|
|
41
|
+
// surface at create, not silently fall back to the default and ship a
|
|
42
|
+
// mis-tuned poll loop. THROW on present-but-bad; absent keeps the default.
|
|
43
|
+
validateOpts.optionalPositiveInt(opts.pollIntervalMs,
|
|
44
|
+
"pubsub: pollIntervalMs", PubsubError, "BAD_OPT");
|
|
45
|
+
validateOpts.optionalPositiveInt(opts.retentionMs,
|
|
46
|
+
"pubsub: retentionMs", PubsubError, "BAD_OPT");
|
|
47
|
+
validateOpts.optionalPositiveInt(opts.pruneEveryMs,
|
|
48
|
+
"pubsub: pruneEveryMs", PubsubError, "BAD_OPT");
|
|
49
|
+
var pollIntervalMs = opts.pollIntervalMs !== undefined ? opts.pollIntervalMs : DEFAULT_POLL_INTERVAL_MS;
|
|
50
|
+
var retentionMs = opts.retentionMs !== undefined ? opts.retentionMs : DEFAULT_RETENTION_MS;
|
|
51
|
+
var pruneEveryMs = opts.pruneEveryMs !== undefined ? opts.pruneEveryMs : DEFAULT_PRUNE_EVERY_MS;
|
|
39
52
|
|
|
40
53
|
var lastSeenId = 0;
|
|
41
54
|
var primed = false;
|
package/lib/queue-sqs.js
CHANGED
|
@@ -50,6 +50,7 @@ var httpClient = require("./http-client");
|
|
|
50
50
|
var cryptoField = require("./crypto-field");
|
|
51
51
|
var safeJson = require("./safe-json");
|
|
52
52
|
var safeUrl = require("./safe-url");
|
|
53
|
+
var validateOpts = require("./validate-opts");
|
|
53
54
|
var { generateToken } = require("./crypto");
|
|
54
55
|
var { QueueError } = require("./framework-error");
|
|
55
56
|
|
|
@@ -102,8 +103,25 @@ function create(opts) {
|
|
|
102
103
|
var accountId = opts.accountId ? String(opts.accountId) : null;
|
|
103
104
|
var timeoutMs = opts.timeoutMs;
|
|
104
105
|
var allowInternal = opts.allowInternal != null ? opts.allowInternal : null;
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
// Config-time: a typo (NaN-coercing string / negative / fractional)
|
|
107
|
+
// must surface at create, not silently fall back to the default and ship
|
|
108
|
+
// a mis-tuned lease loop. THROW on present-but-bad; absent keeps default.
|
|
109
|
+
validateOpts.optionalPositiveInt(opts.visibilityTimeoutSec,
|
|
110
|
+
"queue-sqs: visibilityTimeoutSec", QueueError, "INVALID_CONFIG");
|
|
111
|
+
// waitTimeSec=0 is the valid SQS short-poll sentinel (the default), so a
|
|
112
|
+
// positive-int check would wrongly reject it — allow non-negative integers.
|
|
113
|
+
if (opts.waitTimeSec !== undefined &&
|
|
114
|
+
(typeof opts.waitTimeSec !== "number" || !isFinite(opts.waitTimeSec) ||
|
|
115
|
+
opts.waitTimeSec < 0 || Math.floor(opts.waitTimeSec) !== opts.waitTimeSec)) {
|
|
116
|
+
throw _err("INVALID_CONFIG",
|
|
117
|
+
"queue-sqs: waitTimeSec must be a non-negative integer (0 = short-poll), got " +
|
|
118
|
+
(typeof opts.waitTimeSec === "number" ? String(opts.waitTimeSec) : typeof opts.waitTimeSec),
|
|
119
|
+
true);
|
|
120
|
+
}
|
|
121
|
+
var visibilityTimeoutSec = opts.visibilityTimeoutSec !== undefined
|
|
122
|
+
? opts.visibilityTimeoutSec : DEFAULT_VISIBILITY_TIMEOUT_SEC;
|
|
123
|
+
var waitTimeSec = opts.waitTimeSec !== undefined
|
|
124
|
+
? opts.waitTimeSec : DEFAULT_WAIT_TIME_SEC;
|
|
107
125
|
|
|
108
126
|
var queueUrlResolver = typeof opts.queueUrlByName === "function"
|
|
109
127
|
? opts.queueUrlByName
|
package/lib/redis-client.js
CHANGED
|
@@ -169,11 +169,36 @@ function create(opts) {
|
|
|
169
169
|
var useTls = opts.tls !== undefined ? !!opts.tls : parsed.tls;
|
|
170
170
|
var password = opts.password !== undefined ? opts.password : parsed.password;
|
|
171
171
|
var username = opts.username !== undefined ? opts.username : parsed.username;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
// Config-time entry-point opts: a bad type must fail at create() rather
|
|
173
|
+
// than coerce-or-default silently. connectTimeoutMs:"abc" → NaN would
|
|
174
|
+
// otherwise fall through to the default; a negative timeout would sail
|
|
175
|
+
// into setTimeout; maxReconnectAttempts:"abc" → NaN would make the
|
|
176
|
+
// `>= 0` reconnect-cap check below false and SILENTLY disable the bound
|
|
177
|
+
// (unbounded reconnects). db and maxReconnectAttempts must allow 0
|
|
178
|
+
// (db 0 = no SELECT; maxReconnectAttempts 0 = give up immediately).
|
|
179
|
+
if (opts.db !== undefined &&
|
|
180
|
+
(typeof opts.db !== "number" || !Number.isInteger(opts.db) || opts.db < 0)) {
|
|
181
|
+
throw _err("BAD_OPTS",
|
|
182
|
+
"redis.create: opts.db must be a non-negative integer, got " +
|
|
183
|
+
(typeof opts.db === "number" ? String(opts.db) : typeof opts.db));
|
|
184
|
+
}
|
|
185
|
+
if (opts.maxReconnectAttempts !== undefined &&
|
|
186
|
+
(typeof opts.maxReconnectAttempts !== "number" ||
|
|
187
|
+
!Number.isInteger(opts.maxReconnectAttempts) || opts.maxReconnectAttempts < 0)) {
|
|
188
|
+
throw _err("BAD_OPTS",
|
|
189
|
+
"redis.create: opts.maxReconnectAttempts must be a non-negative integer, got " +
|
|
190
|
+
(typeof opts.maxReconnectAttempts === "number"
|
|
191
|
+
? String(opts.maxReconnectAttempts) : typeof opts.maxReconnectAttempts));
|
|
192
|
+
}
|
|
193
|
+
validateOpts.optionalPositiveInt(opts.connectTimeoutMs,
|
|
194
|
+
"redis.create: opts.connectTimeoutMs", RedisError, "BAD_OPTS");
|
|
195
|
+
validateOpts.optionalPositiveInt(opts.commandTimeoutMs,
|
|
196
|
+
"redis.create: opts.commandTimeoutMs", RedisError, "BAD_OPTS");
|
|
197
|
+
var db = opts.db !== undefined ? opts.db : parsed.db;
|
|
198
|
+
var connectTimeoutMs = opts.connectTimeoutMs !== undefined ? opts.connectTimeoutMs : 5000;
|
|
199
|
+
var commandTimeoutMs = opts.commandTimeoutMs !== undefined ? opts.commandTimeoutMs : 10000;
|
|
175
200
|
var maxReconnectAttempts = opts.maxReconnectAttempts === undefined ? 10
|
|
176
|
-
:
|
|
201
|
+
: opts.maxReconnectAttempts;
|
|
177
202
|
// TLS verification controls. Operators using rediss:// against private
|
|
178
203
|
// CAs (managed Redis services, on-prem clusters with internal PKI)
|
|
179
204
|
// pin the trust roots via opts.ca; rejectUnauthorized stays on by
|
|
@@ -470,6 +495,9 @@ function create(opts) {
|
|
|
470
495
|
pending: pending.length, backlog: backlog.length,
|
|
471
496
|
reconnect: reconnectAttempt,
|
|
472
497
|
host: host, port: port, db: db, tls: useTls,
|
|
498
|
+
connectTimeoutMs: connectTimeoutMs,
|
|
499
|
+
commandTimeoutMs: commandTimeoutMs,
|
|
500
|
+
maxReconnectAttempts: maxReconnectAttempts,
|
|
473
501
|
};
|
|
474
502
|
},
|
|
475
503
|
};
|
package/lib/safe-redirect.js
CHANGED
|
@@ -76,8 +76,21 @@ function resolve(rawTarget, opts) {
|
|
|
76
76
|
// Full URL — parse and check against allowlist.
|
|
77
77
|
var allowedOrigins = Array.isArray(opts.allowedOrigins) ? opts.allowedOrigins : null;
|
|
78
78
|
var allowedHosts = Array.isArray(opts.allowedHosts) ? opts.allowedHosts : null;
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
|
|
80
|
+
// The application's own origin (opts.base) is same-origin by
|
|
81
|
+
// definition, so a full URL pointing at it is safe even when the
|
|
82
|
+
// operator supplied no explicit allowedOrigins / allowedHosts. Derive
|
|
83
|
+
// the origin from base and treat it as an implicitly-allowed origin.
|
|
84
|
+
var baseOrigin = null;
|
|
85
|
+
if (typeof opts.base === "string" && opts.base.length > 0) {
|
|
86
|
+
try {
|
|
87
|
+
baseOrigin = safeUrl.parse(opts.base, { allowedProtocols: safeUrl.ALLOW_HTTP_TLS }).origin;
|
|
88
|
+
} catch (_e) { baseOrigin = null; }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!allowedOrigins && !allowedHosts && baseOrigin === null) {
|
|
92
|
+
// Operator gave no allowlist and no usable base — refuse all full
|
|
93
|
+
// URLs (the safe default).
|
|
81
94
|
return fallback;
|
|
82
95
|
}
|
|
83
96
|
|
|
@@ -85,6 +98,7 @@ function resolve(rawTarget, opts) {
|
|
|
85
98
|
try { parsed = safeUrl.parse(rawTarget, { allowedProtocols: safeUrl.ALLOW_HTTP_TLS }); }
|
|
86
99
|
catch (_e) { return fallback; }
|
|
87
100
|
|
|
101
|
+
if (baseOrigin !== null && parsed.origin === baseOrigin) return rawTarget;
|
|
88
102
|
if (allowedOrigins) {
|
|
89
103
|
for (var i = 0; i < allowedOrigins.length; i += 1) {
|
|
90
104
|
if (parsed.origin === allowedOrigins[i]) return rawTarget;
|
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:37cb0e0e-7cba-440b-89c3-febfeb9f7eef",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-06-
|
|
8
|
+
"timestamp": "2026-06-05T04:48:42.555Z",
|
|
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.21",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.14.
|
|
25
|
+
"version": "0.14.21",
|
|
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.21",
|
|
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.21",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|