@blamejs/blamejs-shop 0.3.11 → 0.3.13
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/lib/admin.js +157 -117
- package/lib/asset-manifest.json +1 -1
- package/lib/currency-rounding.js +2 -14
- package/lib/index.js +1 -0
- package/lib/text-guard.js +227 -0
- package/lib/vendor/MANIFEST.json +2 -2
- package/lib/vendor/blamejs/CHANGELOG.md +2 -0
- package/lib/vendor/blamejs/README.md +3 -2
- package/lib/vendor/blamejs/SECURITY.md +3 -0
- package/lib/vendor/blamejs/api-snapshot.json +14 -2
- package/lib/vendor/blamejs/lib/agent-event-bus.js +4 -4
- package/lib/vendor/blamejs/lib/agent-idempotency.js +6 -6
- package/lib/vendor/blamejs/lib/agent-orchestrator.js +9 -9
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +10 -10
- package/lib/vendor/blamejs/lib/agent-saga.js +6 -7
- package/lib/vendor/blamejs/lib/agent-snapshot.js +8 -8
- package/lib/vendor/blamejs/lib/agent-stream.js +3 -3
- package/lib/vendor/blamejs/lib/agent-tenant.js +4 -4
- package/lib/vendor/blamejs/lib/agent-trace.js +5 -5
- package/lib/vendor/blamejs/lib/ai-disclosure.js +3 -3
- package/lib/vendor/blamejs/lib/app.js +2 -2
- package/lib/vendor/blamejs/lib/archive-read.js +1 -1
- package/lib/vendor/blamejs/lib/archive-tar-read.js +1 -1
- package/lib/vendor/blamejs/lib/archive-wrap.js +5 -5
- package/lib/vendor/blamejs/lib/audit-tools.js +65 -5
- package/lib/vendor/blamejs/lib/audit.js +2 -2
- package/lib/vendor/blamejs/lib/auth/ciba.js +1 -1
- package/lib/vendor/blamejs/lib/auth/dpop.js +1 -1
- package/lib/vendor/blamejs/lib/auth/fal.js +1 -1
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +2 -3
- package/lib/vendor/blamejs/lib/auth/jwt-external.js +2 -2
- package/lib/vendor/blamejs/lib/auth/oauth.js +9 -9
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +7 -7
- package/lib/vendor/blamejs/lib/auth/oid4vp.js +1 -1
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +5 -5
- package/lib/vendor/blamejs/lib/auth/passkey.js +6 -6
- package/lib/vendor/blamejs/lib/auth/saml.js +1 -1
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +3 -6
- package/lib/vendor/blamejs/lib/backup/index.js +18 -18
- package/lib/vendor/blamejs/lib/cache.js +4 -4
- package/lib/vendor/blamejs/lib/calendar.js +5 -5
- package/lib/vendor/blamejs/lib/circuit-breaker.js +1 -1
- package/lib/vendor/blamejs/lib/cms-codec.js +2 -2
- package/lib/vendor/blamejs/lib/compliance.js +14 -14
- package/lib/vendor/blamejs/lib/crypto-field.js +58 -21
- package/lib/vendor/blamejs/lib/crypto.js +5 -6
- package/lib/vendor/blamejs/lib/db-query.js +131 -9
- package/lib/vendor/blamejs/lib/db.js +106 -22
- package/lib/vendor/blamejs/lib/external-db.js +64 -16
- package/lib/vendor/blamejs/lib/framework-schema.js +4 -4
- package/lib/vendor/blamejs/lib/guard-list-id.js +2 -2
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +1 -2
- package/lib/vendor/blamejs/lib/incident-report.js +150 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +1 -1
- package/lib/vendor/blamejs/lib/mail-deploy.js +3 -3
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +2 -2
- package/lib/vendor/blamejs/lib/mail-server-pop3.js +2 -2
- package/lib/vendor/blamejs/lib/mail-store.js +1 -1
- package/lib/vendor/blamejs/lib/metrics.js +8 -8
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/dpop.js +5 -5
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +21 -22
- package/lib/vendor/blamejs/lib/middleware/protected-resource-metadata.js +2 -2
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +2 -2
- package/lib/vendor/blamejs/lib/network-dns.js +1 -2
- package/lib/vendor/blamejs/lib/network-tls.js +0 -1
- package/lib/vendor/blamejs/lib/outbox.js +1 -1
- package/lib/vendor/blamejs/lib/pqc-agent.js +1 -1
- package/lib/vendor/blamejs/lib/retention.js +1 -1
- package/lib/vendor/blamejs/lib/retry.js +1 -1
- package/lib/vendor/blamejs/lib/safe-archive.js +2 -2
- package/lib/vendor/blamejs/lib/safe-ical.js +2 -2
- package/lib/vendor/blamejs/lib/safe-mime.js +1 -1
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +1 -1
- package/lib/vendor/blamejs/lib/self-update.js +2 -2
- package/lib/vendor/blamejs/lib/static.js +1 -1
- package/lib/vendor/blamejs/lib/subject.js +2 -2
- package/lib/vendor/blamejs/lib/vault/index.js +64 -1
- package/lib/vendor/blamejs/lib/vault/rotate.js +19 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +1 -1
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.14.7.json +77 -0
- package/lib/vendor/blamejs/scripts/release.js +28 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-tools-dual-control.test.js +115 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +163 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +102 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-key-aad.test.js +109 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +53 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/incident-report.test.js +65 -0
- package/lib/webhook-subscriptions.js +11 -24
- package/lib/webhooks.js +12 -33
- package/package.json +1 -1
|
@@ -58,8 +58,7 @@
|
|
|
58
58
|
*
|
|
59
59
|
* ## No saga-level retry
|
|
60
60
|
*
|
|
61
|
-
*
|
|
62
|
-
* 2026-05-14): saga's value-add is compensation, not retry. If a
|
|
61
|
+
* Saga's value-add is compensation, not retry. If a
|
|
63
62
|
* step needs retry-with-backoff, the operator wraps `step.run`
|
|
64
63
|
* with `b.retry` inside the step body. With v0.9.22 idempotency
|
|
65
64
|
* available, internal retry inside step.run is side-effect-safe.
|
|
@@ -109,7 +108,7 @@ var SAGA_ID_RAND_BYTES = 8;
|
|
|
109
108
|
function create(config) {
|
|
110
109
|
guardSagaConfig.validate(config);
|
|
111
110
|
var auditImpl = config.audit || audit();
|
|
112
|
-
//
|
|
111
|
+
// Operator wires a stateStore for crash-safe resume.
|
|
113
112
|
// Interface: { saveStep, loadResumePoint, markCompleted, markFailed }.
|
|
114
113
|
// saveStep({sagaId, stepIndex, stepName, state, status}) commits
|
|
115
114
|
// after each step.run; loadResumePoint(sagaId) returns the resume
|
|
@@ -189,7 +188,7 @@ async function _runFrom(config, auditImpl, stateStore, ctx, state, opts, sagaId,
|
|
|
189
188
|
});
|
|
190
189
|
await step.run(ctx, state);
|
|
191
190
|
completedSteps.push({ step: step, index: i });
|
|
192
|
-
//
|
|
191
|
+
// Checkpoint after the step.run returns. saveStep
|
|
193
192
|
// commits the post-step state so a crash before the NEXT step
|
|
194
193
|
// resumes from i+1. The audit chain records the checkpoint
|
|
195
194
|
// independently of the operator's stateStore — operator can
|
|
@@ -232,7 +231,7 @@ async function _runFrom(config, auditImpl, stateStore, ctx, state, opts, sagaId,
|
|
|
232
231
|
message: (stepErr && stepErr.message) || String(stepErr),
|
|
233
232
|
});
|
|
234
233
|
var compensationError = null;
|
|
235
|
-
//
|
|
234
|
+
// Capture the compensation step that ACTUALLY failed,
|
|
236
235
|
// not "completedSteps[completedSteps.length-1].name" which
|
|
237
236
|
// names the last-COMPLETED step regardless of which compensation
|
|
238
237
|
// threw. CWE-209-adjacent (information disclosure via wrong
|
|
@@ -278,7 +277,7 @@ async function _runFrom(config, auditImpl, stateStore, ctx, state, opts, sagaId,
|
|
|
278
277
|
});
|
|
279
278
|
} catch (_e) { /* drop-silent — audit already records */ }
|
|
280
279
|
}
|
|
281
|
-
//
|
|
280
|
+
// Attach cause:stepErr so the original step
|
|
282
281
|
// error stack survives. ES2022 Error.cause is the standard
|
|
283
282
|
// mechanism; the framework's defineClass-built AgentSagaError
|
|
284
283
|
// accepts cause via the third arg.
|
|
@@ -290,7 +289,7 @@ async function _runFrom(config, auditImpl, stateStore, ctx, state, opts, sagaId,
|
|
|
290
289
|
((compensationError.message) || String(compensationError));
|
|
291
290
|
}
|
|
292
291
|
var sagaErr = new AgentSagaError("agent-saga/failed", detailMsg);
|
|
293
|
-
//
|
|
292
|
+
// ES2022 Error.cause attaches the originating
|
|
294
293
|
// stepErr so operator stack-trace tooling can walk the chain.
|
|
295
294
|
// defineClass({alwaysPermanent:true}) doesn't accept cause in
|
|
296
295
|
// its constructor signature; the property assignment after
|
|
@@ -60,7 +60,7 @@ var vault = lazyRequire(function () { return require("./vault");
|
|
|
60
60
|
|
|
61
61
|
var AgentSnapshotError = defineClass("AgentSnapshotError", { alwaysPermanent: true });
|
|
62
62
|
|
|
63
|
-
//
|
|
63
|
+
// Sealed envelopes start with this prefix on disk; the
|
|
64
64
|
// loader sniffs it and routes through unseal before guardSnapshotEnvelope
|
|
65
65
|
// validation. Compatible with operator backends that store the value
|
|
66
66
|
// as a string (JSON DBs, k/v stores) or wrap it in `{ value: "..." }`.
|
|
@@ -113,19 +113,19 @@ function create(opts) {
|
|
|
113
113
|
var snapshotIntervalMs = typeof policy.snapshotIntervalMs === "number" ? policy.snapshotIntervalMs : DEFAULT_SNAPSHOT_INTERVAL_MS;
|
|
114
114
|
var maxSnapshotBytes = typeof policy.maxSnapshotBytes === "number" ? policy.maxSnapshotBytes : DEFAULT_MAX_SNAPSHOT_BYTES;
|
|
115
115
|
var auditImpl = opts.audit || audit();
|
|
116
|
-
//
|
|
116
|
+
// Operator may inject `signer` (interface
|
|
117
117
|
// `{ sign(bytes) → Buffer, verify(bytes, sig, pubKey?) → boolean }`)
|
|
118
118
|
// for testing / alternate key custody. Default = b.auditSign when
|
|
119
119
|
// initialized at boot; refuses persist() with a clear error if
|
|
120
120
|
// neither is wired so secure-by-default holds.
|
|
121
121
|
var signer = opts.signer || null;
|
|
122
|
-
//
|
|
122
|
+
// Operator may inject `sealer` (interface
|
|
123
123
|
// `{ seal(plaintext, aadParts) → string, unseal(value, aadParts) → string }`)
|
|
124
124
|
// for alternate KMS integration. Default = b.vault.aad. Refused if
|
|
125
125
|
// neither is wired AND opts.allowPlaintext is not explicitly true
|
|
126
126
|
// (operator-justified dev / single-tenant deployments only).
|
|
127
127
|
var sealer = opts.sealer || null;
|
|
128
|
-
//
|
|
128
|
+
// Operator-supplied restoreHandlers walk the
|
|
129
129
|
// snapshot inFlight + idempotencyCache + orchestratorState segments
|
|
130
130
|
// and hydrate the corresponding consumer module. Map shape:
|
|
131
131
|
// { streams, sagas, outboxJobs, busSubscribers, pendingDeliveries,
|
|
@@ -300,7 +300,7 @@ async function _takeSnapshot(ctx, snapshotOpts) {
|
|
|
300
300
|
|
|
301
301
|
async function _persist(ctx, snap) {
|
|
302
302
|
guardSnapshotEnvelope.validate(snap);
|
|
303
|
-
//
|
|
303
|
+
// Sign first so a backend that mutates on put() (very
|
|
304
304
|
// common for k/v stores adding metadata) doesn't poison the signed
|
|
305
305
|
// bytes downstream readers verify.
|
|
306
306
|
var signer = _resolveSigner(ctx);
|
|
@@ -314,7 +314,7 @@ async function _persist(ctx, snap) {
|
|
|
314
314
|
// pubkey at verify time).
|
|
315
315
|
snap.sigPubKey = (typeof signer.getPublicKey === "function" && signer.getPublicKey()) || null;
|
|
316
316
|
|
|
317
|
-
//
|
|
317
|
+
// Seal the entire envelope under AAD that pins
|
|
318
318
|
// snapshotId + schemaVersion + tenantId. AAD mismatch on unseal (a
|
|
319
319
|
// copy-paste attack from one snapshotId's row into another) fails
|
|
320
320
|
// the Poly1305 tag check; tampered bytes also fail. The sealed
|
|
@@ -405,7 +405,7 @@ async function _unwrapAndVerify(ctx, raw, expectedId) {
|
|
|
405
405
|
throw new AgentSnapshotError("agent-snapshot/snapshot-id-mismatch",
|
|
406
406
|
"load: wrapper snapshotId='" + expectedId + "' does not match envelope='" + snap.snapshotId + "'");
|
|
407
407
|
}
|
|
408
|
-
//
|
|
408
|
+
// Verify the signature before returning the envelope
|
|
409
409
|
// to the caller. Restore-side trust derives from this gate. The
|
|
410
410
|
// allowPlaintext escape hatch (operator-acknowledged dev mode)
|
|
411
411
|
// also waives signature verification because there's no key custody
|
|
@@ -496,7 +496,7 @@ async function _restore(ctx, snap, restoreOpts) {
|
|
|
496
496
|
affectedStreams: (snap.inFlight && snap.inFlight.streams || []).length,
|
|
497
497
|
});
|
|
498
498
|
}
|
|
499
|
-
//
|
|
499
|
+
// Invoke operator-supplied restoreHandlers across
|
|
500
500
|
// every segment the snapshot envelope carries. Handlers are
|
|
501
501
|
// declared at create() time; the snapshot primitive owns ordering
|
|
502
502
|
// (orchestratorState first so live agents register before consumers
|
|
@@ -138,7 +138,7 @@ function _makeIterator(ctx) {
|
|
|
138
138
|
var done = false;
|
|
139
139
|
var closed = false;
|
|
140
140
|
var drained = false;
|
|
141
|
-
//
|
|
141
|
+
// Track the cursor of the LAST row actually yielded
|
|
142
142
|
// to the consumer. The prior shape called cursor.lastSeenCursor()
|
|
143
143
|
// at drain-marker emit, which returned the position of the last
|
|
144
144
|
// FETCHED batch — clients resuming from that cursor SKIPPED every
|
|
@@ -167,7 +167,7 @@ function _makeIterator(ctx) {
|
|
|
167
167
|
try {
|
|
168
168
|
if (buffer.length > 0) {
|
|
169
169
|
var row = buffer.shift();
|
|
170
|
-
//
|
|
170
|
+
// Record the cursor for this yielded row so a
|
|
171
171
|
// drain that fires BETWEEN buffered yields emits a marker
|
|
172
172
|
// whose lastSeenCursor matches what the client actually
|
|
173
173
|
// received. The cursor extraction shape mirrors the
|
|
@@ -248,7 +248,7 @@ function _safeAudit(auditImpl, action, actor, metadata) {
|
|
|
248
248
|
agentAudit.safeAudit(auditImpl, action, actor, metadata);
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
//
|
|
251
|
+
// Resolve the resume cursor for a row about to be
|
|
252
252
|
// yielded. Operators may attach the cursor per-row (`row._cursor` /
|
|
253
253
|
// `row.cursor`) OR rely on the cursor's own per-row tracker
|
|
254
254
|
// (`cursor.cursorForRow(row)`) — both shapes supported.
|
|
@@ -222,7 +222,7 @@ async function _unregister(ctx, tenantId, args) {
|
|
|
222
222
|
}
|
|
223
223
|
// Archive default — retain the key + metadata for retention-mandated
|
|
224
224
|
// restoration. Operator's compliance regime drives archivePolicy.
|
|
225
|
-
//
|
|
225
|
+
// Persist as a `status: "archived"` row in the same
|
|
226
226
|
// backend rather than only the process-local Map. GDPR Art. 17 +
|
|
227
227
|
// HIPAA §164.530(j) require the archived state to survive process
|
|
228
228
|
// restart (auditor pulls a deleted tenant's archival record years
|
|
@@ -373,7 +373,7 @@ function _check(ctx, actor, agentTenantId) {
|
|
|
373
373
|
|
|
374
374
|
// ---- Per-tenant derived key -----------------------------------------------
|
|
375
375
|
//
|
|
376
|
-
//
|
|
376
|
+
// `namespaceHash(label, tenantId)` is a PUBLIC function
|
|
377
377
|
// over PUBLIC inputs; an attacker who learns `tenantId` (an account id
|
|
378
378
|
// surfaced in URLs / API responses) reconstructs every per-tenant key
|
|
379
379
|
// without any secret material. The defense the docstring promises —
|
|
@@ -555,7 +555,7 @@ var TENANT_FIELD_PREFIX = "tnt-v1:";
|
|
|
555
555
|
function _tenantFieldKey(tenantId, table) {
|
|
556
556
|
// 32-byte symmetric key for XChaCha20-Poly1305. _deriveTenantKeyBytes
|
|
557
557
|
// returns the raw key bound to the vault master + tenantId + purpose
|
|
558
|
-
// — see
|
|
558
|
+
// — see the commentary above _derivedKey for the threat
|
|
559
559
|
// model that drove this away from public-input-only derivation.
|
|
560
560
|
return _deriveTenantKeyBytes(tenantId, "cryptoField:" + table);
|
|
561
561
|
}
|
|
@@ -646,7 +646,7 @@ function _unsealRowForTenant(ctx, tenantId, table, row) {
|
|
|
646
646
|
if (out[f] !== undefined && out[f] !== null) {
|
|
647
647
|
try { out[f] = _unsealField(tenantId, table, f, out[f]); }
|
|
648
648
|
catch (e) {
|
|
649
|
-
//
|
|
649
|
+
// Null-on-decrypt-failure was silent; the docstring
|
|
650
650
|
// promised "audit chain surfaces the failure" but no emit ever
|
|
651
651
|
// ran. Cross-tenant ciphertext replay / tampered row / wrong-
|
|
652
652
|
// prefix all hit this path; operator audit pipelines need the
|
|
@@ -70,7 +70,7 @@ var audit = lazyRequire(function () { return require("./audit"); })
|
|
|
70
70
|
|
|
71
71
|
var AgentTraceError = defineClass("AgentTraceError", { alwaysPermanent: true });
|
|
72
72
|
|
|
73
|
-
//
|
|
73
|
+
// Once-per-process audit emit on the first tracer
|
|
74
74
|
// failure each install fires. Operators get the signal even when
|
|
75
75
|
// individual span calls are best-effort suppressed.
|
|
76
76
|
var _failureAuditEmittedFor = Object.create(null);
|
|
@@ -122,7 +122,7 @@ function create(opts) {
|
|
|
122
122
|
injectIntoEnvelope: function (envelope, span) { return _injectIntoEnvelope(opts.tracing, envelope, span); },
|
|
123
123
|
extractFromEnvelope: function (envelope) { return _extractFromEnvelope(envelope); },
|
|
124
124
|
recordResult: function (span, result, error) { return _recordResult(span, result, error, auditImpl); },
|
|
125
|
-
//
|
|
125
|
+
// `shouldSample` now takes a traceId so the same
|
|
126
126
|
// trace gets the same decision across hops. Operator-supplied
|
|
127
127
|
// traceId comes from the W3C `traceparent` header at request-
|
|
128
128
|
// entry; absent that, falls back to Math.random (start of trace).
|
|
@@ -150,7 +150,7 @@ function _startSpan(tracing, name, sopts, auditImpl) {
|
|
|
150
150
|
return tracing.startSpan(name, sopts);
|
|
151
151
|
}
|
|
152
152
|
} catch (e) {
|
|
153
|
-
//
|
|
153
|
+
// Tracer failures should not crash the agent's
|
|
154
154
|
// method call; surface the first failure to the audit chain
|
|
155
155
|
// (rate-limited) so operators get the signal.
|
|
156
156
|
_emitFirstFailureAudit(auditImpl, "startSpan", e && e.message);
|
|
@@ -206,7 +206,7 @@ function _extractFromEnvelope(envelope) {
|
|
|
206
206
|
|
|
207
207
|
function _recordResult(span, result, error, auditImpl) {
|
|
208
208
|
if (!span || typeof span !== "object") return;
|
|
209
|
-
//
|
|
209
|
+
// Surface first occurrence of each span-op failure
|
|
210
210
|
// via audit so the operator gets the signal. Subsequent failures
|
|
211
211
|
// stay silent (best-effort) per the operational spec.
|
|
212
212
|
if (error) {
|
|
@@ -228,7 +228,7 @@ function _recordResult(span, result, error, auditImpl) {
|
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
//
|
|
231
|
+
// Deterministic sampling per W3C Trace Context §3.2.3.1.
|
|
232
232
|
// `Math.random` makes child-vs-parent sampling decisions non-coherent:
|
|
233
233
|
// a parent span sampled OUT can still have child spans sampled IN,
|
|
234
234
|
// producing orphaned spans operators can't correlate. Hashing the
|
|
@@ -133,7 +133,7 @@ function chatbot(session, opts) {
|
|
|
133
133
|
var text = typeof opts.text === "string" && opts.text.length > 0
|
|
134
134
|
? opts.text
|
|
135
135
|
: DEFAULT_CHATBOT_TEXT;
|
|
136
|
-
//
|
|
136
|
+
// "on-request" placement gates on
|
|
137
137
|
// the operator's explicit `opts.requested: true` signal. Without
|
|
138
138
|
// it, "on-request" collapsed into "always" semantics and emitted
|
|
139
139
|
// every call. The operator wires this from an explicit user
|
|
@@ -155,7 +155,7 @@ function chatbot(session, opts) {
|
|
|
155
155
|
regulation: "Regulation (EU) 2024/1689",
|
|
156
156
|
};
|
|
157
157
|
if (shouldEmit) {
|
|
158
|
-
//
|
|
158
|
+
// Mark the session so subsequent
|
|
159
159
|
// calls with the same session under "first-message" placement
|
|
160
160
|
// see `aiDisclosureEmitted: true` and return shouldEmit=false.
|
|
161
161
|
// Without this mutation operators had to remember to flip the
|
|
@@ -385,7 +385,7 @@ function applyAll(scenario) {
|
|
|
385
385
|
"applyAll: scenario.kinds must be a non-empty array of " +
|
|
386
386
|
"\"chatbot\" / \"deepfake\" / \"emotion\"");
|
|
387
387
|
}
|
|
388
|
-
//
|
|
388
|
+
// Validate every kind +
|
|
389
389
|
// per-kind required field UP FRONT, before any emission.
|
|
390
390
|
// Previously a later-kind failure (e.g. deepfake missing
|
|
391
391
|
// contentType, unknown trailing kind) ran AFTER earlier kinds
|
|
@@ -113,7 +113,7 @@ function _resolveMiddlewareOpt(value, allowDefault, name) {
|
|
|
113
113
|
if (value === false) {
|
|
114
114
|
// Operator explicitly disabled this middleware. When it's one of the
|
|
115
115
|
// security-on-by-default layers (allowDefault), leave an audit trace
|
|
116
|
-
// so the weakened posture is visible —
|
|
116
|
+
// so the weakened posture is visible — security defaults
|
|
117
117
|
// shouldn't be silently opt-out-able. Drop-silent observability sink.
|
|
118
118
|
if (allowDefault && name) {
|
|
119
119
|
try {
|
|
@@ -227,7 +227,7 @@ async function createApp(opts) {
|
|
|
227
227
|
var rateLimitOpts = _resolveMiddlewareOpt(mwConfig.rateLimit, false, "rateLimit");
|
|
228
228
|
if (rateLimitOpts) router.use(middleware.rateLimit(rateLimitOpts));
|
|
229
229
|
|
|
230
|
-
// Security middleware wired ON by default
|
|
230
|
+
// Security middleware wired ON by default. Each reads its
|
|
231
231
|
// config from opts.middleware.<name>: pass `false` to opt out (audited
|
|
232
232
|
// via _resolveMiddlewareOpt), or an object to customize — operator cookie
|
|
233
233
|
// / field names flow straight through, nothing static is baked in.
|
|
@@ -260,7 +260,7 @@ async function _readCentralDirectory(adapter, eocd) {
|
|
|
260
260
|
"central directory entry " + n + " carries ZIP64 sentinel sizes (unsupported — use tar for >4 GiB / >65535 entries)");
|
|
261
261
|
}
|
|
262
262
|
// ZIP names are CP437 or UTF-8 (per FLAG_UTF8_NAME bit). Decode
|
|
263
|
-
// as UTF-8 unconditionally —
|
|
263
|
+
// as UTF-8 unconditionally — a concern if operators in
|
|
264
264
|
// the wild rely on CP437; v0.12.7 ships UTF-8 only and operators
|
|
265
265
|
// with legacy CP437-only producers reach for an external decoder.
|
|
266
266
|
var name = cdBytes.slice(nameStart, nameStart + nameLen).toString("utf8");
|
|
@@ -237,7 +237,7 @@ function tar(adapter, opts) {
|
|
|
237
237
|
}
|
|
238
238
|
var bodyStart = pos;
|
|
239
239
|
var paddedSize = Math.ceil(hdr.size / BLOCK_SIZE) * BLOCK_SIZE;
|
|
240
|
-
//
|
|
240
|
+
// Refuse truncated archives upfront.
|
|
241
241
|
// The walker advances `pos` by the declared padded block size; if
|
|
242
242
|
// the buffer ends mid-body, extract() would silently slice a
|
|
243
243
|
// partial payload (header says 11 bytes, buffer holds 8 — without
|
|
@@ -272,7 +272,7 @@ function _encryptForRecipient(bytes, opts) {
|
|
|
272
272
|
};
|
|
273
273
|
}
|
|
274
274
|
if (r.publicKey) {
|
|
275
|
-
//
|
|
275
|
+
// B.crypto.encrypt falls back to
|
|
276
276
|
// ML-KEM-only when ecPublicKey is undefined (with a one-shot
|
|
277
277
|
// audit). For archive-wrap's recipient contract the hybrid leg
|
|
278
278
|
// (P-384 ECDH defence-in-depth backstop on top of ML-KEM-1024)
|
|
@@ -343,7 +343,7 @@ function sniffEnvelope(bytes) {
|
|
|
343
343
|
if (!Buffer.isBuffer(bytes) && !(bytes instanceof Uint8Array)) {
|
|
344
344
|
return "none";
|
|
345
345
|
}
|
|
346
|
-
//
|
|
346
|
+
// `Buffer.from(uint8Array)` copies
|
|
347
347
|
// the entire input, turning a constant-time 5-byte probe into an
|
|
348
348
|
// O(n) allocation. Use the zero-copy view form so the sniff is
|
|
349
349
|
// truly cheap regardless of input size.
|
|
@@ -351,7 +351,7 @@ function sniffEnvelope(bytes) {
|
|
|
351
351
|
? bytes
|
|
352
352
|
: Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
353
353
|
if (buf.length < 5) return "none";
|
|
354
|
-
//
|
|
354
|
+
// Match on the 5-byte ASCII magic
|
|
355
355
|
// alone, NOT on the full header (which requires version + saltLen
|
|
356
356
|
// bytes). A truncated envelope (`BAWRP` + nothing else) is still a
|
|
357
357
|
// recipient envelope; the unwrap call surfaces the truncation with
|
|
@@ -413,7 +413,7 @@ async function wrapWithPassphrase(bytes, opts) {
|
|
|
413
413
|
// alphabet. Operators sourcing passphrases from a random-bytes
|
|
414
414
|
// generator (high entropy density) pass without issue; operators
|
|
415
415
|
// typing dictionary phrases trip the gate.
|
|
416
|
-
//
|
|
416
|
+
// Typeof NaN === "number" passes
|
|
417
417
|
// typeof gate but bypasses downstream comparisons. Use isFinite
|
|
418
418
|
// so NaN / Infinity can't slip past the entropy gate.
|
|
419
419
|
var minEntropy;
|
|
@@ -516,7 +516,7 @@ async function unwrapWithPassphrase(sealed, opts) {
|
|
|
516
516
|
}
|
|
517
517
|
|
|
518
518
|
function _estimatePassphraseEntropyBits(passphrase) {
|
|
519
|
-
//
|
|
519
|
+
// Buffer passphrases (CSPRNG-
|
|
520
520
|
// generated random bytes) shouldn't be UTF-8 decoded for entropy
|
|
521
521
|
// estimation; the decoding artifacts (invalid sequences, BOM,
|
|
522
522
|
// surrogate pairs) make the alphabet-class measure unstable and
|
|
@@ -75,9 +75,39 @@ var FRAMEWORK_VERSION = (pkg && pkg.version) || "unknown";
|
|
|
75
75
|
// so importing db at audit-tools' top would close the cycle. Lazy
|
|
76
76
|
// keeps the load order one-way.
|
|
77
77
|
var db = lazyRequire(function () { return require("./db"); });
|
|
78
|
+
var audit = lazyRequire(function () { return require("./audit"); });
|
|
78
79
|
|
|
79
80
|
var AuditToolsError = defineClass("AuditToolsError", { alwaysPermanent: true });
|
|
80
81
|
|
|
82
|
+
// Dual-control gate constants for the audit_log physical purge. The
|
|
83
|
+
// purge erases signed audit history, so when an operator has declared
|
|
84
|
+
// audit_log under b.db.declareRequireDualControl the deletion requires
|
|
85
|
+
// a consumed m-of-n grant whose action matches AUDIT_LOG_PURGE_ACTION —
|
|
86
|
+
// the same separation-of-duties control b.db.eraseHard enforces (NIST
|
|
87
|
+
// SP 800-53 AU-9 + AC-5, HIPAA 45 CFR 164.312(b), PCI-DSS v4.0 10.5.1 /
|
|
88
|
+
// 10.7, SEC 17a-4(f), CWE-778).
|
|
89
|
+
var AUDIT_LOG_GATE_TABLE = "audit_log";
|
|
90
|
+
var AUDIT_LOG_PURGE_ACTION = "auditTools.purge";
|
|
91
|
+
|
|
92
|
+
function _resolveDualControlGate(opts) {
|
|
93
|
+
var checker = typeof opts.checkDualControlGate === "function"
|
|
94
|
+
? opts.checkDualControlGate
|
|
95
|
+
: function (t) { return db()._checkDualControlGate(t); };
|
|
96
|
+
try { return checker(AUDIT_LOG_GATE_TABLE); }
|
|
97
|
+
catch (_e) { return null; }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function _emitPurgeDenied(gate, reason) {
|
|
101
|
+
try {
|
|
102
|
+
audit().safeEmit({
|
|
103
|
+
action: "auditTools.purge.denied",
|
|
104
|
+
outcome: "denied",
|
|
105
|
+
reason: reason,
|
|
106
|
+
metadata: { table: AUDIT_LOG_GATE_TABLE, m: gate.m, n: gate.n, posture: gate.posture || null },
|
|
107
|
+
});
|
|
108
|
+
} catch (_e) { /* drop-silent — denial audit is best-effort */ }
|
|
109
|
+
}
|
|
110
|
+
|
|
81
111
|
var BUNDLE_FORMAT = "blamejs-audit-bundle-v1";
|
|
82
112
|
var KIND_ARCHIVE = "archive";
|
|
83
113
|
var KIND_EXPORT = "export";
|
|
@@ -149,7 +179,7 @@ function _rowToWireForm(row) {
|
|
|
149
179
|
return out;
|
|
150
180
|
}
|
|
151
181
|
|
|
152
|
-
//
|
|
182
|
+
// Operator-facing wire helper that surfaces recordedAt as
|
|
153
183
|
// ISO-8601 / RFC 3339 alongside the existing Unix-ms integer.
|
|
154
184
|
// Auditors comparing rows against external SIEM events expect ISO
|
|
155
185
|
// with explicit Z; the framework's primary ms storage stays
|
|
@@ -806,10 +836,11 @@ function _defaultVerifyCheckpointSignature(checkpoint) {
|
|
|
806
836
|
* `lastPurgedRowHash` becomes the new chain origin.
|
|
807
837
|
*
|
|
808
838
|
* @opts
|
|
809
|
-
* confirm:
|
|
810
|
-
* archive:
|
|
811
|
-
* passphrase:
|
|
812
|
-
* verifySignature:
|
|
839
|
+
* confirm: true, // exact `true` required
|
|
840
|
+
* archive: string, // path to a verified archive bundle
|
|
841
|
+
* passphrase: Buffer|string, // bundle decryption passphrase
|
|
842
|
+
* verifySignature: function(checkpoint),// auditor pubkey override
|
|
843
|
+
* dualControlGrant: object, // required when audit_log is declared under b.db.declareRequireDualControl — from b.dualControl.consume({ action: "auditTools.purge" })
|
|
813
844
|
*
|
|
814
845
|
* @example
|
|
815
846
|
* var result = await b.auditTools.purge({
|
|
@@ -846,6 +877,34 @@ async function purge(opts) {
|
|
|
846
877
|
"purge: bundle kind is '" + v.kind + "', must be 'archive'");
|
|
847
878
|
}
|
|
848
879
|
|
|
880
|
+
// Dual-control gate. When audit_log is declared under
|
|
881
|
+
// b.db.declareRequireDualControl, the physical purge requires a
|
|
882
|
+
// consumed m-of-n grant — confirm:true alone is not enough. Mirrors
|
|
883
|
+
// b.db.eraseHard, and additionally binds the grant's action so a
|
|
884
|
+
// grant minted for a different operation can't be replayed here.
|
|
885
|
+
var dcGate = _resolveDualControlGate(opts);
|
|
886
|
+
if (dcGate) {
|
|
887
|
+
var grant = opts.dualControlGrant;
|
|
888
|
+
if (!grant) {
|
|
889
|
+
_emitPurgeDenied(dcGate, "no-grant");
|
|
890
|
+
throw new AuditToolsError("audit-tools/dual-control-required",
|
|
891
|
+
"purge: audit_log is under dual control (m=" + dcGate.m + ", n=" + dcGate.n +
|
|
892
|
+
"); pass opts.dualControlGrant from b.dualControl.consume({ action: \"" +
|
|
893
|
+
AUDIT_LOG_PURGE_ACTION + "\" }).");
|
|
894
|
+
}
|
|
895
|
+
if (grant.ready !== true) {
|
|
896
|
+
_emitPurgeDenied(dcGate, "grant-not-ready");
|
|
897
|
+
throw new AuditToolsError("audit-tools/dual-control-grant-not-ready",
|
|
898
|
+
"purge: opts.dualControlGrant.ready must be true (a consumed m-of-n grant)");
|
|
899
|
+
}
|
|
900
|
+
if (grant.action !== AUDIT_LOG_PURGE_ACTION) {
|
|
901
|
+
_emitPurgeDenied(dcGate, "grant-action-mismatch");
|
|
902
|
+
throw new AuditToolsError("audit-tools/dual-control-grant-mismatch",
|
|
903
|
+
"purge: dualControlGrant.action is '" + grant.action + "', must be '" +
|
|
904
|
+
AUDIT_LOG_PURGE_ACTION + "'");
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
849
908
|
// 2. Refuse if the archive doesn't start at the next purge point. Keeps
|
|
850
909
|
// the chain anchor monotonic — operators can't jump-purge a middle range.
|
|
851
910
|
var readAnchor = opts.readAnchor || _defaultReadPurgeAnchor;
|
|
@@ -881,6 +940,7 @@ async function purge(opts) {
|
|
|
881
940
|
lastPurgedCounter: Number(v.range.lastCounter),
|
|
882
941
|
lastPurgedRowHash: v.range.lastRowHash,
|
|
883
942
|
archiveBundleId: result.archiveBundleId,
|
|
943
|
+
dualControlConsumed: !!dcGate,
|
|
884
944
|
};
|
|
885
945
|
}
|
|
886
946
|
|
|
@@ -305,7 +305,7 @@ var FRAMEWORK_NAMESPACES = [
|
|
|
305
305
|
"sandbox", // b.sandbox (sandbox.run / sandbox.run.refused — operator-supplied transform isolation)
|
|
306
306
|
"safeurl", // b.safeUrl.parse (safeurl.idn_homograph.refused — UTS #39 mixed-script host-label refusal)
|
|
307
307
|
"http", // b.middleware.bodyParser (http.chunked.malformed.refused — RFC 9112 §7.1 chunked-decode failure with Connection: close) // RFC number in prose
|
|
308
|
-
"cryptofield", // b.cryptoField.eraseRow (cryptofield.vacuum.skipped —
|
|
308
|
+
"cryptofield", // b.cryptoField.eraseRow (cryptofield.vacuum.skipped — vacuum-after-erase signal when DB not initialized at erase time)
|
|
309
309
|
"acme", // b.acme (acme.account.registered / order.* / cert.issued / cert.renewed / cert.renew.skipped — RFC 8555 + RFC 9773 ARI workflow)
|
|
310
310
|
"cert", // b.cert (cert.account.generated / cert.issued / cert.renewed / cert.renew-failed / cert.challenge-cleanup — turnkey cert-manager lifecycle)
|
|
311
311
|
"tls", // b.router 0-RTT posture (tls.0rtt.refused / tls.0rtt.replayed) — RFC 8446 §8 anti-replay surface // RFC number in prose
|
|
@@ -1620,7 +1620,7 @@ async function assertSegregation(opts) {
|
|
|
1620
1620
|
return { ok: ok, missing: missing };
|
|
1621
1621
|
}
|
|
1622
1622
|
|
|
1623
|
-
// applyPosture —
|
|
1623
|
+
// applyPosture — cascade hook. b.compliance.set(posture)
|
|
1624
1624
|
// calls this to record the active posture so audit emissions can
|
|
1625
1625
|
// surface the regulatory regime in metadata where downstream tooling
|
|
1626
1626
|
// (forensic export, SIEM correlation) needs it. The chain itself is
|
|
@@ -577,7 +577,7 @@ function create(opts) {
|
|
|
577
577
|
"ciba.parseNotification: empty bearer or no expected token configured");
|
|
578
578
|
}
|
|
579
579
|
// Constant-time compare on the SHA3 hash of both tokens —
|
|
580
|
-
// matches the project-wide discipline
|
|
580
|
+
// matches the project-wide discipline. Both
|
|
581
581
|
// sides are fixed-width sha3-512 hex strings; timingSafeEqual
|
|
582
582
|
// adds explicit defense-in-depth over `!==` even though equal-
|
|
583
583
|
// length JS string compare is already broadly understood as
|
|
@@ -437,7 +437,7 @@ async function verify(proof, opts) {
|
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
// nonce — when caller supplies expected nonce, payload MUST match.
|
|
440
|
-
// Constant-time compare
|
|
440
|
+
// Constant-time compare: the nonce is a server-
|
|
441
441
|
// issued secret-shaped value matched against attacker-controlled
|
|
442
442
|
// payload bytes. RFC 9449 §8 mandates the value be unpredictable;
|
|
443
443
|
// a leaking compare reveals prefix bytes over many attempts. ath
|
|
@@ -179,7 +179,7 @@ function fromAssertion(opts) {
|
|
|
179
179
|
return FAL3;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
//
|
|
182
|
+
// FAL2 per NIST SP 800-63C-4 §5.2 requires "injection
|
|
183
183
|
// protection" on the back-channel: either the back-channel itself is
|
|
184
184
|
// encrypted-and-authenticated (mTLS / signed transport) OR the
|
|
185
185
|
// assertion is encrypted to the RP. A plain HTTP back-channel with
|
|
@@ -73,7 +73,7 @@ var REFUSE_STATUS = {
|
|
|
73
73
|
// FIDO MDS3 §3.1.4 — attestation-key compromise means the
|
|
74
74
|
// manufacturer's batch-signing key is suspect; every credential
|
|
75
75
|
// attested under that key MUST be refused. Pre-v0.9.2 this token
|
|
76
|
-
// was missing from the refuse-list
|
|
76
|
+
// was missing from the refuse-list.
|
|
77
77
|
ATTESTATION_KEY_COMPROMISE: 1,
|
|
78
78
|
};
|
|
79
79
|
|
|
@@ -362,7 +362,6 @@ function _verifyAndParseBlob(token) {
|
|
|
362
362
|
// cache; an attacker serving an ancient signed-but-expired BLOB
|
|
363
363
|
// could keep operators on a revoked-authenticator-list-frozen-at-X.
|
|
364
364
|
// Refuse at parse time so neither fetch nor cache lookup honors it.
|
|
365
|
-
// (Audit 2026-05-11.)
|
|
366
365
|
if (nextUpdate.getTime() < Date.now()) {
|
|
367
366
|
throw new FidoMds3Error("fido-mds3/blob-stale",
|
|
368
367
|
"BLOB payload nextUpdate \"" + payload.nextUpdate +
|
|
@@ -619,7 +618,7 @@ function verifyAuthenticator(blob, registrationInfo, vopts) {
|
|
|
619
618
|
}
|
|
620
619
|
var entry = lookupAaguid(blob, registrationInfo.aaguid);
|
|
621
620
|
if (!entry) {
|
|
622
|
-
// Fail-CLOSED default for unknown AAGUIDs
|
|
621
|
+
// Fail-CLOSED default for unknown AAGUIDs.
|
|
623
622
|
// Pre-v0.9.2 default was `ok: true, reason: "aaguid-not-in-blob"`
|
|
624
623
|
// — an attacker registering a credential with an AAGUID not in
|
|
625
624
|
// the BLOB (rogue authenticator, fake hardware) silently passed.
|
|
@@ -271,7 +271,7 @@ function _selectKey(keys, header, vopts) {
|
|
|
271
271
|
throw new AuthError("auth-jwt-external/no-matching-kid",
|
|
272
272
|
"no JWKS key matches header.kid='" + header.kid + "'");
|
|
273
273
|
}
|
|
274
|
-
// Refuse kid-less tokens by default
|
|
274
|
+
// Refuse kid-less tokens by default. JWKS
|
|
275
275
|
// rotation creates a window where the rotated-out key is still
|
|
276
276
|
// cached but the rotated-in key is already published; an
|
|
277
277
|
// attacker shipping a kid-less token gets the lone-key path
|
|
@@ -301,7 +301,7 @@ async function verifyExternal(token, opts) {
|
|
|
301
301
|
"audience", "issuer", "subject", "clockSkewMs",
|
|
302
302
|
// v0.9.4 — opt-out for the kid-less-token JWKS-of-one refusal
|
|
303
303
|
// (default refuses; non-conforming IdPs that emit kid-less tokens
|
|
304
|
-
// set this true).
|
|
304
|
+
// set this true).
|
|
305
305
|
"allowKidlessJwks",
|
|
306
306
|
], "auth.jwt.verifyExternal");
|
|
307
307
|
|
|
@@ -621,7 +621,7 @@ function create(opts) {
|
|
|
621
621
|
// constrained tokens (DPoP / mTLS) can opt out by NOT supplying
|
|
622
622
|
// a seen callback.
|
|
623
623
|
//
|
|
624
|
-
// Atomic check-and-insert
|
|
624
|
+
// Atomic check-and-insert — pre-v0.9.3 the
|
|
625
625
|
// check ran via `ropts.seen(token)` which was a check-then-act
|
|
626
626
|
// race: two concurrent refresh requests landed on the same
|
|
627
627
|
// event-loop tick could both see `seen === false` and both POST
|
|
@@ -648,7 +648,7 @@ function create(opts) {
|
|
|
648
648
|
// Spec contract: inserted===true → first sighting (OK);
|
|
649
649
|
// inserted===false → replay. v0.9.3 had this inverted, which
|
|
650
650
|
// broke every first refresh attempt for operators reusing an
|
|
651
|
-
// existing b.nonceStore-style backend.
|
|
651
|
+
// existing b.nonceStore-style backend.
|
|
652
652
|
alreadySeen = inserted === false;
|
|
653
653
|
} else if (typeof ropts.seen === "function") {
|
|
654
654
|
// Legacy non-atomic path. Documented as a check-then-act race;
|
|
@@ -773,7 +773,7 @@ function create(opts) {
|
|
|
773
773
|
// Constant-time compare on the CSRF state token. Project
|
|
774
774
|
// discipline (auth/dpop.js, mail-srs.js, webhook.js) is
|
|
775
775
|
// timingSafeEqual for any secret-shaped value compared
|
|
776
|
-
// against attacker-controlled input.
|
|
776
|
+
// against attacker-controlled input.
|
|
777
777
|
if (typeof query.state !== "string" ||
|
|
778
778
|
!cryptoTimingSafeEqual(query.state, popts.expectedState)) {
|
|
779
779
|
throw new OAuthError("auth-oauth/state-mismatch",
|
|
@@ -971,7 +971,7 @@ function create(opts) {
|
|
|
971
971
|
// surface as `["admin", "read"]` and the operator's scope
|
|
972
972
|
// allowlist saw two distinct scopes. Spec-strict split on
|
|
973
973
|
// single-space + reject scope tokens that contain non-token
|
|
974
|
-
// chars.
|
|
974
|
+
// chars.
|
|
975
975
|
scope: raw.scope ? raw.scope.split(" ").filter(function (s) { return s.length > 0; }) : scope.slice(),
|
|
976
976
|
raw: raw,
|
|
977
977
|
};
|
|
@@ -1052,7 +1052,7 @@ function create(opts) {
|
|
|
1052
1052
|
// verifier in the framework (jwt.js, jwt-external.js, dpop.js)
|
|
1053
1053
|
// refuses; verifyIdToken previously silently ignored, letting an
|
|
1054
1054
|
// attacker-controlled OP ship critical extensions the verifier
|
|
1055
|
-
// doesn't understand.
|
|
1055
|
+
// doesn't understand.
|
|
1056
1056
|
if (header.crit !== undefined && header.crit !== null) {
|
|
1057
1057
|
throw new OAuthError("auth-oauth/crit-not-supported",
|
|
1058
1058
|
"ID token JWS header carries 'crit' extension list; this verifier does not " +
|
|
@@ -1072,7 +1072,7 @@ function create(opts) {
|
|
|
1072
1072
|
// key was still cached at the IdP but the rotated-in key is
|
|
1073
1073
|
// already published. Refuse kid-less tokens unconditionally —
|
|
1074
1074
|
// every modern IdP includes kid; absent kid is a spec smell.
|
|
1075
|
-
//
|
|
1075
|
+
// Operators with non-conforming IdPs that
|
|
1076
1076
|
// genuinely emit kid-less tokens can opt out via
|
|
1077
1077
|
// vopts.allowKidlessJwks = true with a logged warning.
|
|
1078
1078
|
if (!match) {
|
|
@@ -1117,7 +1117,7 @@ function create(opts) {
|
|
|
1117
1117
|
// ES256 signature attempted against an RS256 key returned by a
|
|
1118
1118
|
// hostile or buggy IdP with duplicate kids). Wrap so the panic
|
|
1119
1119
|
// becomes a typed AuthError, matching the discipline in
|
|
1120
|
-
// jwt-external.js + dpop.js.
|
|
1120
|
+
// jwt-external.js + dpop.js.
|
|
1121
1121
|
var verified;
|
|
1122
1122
|
try {
|
|
1123
1123
|
verified = nodeCrypto.verify(params.hash, Buffer.from(signingInput, "ascii"), verifyOpts, sig);
|
|
@@ -1179,7 +1179,7 @@ function create(opts) {
|
|
|
1179
1179
|
}
|
|
1180
1180
|
if (vopts.nonce && !vopts.skipNonceCheck) {
|
|
1181
1181
|
// Constant-time nonce compare — secret-shaped value matched
|
|
1182
|
-
// against attacker-controlled payload.
|
|
1182
|
+
// against attacker-controlled payload.
|
|
1183
1183
|
if (typeof payload.nonce !== "string" ||
|
|
1184
1184
|
!cryptoTimingSafeEqual(payload.nonce, vopts.nonce)) {
|
|
1185
1185
|
throw new OAuthError("auth-oauth/nonce-mismatch",
|
|
@@ -1212,7 +1212,7 @@ function create(opts) {
|
|
|
1212
1212
|
// supplied; an operator typo could ship `http://` or
|
|
1213
1213
|
// `javascript:`. Route through the framework's URL gate before
|
|
1214
1214
|
// emitting so the URL is validated the same way as every other
|
|
1215
|
-
// operator-supplied OAuth URL
|
|
1215
|
+
// operator-supplied OAuth URL.
|
|
1216
1216
|
_validateUrl(uopts.postLogoutRedirectUri, allowHttp, "postLogoutRedirectUri");
|
|
1217
1217
|
params.set("post_logout_redirect_uri", uopts.postLogoutRedirectUri);
|
|
1218
1218
|
}
|