@blamejs/core 0.15.67 → 0.16.0
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 +6 -0
- package/README.md +2 -1
- package/lib/archive-tar.js +7 -0
- package/lib/archive.js +4 -0
- package/lib/auth/status-list.js +5 -0
- package/lib/bounded-map.js +1 -1
- package/lib/csv.js +12 -1
- package/lib/eat.js +2 -1
- package/lib/mail-arc-sign.js +15 -12
- package/lib/mail-arf.js +2 -0
- package/lib/mail-bimi.js +2 -1
- package/lib/mail-bounce.js +21 -6
- package/lib/mail-mdn.js +15 -0
- package/lib/mail-send-deliver.js +17 -7
- package/lib/mail-srs.js +6 -0
- package/lib/middleware/idempotency-key.js +28 -2
- package/lib/mime-parse.js +3 -1
- package/lib/network-dns-resolver.js +53 -18
- package/lib/observability-otlp-exporter.js +2 -2
- package/lib/safe-buffer.js +64 -0
- package/lib/safe-icap.js +5 -0
- package/lib/session.js +47 -32
- package/lib/vc.js +29 -0
- package/lib/vendor/MANIFEST.json +10 -10
- package/lib/vendor/public-suffix-list.dat +2 -4
- package/lib/vendor/public-suffix-list.data.js +745 -745
- package/lib/xml-c14n.js +8 -7
- package/package.json +3 -3
- package/sbom.cdx.json +7 -7
|
@@ -232,6 +232,7 @@ function create(opts) {
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
var cache = new Map(); // key → { response, parsed, ttl, expiresAt, staleUntil }
|
|
235
|
+
var inflight = new Map(); // key → Promise (single-flight: coalesce concurrent misses)
|
|
235
236
|
|
|
236
237
|
// CWE-400/770. LRU eviction on insert when the cache is at
|
|
237
238
|
// capacity. v8 Map preserves insertion order; oldest key is the
|
|
@@ -291,11 +292,53 @@ function create(opts) {
|
|
|
291
292
|
return _result(hit.parsed, hit.ttl, true, false, hit.validated);
|
|
292
293
|
}
|
|
293
294
|
|
|
294
|
-
// Cache miss / expired
|
|
295
|
-
//
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
// own
|
|
295
|
+
// Cache miss / expired. Single-flight: coalesce concurrent misses for the
|
|
296
|
+
// same key so N simultaneous callers trigger ONE upstream lookup instead
|
|
297
|
+
// of a thundering herd (cache stampede). The winner fills the cache; the
|
|
298
|
+
// others await it and re-derive their own verdict from the shared entry
|
|
299
|
+
// (applying their own validate gate — a non-validating fill mustn't hand a
|
|
300
|
+
// validating caller an AD=0 pass).
|
|
301
|
+
if (inflight.has(key)) {
|
|
302
|
+
try { await inflight.get(key).promise; } catch (_e) { /* winner failed — re-check cache, else fetch ourselves below */ }
|
|
303
|
+
var shared = cache.get(key);
|
|
304
|
+
if (shared && shared.expiresAt > Date.now()) {
|
|
305
|
+
if (validate && !shared.validated) {
|
|
306
|
+
throw new ResolverError("resolver/validate-failed",
|
|
307
|
+
"query: validate: true but cached response was AD=0 for " + name + "/" + qtype);
|
|
308
|
+
}
|
|
309
|
+
_touch(key, shared);
|
|
310
|
+
return _result(shared.parsed, shared.ttl, true, false, shared.validated);
|
|
311
|
+
}
|
|
312
|
+
// No fresh shared entry (winner errored / served stale) — fetch ourselves.
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Wrap the fill promise in a plain entry object so the finally-guard below
|
|
316
|
+
// compares object identity, not promise identity: if this fill errors and a
|
|
317
|
+
// follower re-registers its own fresh entry for the same key before this
|
|
318
|
+
// finally runs, the winner deletes only its OWN slot, never the follower's.
|
|
319
|
+
var entry = { promise: _fetchFill(name, qtype, key, hit, now) };
|
|
320
|
+
inflight.set(key, entry);
|
|
321
|
+
var filled;
|
|
322
|
+
try { filled = await entry.promise; }
|
|
323
|
+
finally { if (inflight.get(key) === entry) inflight.delete(key); }
|
|
324
|
+
if (filled.stale) return filled.result;
|
|
325
|
+
if (validate && !filled.validated) {
|
|
326
|
+
throw new ResolverError("resolver/validate-failed",
|
|
327
|
+
"query: validate: true but upstream returned AD=0 for " + name + "/" + qtype);
|
|
328
|
+
}
|
|
329
|
+
return _result(filled.parsed, filled.ttl, false, false, filled.validated);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// The single upstream fetch + parse + cache fill, shared by all coalesced
|
|
333
|
+
// callers. Returns { stale:false, parsed, ttl, validated } on a fresh fill,
|
|
334
|
+
// { stale:true, result } when serving stale on an upstream/parse failure, or
|
|
335
|
+
// throws on an unrecoverable error. Does NOT apply the per-caller validate
|
|
336
|
+
// gate — that stays with the caller so callers with different validate flags
|
|
337
|
+
// share one lookup.
|
|
338
|
+
async function _fetchFill(name, qtype, key, hit, now) {
|
|
339
|
+
// withTimeout rejects the await so a non-responsive / stalling endpoint
|
|
340
|
+
// can't hold the request pending forever; the default _wireLookup also
|
|
341
|
+
// tears the socket down on its own req.setTimeout so the fd is released.
|
|
299
342
|
var wireResponse;
|
|
300
343
|
try {
|
|
301
344
|
wireResponse = await safeAsync.withTimeout(
|
|
@@ -307,7 +350,7 @@ function create(opts) {
|
|
|
307
350
|
// Upstream failure — serve stale if we have it within window.
|
|
308
351
|
if (hit && serveStale > 0 && hit.staleUntil > now) {
|
|
309
352
|
_safeEmit("served_stale", { name: name, qtype: qtype, reason: "upstream-failure" });
|
|
310
|
-
return _result(hit.parsed, hit.ttl, true, true, hit.validated);
|
|
353
|
+
return { stale: true, result: _result(hit.parsed, hit.ttl, true, true, hit.validated) };
|
|
311
354
|
}
|
|
312
355
|
throw new ResolverError("resolver/upstream-failed",
|
|
313
356
|
"query: upstream lookup failed for " + name + "/" + qtype + ": " + (e && e.message || String(e)));
|
|
@@ -320,7 +363,7 @@ function create(opts) {
|
|
|
320
363
|
// Malformed upstream response — serve stale if within window.
|
|
321
364
|
if (hit && serveStale > 0 && hit.staleUntil > now) {
|
|
322
365
|
_safeEmit("served_stale", { name: name, qtype: qtype, reason: "parse-failed" });
|
|
323
|
-
return _result(hit.parsed, hit.ttl, true, true, hit.validated);
|
|
366
|
+
return { stale: true, result: _result(hit.parsed, hit.ttl, true, true, hit.validated) };
|
|
324
367
|
}
|
|
325
368
|
throw e;
|
|
326
369
|
}
|
|
@@ -332,18 +375,10 @@ function create(opts) {
|
|
|
332
375
|
}
|
|
333
376
|
|
|
334
377
|
// AD bit (RFC 4035 §3.2.3) — set by upstream after chain validation.
|
|
335
|
-
// Bit 5 of byte 3 of header; parsed.flags is the full 16-bit flags
|
|
336
|
-
// field at offset 2..3. AD is bit 5 within byte 3 = bit 5 of the
|
|
337
|
-
// low byte of the 16-bit flags value.
|
|
338
378
|
var ad = (parsed.flags & 0x0020) !== 0; // RFC 4035 §3.2.3 AD-bit mask within DNS header flags
|
|
339
|
-
if (validate && !ad) {
|
|
340
|
-
throw new ResolverError("resolver/validate-failed",
|
|
341
|
-
"query: validate: true but upstream returned AD=0 for " + name + "/" + qtype);
|
|
342
|
-
}
|
|
343
379
|
|
|
344
|
-
// Compute effective TTL — min across answer RRs (RFC 2181 §5.2
|
|
345
|
-
//
|
|
346
|
-
// to [minTtlMs, maxTtlMs] to bound any single RR's TTL from
|
|
380
|
+
// Compute effective TTL — min across answer RRs (RFC 2181 §5.2), then
|
|
381
|
+
// clamped to [minTtlMs, maxTtlMs] to bound any single RR's TTL from
|
|
347
382
|
// pinning a poisoned entry past operator policy.
|
|
348
383
|
var rrTtl = _minTtl(parsed.answer);
|
|
349
384
|
var ttlMs = Math.max(minTtlMs, Math.min(maxTtlMs, rrTtl * C.TIME.seconds(1)));
|
|
@@ -358,7 +393,7 @@ function create(opts) {
|
|
|
358
393
|
validated: ad,
|
|
359
394
|
});
|
|
360
395
|
_safeEmit("cached", { name: name, qtype: qtype, ttlMs: ttlMs, adBit: ad });
|
|
361
|
-
return
|
|
396
|
+
return { stale: false, parsed: parsed, ttl: ttlMs, validated: ad };
|
|
362
397
|
}
|
|
363
398
|
|
|
364
399
|
function _result(parsed, ttlMs, fromCache, stale, validated) {
|
|
@@ -161,7 +161,7 @@ function _spanToOtlp(span) {
|
|
|
161
161
|
droppedAttributesCount: span.droppedAttributesCount || 0,
|
|
162
162
|
events: (span.events || []).map(function (e) {
|
|
163
163
|
return {
|
|
164
|
-
name: e.name,
|
|
164
|
+
name: _redactWireString("otel.event.name", e.name),
|
|
165
165
|
timeUnixNano: e.timeUnixNano,
|
|
166
166
|
attributes: _attrToOtlp(e.attributes),
|
|
167
167
|
droppedAttributesCount: 0,
|
|
@@ -357,7 +357,7 @@ function _spanToProto(span) {
|
|
|
357
357
|
var eventsRepeated = pb.repeatedMessage(11, span.events || [], function (e) {
|
|
358
358
|
return Buffer.concat([
|
|
359
359
|
pb.fixed64(1, e.timeUnixNano || 0),
|
|
360
|
-
pb.string(2, e.name || ""),
|
|
360
|
+
pb.string(2, _redactWireString("otel.event.name", e.name || "")),
|
|
361
361
|
pb.repeatedMessage(3, _attrsToProto(e.attributes), _keyValueToProto),
|
|
362
362
|
pb.uint32(4, 0),
|
|
363
363
|
]);
|
package/lib/safe-buffer.js
CHANGED
|
@@ -798,6 +798,68 @@ function stripCrlf(s, replacement) {
|
|
|
798
798
|
return s.replace(CRLF_RE_GLOBAL, replacement === undefined ? "" : replacement);
|
|
799
799
|
}
|
|
800
800
|
|
|
801
|
+
/**
|
|
802
|
+
* @primitive b.safeBuffer.foldHeaderText
|
|
803
|
+
* @signature b.safeBuffer.foldHeaderText(value, replacement?)
|
|
804
|
+
* @since 0.15.68
|
|
805
|
+
* @status stable
|
|
806
|
+
* @related b.safeBuffer.assertHeaderSafe, b.safeBuffer.stripCrlf
|
|
807
|
+
*
|
|
808
|
+
* Neutralize free-text bound for a CRLF-delimited protocol line: replace
|
|
809
|
+
* every CR and LF with <code>replacement</code> (default a single space) so
|
|
810
|
+
* the text folds onto one line, AND remove every NUL byte. Use this for a
|
|
811
|
+
* value that may LEGITIMATELY wrap — a multi-line SMTP 5xx reply folded into
|
|
812
|
+
* one diagnostic line — where <code>assertHeaderSafe</code> (reject) would
|
|
813
|
+
* be too strict. Unlike bare <code>stripCrlf</code>, this also strips NUL,
|
|
814
|
+
* which is never valid in an RFC 5322 header value and which downstream
|
|
815
|
+
* SMTP / mail parsers treat specially. Non-string input passes through
|
|
816
|
+
* unchanged.
|
|
817
|
+
*
|
|
818
|
+
* @example
|
|
819
|
+
* b.safeBuffer.foldHeaderText("550 mailbox full\r\nX-Injected: evil");
|
|
820
|
+
* // → "550 mailbox full X-Injected: evil"
|
|
821
|
+
*/
|
|
822
|
+
function foldHeaderText(value, replacement) {
|
|
823
|
+
if (typeof value !== "string") return value;
|
|
824
|
+
var rep = replacement === undefined ? " " : replacement;
|
|
825
|
+
return value.replace(CRLF_RE_GLOBAL, rep).split("\u0000").join("");
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* @primitive b.safeBuffer.assertHeaderSafe
|
|
830
|
+
* @signature b.safeBuffer.assertHeaderSafe(value, label, ErrorClass, code)
|
|
831
|
+
* @since 0.15.68
|
|
832
|
+
* @status stable
|
|
833
|
+
* @related b.safeBuffer.hasCrlf, b.safeBuffer.stripCrlf
|
|
834
|
+
*
|
|
835
|
+
* Throw when a string bound for a CRLF-delimited protocol line — an SMTP /
|
|
836
|
+
* RFC 5322 header value, an HTTP header — contains CR, LF, or a NUL byte,
|
|
837
|
+
* the canonical header-injection / smuggling vector. Route every
|
|
838
|
+
* <code>Name: value\r\n</code> builder's STRUCTURED fields (addresses,
|
|
839
|
+
* domains, identifiers, MTA names) through this; they can never
|
|
840
|
+
* legitimately carry those bytes. For free-text that may legitimately wrap
|
|
841
|
+
* (a multi-line SMTP reply folded into one diagnostic line), fold it with
|
|
842
|
+
* <code>stripCrlf</code> instead of rejecting. Throws
|
|
843
|
+
* <code>new ErrorClass(code, ...)</code> so each caller reports in its own
|
|
844
|
+
* error domain (the <code>validateOpts</code> convention). A non-string
|
|
845
|
+
* value passes through untouched — callers type-check separately.
|
|
846
|
+
*
|
|
847
|
+
* @example
|
|
848
|
+
* b.safeBuffer.assertHeaderSafe("rcpt@example.com", "to", MailError, "mail/bad-header");
|
|
849
|
+
* // → "rcpt@example.com"
|
|
850
|
+
*
|
|
851
|
+
* b.safeBuffer.assertHeaderSafe("rcpt\r\nBcc: evil@x", "to", MailError, "mail/bad-header");
|
|
852
|
+
* // → throws MailError("mail/bad-header")
|
|
853
|
+
*/
|
|
854
|
+
function assertHeaderSafe(value, label, ErrorClass, code) {
|
|
855
|
+
if (typeof value === "string" &&
|
|
856
|
+
(CRLF_RE.test(value) || value.indexOf("\u0000") !== -1)) {
|
|
857
|
+
throw new ErrorClass(code,
|
|
858
|
+
label + ": must not contain CR, LF, or NUL (header injection)");
|
|
859
|
+
}
|
|
860
|
+
return value;
|
|
861
|
+
}
|
|
862
|
+
|
|
801
863
|
module.exports = {
|
|
802
864
|
normalizeText: normalizeText,
|
|
803
865
|
toBuffer: toBuffer,
|
|
@@ -809,6 +871,8 @@ module.exports = {
|
|
|
809
871
|
isHex: isHex,
|
|
810
872
|
hasCrlf: hasCrlf,
|
|
811
873
|
stripCrlf: stripCrlf,
|
|
874
|
+
foldHeaderText: foldHeaderText,
|
|
875
|
+
assertHeaderSafe: assertHeaderSafe,
|
|
812
876
|
stripTrailingHspace: stripTrailingHspace,
|
|
813
877
|
indexAfterOpenTag: indexAfterOpenTag,
|
|
814
878
|
HEX_RE: HEX_RE,
|
package/lib/safe-icap.js
CHANGED
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
|
|
78
78
|
var C = require("./constants");
|
|
79
79
|
var { defineClass } = require("./framework-error");
|
|
80
|
+
var pick = require("./pick");
|
|
80
81
|
var gateContract = require("./gate-contract");
|
|
81
82
|
|
|
82
83
|
var SafeIcapError = defineClass("SafeIcapError", { alwaysPermanent: true });
|
|
@@ -395,6 +396,10 @@ function _parseHeaderLine(line, maxValueBytes) {
|
|
|
395
396
|
}
|
|
396
397
|
|
|
397
398
|
function _addHeader(headers, name, value) {
|
|
399
|
+
// A header named __proto__ / constructor / prototype would pollute or
|
|
400
|
+
// shadow the plain-object header map; drop it (ICAP headers are echoed
|
|
401
|
+
// from an untrusted upstream response).
|
|
402
|
+
if (pick.isPoisonedKey(name)) return;
|
|
398
403
|
if (headers[name] === undefined) {
|
|
399
404
|
headers[name] = value;
|
|
400
405
|
} else if (Array.isArray(headers[name])) {
|
package/lib/session.js
CHANGED
|
@@ -510,6 +510,30 @@ async function create(opts) {
|
|
|
510
510
|
* var roles = (info.data && info.data.roles) || [];
|
|
511
511
|
* // → { userId: "user-42", data: { roles: ["admin"] }, createdAt: ..., expiresAt: ..., lastActivity: ..., fingerprintDrift: false, fingerprintAnomalyScore: null }
|
|
512
512
|
*/
|
|
513
|
+
// Evaluate the idle + absolute timeout floors against a session row.
|
|
514
|
+
// Returns { action, metadata } describing the breach, or null if the session
|
|
515
|
+
// is within both floors. Centralized so EVERY session-read path (verify,
|
|
516
|
+
// touch, ...) enforces the floors identically — a refresh must never
|
|
517
|
+
// resurrect a session that verify() would expire.
|
|
518
|
+
function _timeoutFloorBreach(row, nowMs, opts) {
|
|
519
|
+
opts = opts || {};
|
|
520
|
+
var idleMs = opts.idleTimeoutMs !== undefined ? opts.idleTimeoutMs : DEFAULT_IDLE_TIMEOUT_MS;
|
|
521
|
+
var absMs = opts.absoluteTimeoutMs !== undefined ? opts.absoluteTimeoutMs : DEFAULT_ABSOLUTE_TIMEOUT_MS;
|
|
522
|
+
if (idleMs > 0) {
|
|
523
|
+
var lastActivity = Number(row.lastActivity);
|
|
524
|
+
if ((nowMs - lastActivity) > idleMs) {
|
|
525
|
+
return { action: "auth.session.expired_idle", metadata: { idleMs: nowMs - lastActivity, threshold: idleMs } };
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (absMs > 0) {
|
|
529
|
+
var createdAt = Number(row.createdAt);
|
|
530
|
+
if ((nowMs - createdAt) > absMs) {
|
|
531
|
+
return { action: "auth.session.expired_absolute", metadata: { ageMs: nowMs - createdAt, threshold: absMs } };
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
|
|
513
537
|
async function verify(token, verifyOpts) {
|
|
514
538
|
if (typeof token !== "string" || token.length === 0) return null;
|
|
515
539
|
verifyOpts = verifyOpts || {};
|
|
@@ -541,39 +565,15 @@ async function verify(token, verifyOpts) {
|
|
|
541
565
|
// SP 800-63B-4). These shorten the effective lifetime even when the
|
|
542
566
|
// operator picked a long ttlMs. Defaults: idle 30m, absolute 12h.
|
|
543
567
|
// Operator opt-out by passing 0 (disables that timeout).
|
|
544
|
-
var
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
try {
|
|
552
|
-
audit.safeEmit({
|
|
553
|
-
action: "auth.session.expired_idle", outcome: "success",
|
|
554
|
-
metadata: { idleMs: nowMs - lastActivity, threshold: idleMs },
|
|
555
|
-
});
|
|
556
|
-
} catch (_ignored) { /* audit best-effort */ }
|
|
557
|
-
if (cluster.isLeader()) {
|
|
558
|
-
try { await _deleteBySidHash(sidHash); } catch (_e) { /* best-effort */ }
|
|
559
|
-
}
|
|
560
|
-
return null;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
if (absMs > 0) {
|
|
564
|
-
var createdAt = Number(row.createdAt);
|
|
565
|
-
if ((nowMs - createdAt) > absMs) {
|
|
566
|
-
try {
|
|
567
|
-
audit.safeEmit({
|
|
568
|
-
action: "auth.session.expired_absolute", outcome: "success",
|
|
569
|
-
metadata: { ageMs: nowMs - createdAt, threshold: absMs },
|
|
570
|
-
});
|
|
571
|
-
} catch (_ignored) { /* audit best-effort */ }
|
|
572
|
-
if (cluster.isLeader()) {
|
|
573
|
-
try { await _deleteBySidHash(sidHash); } catch (_e) { /* best-effort */ }
|
|
574
|
-
}
|
|
575
|
-
return null;
|
|
568
|
+
var floorBreach = _timeoutFloorBreach(row, nowMs, verifyOpts);
|
|
569
|
+
if (floorBreach) {
|
|
570
|
+
try {
|
|
571
|
+
audit.safeEmit({ action: floorBreach.action, outcome: "success", metadata: floorBreach.metadata });
|
|
572
|
+
} catch (_ignored) { /* audit best-effort */ }
|
|
573
|
+
if (cluster.isLeader()) {
|
|
574
|
+
try { await _deleteBySidHash(sidHash); } catch (_e) { /* best-effort */ }
|
|
576
575
|
}
|
|
576
|
+
return null;
|
|
577
577
|
}
|
|
578
578
|
// Unseal sealed columns (userId, data) using the cryptoField pipeline
|
|
579
579
|
// so we return cleartext to the caller — same shape as the previous
|
|
@@ -909,6 +909,21 @@ async function touch(token, opts) {
|
|
|
909
909
|
if (sid === null) return false;
|
|
910
910
|
var sidHash = _hashSid(sid);
|
|
911
911
|
var nowMs = Date.now();
|
|
912
|
+
// A session past its idle / absolute timeout floor must not be resurrected
|
|
913
|
+
// by a refresh — enforce the floors (as verify does) before extending.
|
|
914
|
+
// Without this, touch() would reset lastActivity on a session verify()
|
|
915
|
+
// would have expired, defeating the floor.
|
|
916
|
+
var floorSel = sql.select(_sessionSqlTable(), _sessionSqlOpts())
|
|
917
|
+
.columns(["createdAt", "lastActivity"])
|
|
918
|
+
.where("sidHash", sidHash)
|
|
919
|
+
.where("expiresAt", ">=", nowMs)
|
|
920
|
+
.toSql();
|
|
921
|
+
var floorRow = await _currentStore().executeOne(floorSel.sql, floorSel.params);
|
|
922
|
+
if (!floorRow) return false;
|
|
923
|
+
if (_timeoutFloorBreach(floorRow, nowMs, opts)) {
|
|
924
|
+
try { await _deleteBySidHash(sidHash); } catch (_e) { /* best-effort */ }
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
912
927
|
// Two SQL paths so the SET list stays static (no dynamic column
|
|
913
928
|
// assembly) and matches the call shape clusterStorage expects.
|
|
914
929
|
// extendBy resets expiresAt relative to NOW, not relative to the
|
package/lib/vc.js
CHANGED
|
@@ -69,6 +69,32 @@ var JOSE_ALGS = {
|
|
|
69
69
|
"EdDSA": { nodeHash: null },
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
+
// RFC 7518 §3.4 binds each ECDSA alg to a specific curve. The verifier must
|
|
73
|
+
// reject a signature whose header alg does not match the resolved key's type
|
|
74
|
+
// / curve — otherwise the attacker picks the alg (hash + encoding) from the
|
|
75
|
+
// header independent of the key an ECDSA curve/type confusion (CWE-347).
|
|
76
|
+
var _ES_ALG_CURVE = { "ES256": "prime256v1", "ES384": "secp384r1", "ES512": "secp521r1" };
|
|
77
|
+
|
|
78
|
+
function _assertJoseAlgKey(algName, key) {
|
|
79
|
+
var kt = key && key.asymmetricKeyType;
|
|
80
|
+
if (algName === "EdDSA") {
|
|
81
|
+
if (kt !== "ed25519" && kt !== "ed448") {
|
|
82
|
+
throw new VcError("vc/alg-key-mismatch",
|
|
83
|
+
"vc.verify: alg EdDSA requires an Ed25519/Ed448 key, got " + (kt || "?"));
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
var wantCurve = _ES_ALG_CURVE[algName];
|
|
88
|
+
if (wantCurve) {
|
|
89
|
+
var gotCurve = key && key.asymmetricKeyDetails && key.asymmetricKeyDetails.namedCurve;
|
|
90
|
+
if (kt !== "ec" || gotCurve !== wantCurve) {
|
|
91
|
+
throw new VcError("vc/alg-key-mismatch",
|
|
92
|
+
"vc.verify: alg " + algName + " requires an EC " + wantCurve + " key, got " +
|
|
93
|
+
(kt || "?") + "/" + (gotCurve || "?"));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
72
98
|
function _b64urlJson(obj) {
|
|
73
99
|
return Buffer.from(JSON.stringify(obj), "utf8").toString("base64url");
|
|
74
100
|
}
|
|
@@ -234,6 +260,9 @@ function _verifyJose(token, opts, expectedTyp) {
|
|
|
234
260
|
}
|
|
235
261
|
var params = JOSE_ALGS[header.alg];
|
|
236
262
|
var pub = opts.publicKey ? _toKey(opts.publicKey, "public") : _toKey(opts.keyResolver(header), "public");
|
|
263
|
+
// Bind the header-declared alg to the resolved key's type/curve before
|
|
264
|
+
// verifying — reject an alg the attacker chose that doesn't match the key.
|
|
265
|
+
_assertJoseAlgKey(header.alg, pub);
|
|
237
266
|
var signingInput = parts[0] + "." + parts[1];
|
|
238
267
|
var sig = Buffer.from(parts[2], "base64url");
|
|
239
268
|
var ok = params.nodeHash === null
|
package/lib/vendor/MANIFEST.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"hashes": {
|
|
19
19
|
"server": "sha256:5d539dfc9ef47121d4c09bd7256d76448a1f5ac47ee09ac44c78ff6a062af9ab"
|
|
20
20
|
},
|
|
21
|
-
"refreshedAt": "2026-
|
|
21
|
+
"refreshedAt": "2026-07-02T23:54:12.403Z"
|
|
22
22
|
},
|
|
23
23
|
"@noble/curves": {
|
|
24
24
|
"version": "2.2.0",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"hashes": {
|
|
41
41
|
"server": "sha256:ebf254d5eb56aef8705a1c4af9603f47987b4870a9bb5e657e06907b701e2731"
|
|
42
42
|
},
|
|
43
|
-
"refreshedAt": "2026-
|
|
43
|
+
"refreshedAt": "2026-07-02T23:54:12.403Z"
|
|
44
44
|
},
|
|
45
45
|
"@noble/post-quantum": {
|
|
46
46
|
"version": "0.6.1",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"hashes": {
|
|
72
72
|
"server": "sha256:f9190309daadca4c2e2cc2b76beaa6b96e463429cc3c390bd9f0ceaf7b588c68"
|
|
73
73
|
},
|
|
74
|
-
"refreshedAt": "2026-
|
|
74
|
+
"refreshedAt": "2026-07-02T23:54:12.403Z"
|
|
75
75
|
},
|
|
76
76
|
"@simplewebauthn/server": {
|
|
77
77
|
"version": "13.3.2",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"hashes": {
|
|
95
95
|
"server": "sha256:49411d893f5e9b0e2fcaa564b4ec7921f73a9a06229b5e53d49c1453ea1a365c"
|
|
96
96
|
},
|
|
97
|
-
"refreshedAt": "2026-
|
|
97
|
+
"refreshedAt": "2026-07-02T23:54:12.403Z"
|
|
98
98
|
},
|
|
99
99
|
"SecLists-common-passwords-top-10000": {
|
|
100
100
|
"version": "10k-most-common (master)",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
},
|
|
115
115
|
"runtime_artifact": "lib/vendor/common-passwords-top-10000.data.js",
|
|
116
116
|
"integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
|
|
117
|
-
"refreshedAt": "2026-
|
|
117
|
+
"refreshedAt": "2026-07-02T23:54:12.403Z"
|
|
118
118
|
},
|
|
119
119
|
"bimi-trust-anchors": {
|
|
120
120
|
"version": "operator-managed",
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
},
|
|
140
140
|
"runtime_artifact": "lib/vendor/bimi-trust-anchors.data.js",
|
|
141
141
|
"integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
|
|
142
|
-
"refreshedAt": "2026-
|
|
142
|
+
"refreshedAt": "2026-07-02T23:54:12.403Z"
|
|
143
143
|
},
|
|
144
144
|
"publicsuffix-list": {
|
|
145
145
|
"version": "master",
|
|
@@ -154,12 +154,12 @@
|
|
|
154
154
|
"bundler": "curl https://publicsuffix.org/list/public_suffix_list.dat",
|
|
155
155
|
"bundledAt": "2026-06-24T00:00:00Z",
|
|
156
156
|
"hashes": {
|
|
157
|
-
"server": "sha256:
|
|
158
|
-
"data_js": "sha256:
|
|
157
|
+
"server": "sha256:5afbeea28737514a69a0bdca7cd3818979e67c443444de4d19f2f8713cf5372e",
|
|
158
|
+
"data_js": "sha256:6ed61937db12f148494db7c336498740bf4458cd9f49ec73d887eda83529f207"
|
|
159
159
|
},
|
|
160
160
|
"runtime_artifact": "lib/vendor/public-suffix-list.data.js",
|
|
161
161
|
"integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
|
|
162
|
-
"refreshedAt": "2026-
|
|
162
|
+
"refreshedAt": "2026-07-02T23:54:12.403Z"
|
|
163
163
|
},
|
|
164
164
|
"peculiar-pki": {
|
|
165
165
|
"version": "2.0.0+pkijs-3.4.0",
|
|
@@ -190,7 +190,7 @@
|
|
|
190
190
|
"hashes": {
|
|
191
191
|
"server": "sha256:9bbc191afaaa2b1e5757f00480457c08134cdc2c55d541df18d9155bba9cbf77"
|
|
192
192
|
},
|
|
193
|
-
"refreshedAt": "2026-
|
|
193
|
+
"refreshedAt": "2026-07-02T23:54:12.403Z"
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// Please pull this list from, and only from https://publicsuffix.org/list/public_suffix_list.dat,
|
|
6
6
|
// rather than any other VCS sites. Pulling from any other URL is not guaranteed to be supported.
|
|
7
7
|
|
|
8
|
-
// VERSION: 2026-
|
|
9
|
-
// COMMIT:
|
|
8
|
+
// VERSION: 2026-07-02_09-58-14_UTC
|
|
9
|
+
// COMMIT: d67b6cb8e70e9c76b0803809b66e7ffdd09484bd
|
|
10
10
|
|
|
11
11
|
// Instructions on pulling and using this list can be found at https://publicsuffix.org/list/.
|
|
12
12
|
|
|
@@ -14614,7 +14614,6 @@ polyspace.com
|
|
|
14614
14614
|
// May First - People Link : https://mayfirst.org/
|
|
14615
14615
|
// Submitted by Jamie McClelland <info@mayfirst.org>
|
|
14616
14616
|
mayfirst.info
|
|
14617
|
-
mayfirst.org
|
|
14618
14617
|
|
|
14619
14618
|
// McHost : https://mchost.ru
|
|
14620
14619
|
// Submitted by Evgeniy Subbotin <e.subbotin@mchost.ru>
|
|
@@ -16411,7 +16410,6 @@ zabc.net
|
|
|
16411
16410
|
|
|
16412
16411
|
// ===END PRIVATE DOMAINS===
|
|
16413
16412
|
|
|
16414
|
-
|
|
16415
16413
|
// ===BEGIN blamejs canary===
|
|
16416
16414
|
// Honeytoken — vendor-data integrity defense (lib/vendor-data.js).
|
|
16417
16415
|
_blamejs_canary_v0_9_8_.local
|