@blamejs/core 0.8.52 → 0.8.57

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/index.js +8 -0
  3. package/lib/audit.js +4 -0
  4. package/lib/auth/fido-mds3.js +624 -0
  5. package/lib/auth/passkey.js +214 -2
  6. package/lib/auth-bot-challenge.js +1 -1
  7. package/lib/credential-hash.js +2 -2
  8. package/lib/framework-error.js +55 -0
  9. package/lib/guard-cidr.js +2 -1
  10. package/lib/guard-jwt.js +2 -2
  11. package/lib/guard-oauth.js +2 -2
  12. package/lib/http-client-cache.js +916 -0
  13. package/lib/http-client.js +242 -0
  14. package/lib/mail-arf.js +343 -0
  15. package/lib/mail-auth.js +265 -40
  16. package/lib/mail-bimi.js +948 -33
  17. package/lib/mail-bounce.js +386 -4
  18. package/lib/mail-mdn.js +424 -0
  19. package/lib/mail-unsubscribe.js +265 -25
  20. package/lib/mail.js +403 -21
  21. package/lib/middleware/bearer-auth.js +1 -1
  22. package/lib/middleware/clear-site-data.js +122 -0
  23. package/lib/middleware/dpop.js +1 -1
  24. package/lib/middleware/index.js +9 -0
  25. package/lib/middleware/nel.js +214 -0
  26. package/lib/middleware/security-headers.js +56 -4
  27. package/lib/middleware/speculation-rules.js +323 -0
  28. package/lib/mime-parse.js +198 -0
  29. package/lib/network-dns.js +890 -27
  30. package/lib/network-tls.js +745 -0
  31. package/lib/object-store/sigv4.js +54 -0
  32. package/lib/public-suffix.js +414 -0
  33. package/lib/safe-buffer.js +7 -0
  34. package/lib/safe-json.js +1 -1
  35. package/lib/static.js +120 -0
  36. package/lib/storage.js +11 -0
  37. package/lib/vendor/MANIFEST.json +33 -0
  38. package/lib/vendor/bimi-trust-anchors.pem +33 -0
  39. package/lib/vendor/public-suffix-list.dat +16376 -0
  40. package/package.json +1 -1
  41. package/sbom.cyclonedx.json +6 -6
@@ -121,20 +121,55 @@ async function verifyRegistration(opts) {
121
121
  _requireString(opts.expectedOrigin, "expectedOrigin");
122
122
  _requireString(opts.expectedRPID, "expectedRPID");
123
123
 
124
- return await _vendor().verifyRegistrationResponse({
124
+ var rv = await _vendor().verifyRegistrationResponse({
125
125
  response: opts.response,
126
126
  expectedChallenge: opts.expectedChallenge,
127
127
  expectedOrigin: opts.expectedOrigin,
128
128
  expectedRPID: opts.expectedRPID,
129
129
  requireUserVerification: opts.requireUserVerification !== false,
130
130
  });
131
+ // WebAuthn L3 §6.1.3 — surface authenticator-data BE/BS flags as
132
+ // named fields. backupEligible (BE) signals the credential CAN be
133
+ // backed up to a cloud account; backupState (BS) signals it IS
134
+ // currently backed up. Operators key trust decisions on these
135
+ // (single-device passkey → require step-up; multi-device synced
136
+ // passkey → strong signal). The vendor parses authData and exposes
137
+ // credentialDeviceType ("singleDevice" | "multiDevice") and
138
+ // credentialBackedUp (boolean) on registrationInfo; we map them to
139
+ // the spec's flag names and add them to the top-level result so
140
+ // callers don't have to dig through registrationInfo.
141
+ if (rv && rv.registrationInfo) {
142
+ rv.backupEligible = rv.registrationInfo.credentialDeviceType === "multiDevice";
143
+ rv.backupState = rv.registrationInfo.credentialBackedUp === true;
144
+ } else {
145
+ rv = rv || {};
146
+ rv.backupEligible = false;
147
+ rv.backupState = false;
148
+ }
149
+ return rv;
131
150
  }
132
151
 
133
152
  // ---- Authentication ----
134
153
 
154
+ // startAuthentication accepts an optional `mediation` token that the
155
+ // caller passes through verbatim to the browser as
156
+ // `navigator.credentials.get({ publicKey, mediation })`. The descriptor
157
+ // itself doesn't carry mediation — it's a separate argument on the
158
+ // page — but startAuthentication echoes it onto the returned options
159
+ // so the operator's transport (typically a JSON GET) carries it to
160
+ // the page without losing the value. Allowed tokens per the W3C
161
+ // Credential Management spec: "silent" / "optional" / "required" /
162
+ // "conditional". "conditional" enables passkey autofill on
163
+ // <input autocomplete="webauthn">.
164
+ var ALLOWED_MEDIATION = { silent: 1, optional: 1, required: 1, conditional: 1 };
165
+
135
166
  async function startAuthentication(opts) {
136
167
  if (!opts) throw new AuthError("auth-passkey/missing-opts", "opts is required");
137
168
  _requireString(opts.rpId, "rpId");
169
+ if (opts.mediation !== undefined && !ALLOWED_MEDIATION[opts.mediation]) {
170
+ throw new AuthError("auth-passkey/bad-mediation",
171
+ "mediation must be one of silent/optional/required/conditional");
172
+ }
138
173
 
139
174
  var options = await _vendor().generateAuthenticationOptions({
140
175
  rpID: opts.rpId,
@@ -148,9 +183,169 @@ async function startAuthentication(opts) {
148
183
  } else {
149
184
  options.hints = opts.hints;
150
185
  }
186
+ if (opts.mediation !== undefined) {
187
+ options.mediation = opts.mediation;
188
+ }
189
+ return options;
190
+ }
191
+
192
+ // conditionalAuthOptions — convenience wrapper for the passkey-autofill
193
+ // flow (mediation: "conditional"). Browsers require an empty
194
+ // allowCredentials list, presence-only userVerification (so the
195
+ // autofill chip can surface without forcing biometric), and a present
196
+ // challenge. Returns an object shaped for
197
+ // `navigator.credentials.get({ publicKey: <opts>, mediation: "conditional" })`.
198
+ async function conditionalAuthOptions(opts) {
199
+ if (!opts) throw new AuthError("auth-passkey/missing-opts", "opts is required");
200
+ _requireString(opts.rpId, "rpId");
201
+
202
+ var options = await _vendor().generateAuthenticationOptions({
203
+ rpID: opts.rpId,
204
+ // For conditional UI the spec mandates an empty allowCredentials
205
+ // list — discoverable credentials only. Supplying a list here
206
+ // suppresses the autofill chip in current browsers.
207
+ allowCredentials: [],
208
+ userVerification: opts.userVerification || "preferred",
209
+ timeout: opts.timeout,
210
+ extensions: opts.extensions,
211
+ });
212
+ options.mediation = "conditional";
213
+ if (!opts.hints) {
214
+ options.hints = ["client-device", "hybrid"];
215
+ } else {
216
+ options.hints = opts.hints;
217
+ }
151
218
  return options;
152
219
  }
153
220
 
221
+ // ---- WebAuthn L3 extension helpers (PRF / largeBlob / credBlob) ----
222
+ //
223
+ // Pre-compute the spec-correct shape so callers don't have to remember
224
+ // (a) what the field is called this year, (b) which inputs travel as
225
+ // base64url vs Uint8Array, (c) which support the {support:"required"}
226
+ // contract. Validation tier: throw at config-time. Misuse here is a
227
+ // coding bug, not a request-shape thing.
228
+
229
+ function _b64urlExtInput(value, name) {
230
+ // Accept a base64url string OR a Buffer / Uint8Array. Normalize the
231
+ // wire shape to base64url (the JSON descriptor ships base64url; the
232
+ // browser turns it into an ArrayBuffer before passing to the
233
+ // authenticator).
234
+ if (typeof value === "string") {
235
+ if (value.length === 0 || !safeBuffer.BASE64URL_RE.test(value)) {
236
+ throw new AuthError("auth-passkey/bad-extension-input",
237
+ name + " must be base64url (no padding) when string");
238
+ }
239
+ return value;
240
+ }
241
+ if (Buffer.isBuffer(value)) {
242
+ return value.toString("base64url");
243
+ }
244
+ if (value instanceof Uint8Array) {
245
+ return Buffer.from(value).toString("base64url");
246
+ }
247
+ throw new AuthError("auth-passkey/bad-extension-input",
248
+ name + " must be base64url string, Buffer, or Uint8Array");
249
+ }
250
+
251
+ // PRF (Pseudo-Random Function) extension — WebAuthn L3 §10.1.2.
252
+ // Authenticator-bound HKDF source. eval inputs are 32-byte salts; the
253
+ // authenticator returns deterministic 32-byte outputs the operator
254
+ // uses as a key-encryption key (vault unlock, file-encryption seed).
255
+ // Shape: `{ prf: { eval: { first, second? } } }` per extension-id "prf".
256
+ function _prfExt(args) {
257
+ if (!args || !args.eval) {
258
+ throw new AuthError("auth-passkey/missing-eval",
259
+ "extensions.prf({ eval: { first, second? } }) is required");
260
+ }
261
+ if (args.eval.first === undefined || args.eval.first === null) {
262
+ throw new AuthError("auth-passkey/missing-prf-first",
263
+ "extensions.prf eval.first is required");
264
+ }
265
+ var out = { prf: { eval: { first: _b64urlExtInput(args.eval.first, "eval.first") } } };
266
+ if (args.eval.second !== undefined && args.eval.second !== null) {
267
+ out.prf.eval.second = _b64urlExtInput(args.eval.second, "eval.second");
268
+ }
269
+ return out;
270
+ }
271
+
272
+ // largeBlob extension — WebAuthn L3 §10.3.
273
+ // Per-credential opaque blob storage. At registration the operator
274
+ // asks for support: "preferred" | "required". At auth time the
275
+ // operator asks to read OR write, never both in the same assertion.
276
+ function _largeBlobExt(args) {
277
+ if (!args) {
278
+ throw new AuthError("auth-passkey/missing-largeblob",
279
+ "extensions.largeBlob({ support? | read? | write? }) is required");
280
+ }
281
+ var out = { largeBlob: {} };
282
+ var SUPPORT = { preferred: 1, required: 1 };
283
+ var modes = 0;
284
+ if (args.support !== undefined) {
285
+ if (!SUPPORT[args.support]) {
286
+ throw new AuthError("auth-passkey/bad-largeblob-support",
287
+ "extensions.largeBlob support must be 'preferred' or 'required'");
288
+ }
289
+ out.largeBlob.support = args.support;
290
+ modes++;
291
+ }
292
+ if (args.read === true) {
293
+ out.largeBlob.read = true;
294
+ modes++;
295
+ } else if (args.read !== undefined && args.read !== false) {
296
+ throw new AuthError("auth-passkey/bad-largeblob-read",
297
+ "extensions.largeBlob read must be a boolean");
298
+ }
299
+ if (args.write !== undefined && args.write !== null) {
300
+ if (!Buffer.isBuffer(args.write) && !(args.write instanceof Uint8Array)) {
301
+ throw new AuthError("auth-passkey/bad-largeblob-write",
302
+ "extensions.largeBlob write must be a Uint8Array / Buffer");
303
+ }
304
+ out.largeBlob.write = Buffer.from(args.write).toString("base64url");
305
+ modes++;
306
+ }
307
+ if (modes === 0) {
308
+ throw new AuthError("auth-passkey/empty-largeblob",
309
+ "extensions.largeBlob({}) needs support, read, or write");
310
+ }
311
+ if (args.read === true && args.write !== undefined && args.write !== null) {
312
+ throw new AuthError("auth-passkey/conflicting-largeblob",
313
+ "extensions.largeBlob — read and write are mutually exclusive");
314
+ }
315
+ return out;
316
+ }
317
+
318
+ // credBlob extension — WebAuthn L3 §10.5.
319
+ // Server-supplied opaque blob (≤32 bytes per CTAP2.1) bound to the
320
+ // credential at registration. Returned in subsequent assertions.
321
+ // Shape: `{ credBlob: <base64url> }`.
322
+ function _credBlobExt(args) {
323
+ if (!args || args.blob === undefined || args.blob === null) {
324
+ throw new AuthError("auth-passkey/missing-credblob",
325
+ "extensions.credBlob({ blob }) is required");
326
+ }
327
+ var buf;
328
+ if (Buffer.isBuffer(args.blob)) {
329
+ buf = args.blob;
330
+ } else if (args.blob instanceof Uint8Array) {
331
+ buf = Buffer.from(args.blob);
332
+ } else {
333
+ throw new AuthError("auth-passkey/bad-credblob",
334
+ "extensions.credBlob blob must be a Uint8Array / Buffer");
335
+ }
336
+ if (buf.length === 0 || buf.length > 32) { // allow:raw-byte-literal — CTAP2.1 §11.1 credBlob max
337
+ throw new AuthError("auth-passkey/credblob-bad-length",
338
+ "extensions.credBlob blob must be 1-32 bytes (CTAP2.1 §11.1)");
339
+ }
340
+ return { credBlob: buf.toString("base64url") };
341
+ }
342
+
343
+ var extensions = {
344
+ prf: _prfExt,
345
+ largeBlob: _largeBlobExt,
346
+ credBlob: _credBlobExt,
347
+ };
348
+
154
349
  async function verifyAuthentication(opts) {
155
350
  if (!opts) throw new AuthError("auth-passkey/missing-opts", "opts is required");
156
351
  if (!opts.response) {
@@ -164,7 +359,7 @@ async function verifyAuthentication(opts) {
164
359
  "opts.credential { id, publicKey, counter? } is required");
165
360
  }
166
361
 
167
- return await _vendor().verifyAuthenticationResponse({
362
+ var rv = await _vendor().verifyAuthenticationResponse({
168
363
  response: opts.response,
169
364
  expectedChallenge: opts.expectedChallenge,
170
365
  expectedOrigin: opts.expectedOrigin,
@@ -177,6 +372,21 @@ async function verifyAuthentication(opts) {
177
372
  },
178
373
  requireUserVerification: opts.requireUserVerification !== false,
179
374
  });
375
+ // WebAuthn L3 §6.1.3 — same BE/BS surfacing as verifyRegistration.
376
+ // Authentication assertions also carry the BE/BS bits in authData; a
377
+ // credential that registered as single-device but later asserts as
378
+ // multi-device (or vice versa) is a backup-state-changed signal worth
379
+ // auditing at the operator level. We expose the current values so the
380
+ // caller can compare against what they persisted at registration.
381
+ if (rv && rv.authenticationInfo) {
382
+ rv.backupEligible = rv.authenticationInfo.credentialDeviceType === "multiDevice";
383
+ rv.backupState = rv.authenticationInfo.credentialBackedUp === true;
384
+ } else {
385
+ rv = rv || {};
386
+ rv.backupEligible = false;
387
+ rv.backupState = false;
388
+ }
389
+ return rv;
180
390
  }
181
391
 
182
392
  // ---- WebAuthn Signal API (W3C draft, 2024) ----
@@ -265,6 +475,8 @@ module.exports = {
265
475
  verifyRegistration: verifyRegistration,
266
476
  startAuthentication: startAuthentication,
267
477
  verifyAuthentication: verifyAuthentication,
478
+ conditionalAuthOptions: conditionalAuthOptions,
479
+ extensions: extensions,
268
480
  signalUnknownCredential: signalUnknownCredential,
269
481
  signalAllAcceptedCredentials: signalAllAcceptedCredentials,
270
482
  signalCurrentUserDetails: signalCurrentUserDetails,
@@ -136,7 +136,7 @@ function _defaultKeyExtractor(req) {
136
136
  * @signature b.authBotChallenge.create(opts)
137
137
  * @since 0.8.48
138
138
  * @status stable
139
- * @related b.middleware.botGuard, b.auth.lockout, b.auth.atoKillSwitch
139
+ * @related b.middleware.botGuard
140
140
  *
141
141
  * Build an adaptive bot-challenge gate. Returns
142
142
  * `{ middleware, recordFailure, recordSuccess, check, reset }`.
@@ -173,7 +173,7 @@ function _decodeEnvelope(env) {
173
173
  * @since 0.2.28
174
174
  * @status stable
175
175
  * @compliance pci-dss, soc2, hipaa
176
- * @related b.credentialHash.verify, b.credentialHash.needsRehash, b.auth.password.hash
176
+ * @related b.credentialHash.verify, b.credentialHash.needsRehash
177
177
  *
178
178
  * Hash a credential secret into a base64 envelope ready for storage in
179
179
  * a `credentialHash` column. Default algorithm is SHAKE256 with a
@@ -345,7 +345,7 @@ function inspect(envelope) {
345
345
  * @signature b.credentialHash.needsRehash(envelope, opts?)
346
346
  * @since 0.2.28
347
347
  * @status stable
348
- * @related b.credentialHash.hash, b.credentialHash.verify, b.auth.password.needsRehash
348
+ * @related b.credentialHash.hash, b.credentialHash.verify
349
349
  *
350
350
  * Returns `true` when the stored envelope was produced under an
351
351
  * algorithm or parameter set that no longer matches the framework
@@ -378,6 +378,19 @@ var SmtpPolicyError = defineClass("SmtpPolicyError", { alwaysPermane
378
378
  // record shape, fetch failures, missing keys, alignment issues.
379
379
  // Permanent — DNS-config / message-shape errors, not transient.
380
380
  var MailAuthError = defineClass("MailAuthError", { alwaysPermanent: true });
381
+ // MailArfError covers RFC 5965 Abuse Reporting Format ingest failures:
382
+ // missing required Feedback-Type / User-Agent fields, malformed
383
+ // multipart/report, message/feedback-report MIME-type mismatch, parse
384
+ // errors. Permanent — the report shape is operator-supplied input.
385
+ var MailArfError = defineClass("MailArfError", { alwaysPermanent: true });
386
+ // MailBimiError covers RFC 9091 BIMI VMC / CMC chain validation
387
+ // + Tiny-PS SVG profile violations: VMC fetch failures, X.509 chain
388
+ // validation failures, subjectAltName URI / BIMI domain mismatch,
389
+ // missing BIMI policy OID (1.3.6.1.5.5.7.3.31 mark verification),
390
+ // Tiny-PS SVG profile violations (root, version, baseProfile, scripts,
391
+ // external refs, viewBox, byte cap). Permanent — every case is a
392
+ // brand / certificate / asset shape error.
393
+ var MailBimiError = defineClass("MailBimiError", { alwaysPermanent: true });
381
394
  // SseError covers Server-Sent Events stream-shape violations: newline
382
395
  // or CR or NUL injection in event:/id:/data: fields (CVE-2026-33128
383
396
  // h3, CVE-2026-29085 Hono, CVE-2026-44217 sse-channel — newline in
@@ -553,10 +566,47 @@ var DaemonError = defineClass("DaemonError", { alwaysPermane
553
566
  // framework refuses to coerce. Operators wrap the call in their own
554
567
  // retry policy when polling against a flaky CDN.
555
568
  var SelfUpdateError = defineClass("SelfUpdateError", { alwaysPermanent: true });
569
+ // MailUnsubscribeError — b.mail.unsubscribe (lib/mail-unsubscribe.js).
570
+ // RFC 8058 / RFC 2369 / RFC 2919 List-* header builder violations:
571
+ // non-https URL in url/help/archive, non-mailto in mailto/owner,
572
+ // invalid list-id shape per RFC 2919 §3, control bytes / over-length
573
+ // header values. alwaysPermanent — every case is operator-misconfig
574
+ // at config-time the framework refuses to coerce.
575
+ var MailUnsubscribeError = defineClass("MailUnsubscribeError", { alwaysPermanent: true });
576
+ // FidoMds3Error — b.auth.fidoMds3 (lib/auth/fido-mds3.js). FIDO MDS3
577
+ // metadata BLOB verification + AAGUID lookup violations: BLOB fetch
578
+ // failure (non-2xx, oversize, network), JWS shape mismatch, certificate
579
+ // chain validation failure against the FIDO Alliance MDS3 root,
580
+ // signature verification failure, payload schema violation
581
+ // (missing entries / nextUpdate / no), nextUpdate parse failure,
582
+ // AAGUID lookup against an authenticator carrying a REVOKED /
583
+ // USER_KEY_PHYSICAL_COMPROMISE / USER_KEY_REMOTE_COMPROMISE status
584
+ // report. alwaysPermanent — every case is configuration / network /
585
+ // signing-shape errors that retry alone won't recover.
586
+ var FidoMds3Error = defineClass("FidoMds3Error", { alwaysPermanent: true });
587
+ // PublicSuffixError — b.publicSuffix (lib/public-suffix.js). Bad
588
+ // domain input at lookup time (non-string, empty, overlong, control-
589
+ // byte-bearing, IDN-normalization failure) and missing-vendored-data
590
+ // at module-init are both alwaysPermanent — every case is operator-
591
+ // shaped (caller passed garbage) or packaging-shaped (vendored .dat
592
+ // missing). Codes: `public-suffix/invalid-domain`,
593
+ // `public-suffix/not-loaded`.
594
+ var PublicSuffixError = defineClass("PublicSuffixError", { alwaysPermanent: true });
595
+ // MailMdnError — b.mailMdn (lib/mail-mdn.js). RFC 3798 / RFC 8098
596
+ // Message Disposition Notification builder + parser violations: bad
597
+ // opts at build/parse, malformed multipart/report shape, missing
598
+ // required fields (Original-Recipient / Final-Recipient / Disposition),
599
+ // disposition / action-mode / sending-mode token allowlist drift,
600
+ // auto-generation refusal when the inbound message demanded user
601
+ // confirmation (RFC 3798 §2.1) and the operator did not opt in.
602
+ // alwaysPermanent — every case is operator-shape or message-shape
603
+ // errors that retry will not recover.
604
+ var MailMdnError = defineClass("MailMdnError", { alwaysPermanent: true });
556
605
 
557
606
  module.exports = {
558
607
  FrameworkError: FrameworkError,
559
608
  defineClass: defineClass,
609
+ MailUnsubscribeError: MailUnsubscribeError,
560
610
  ObjectStoreError: ObjectStoreError,
561
611
  LogStreamError: LogStreamError,
562
612
  QueueError: QueueError,
@@ -613,6 +663,8 @@ module.exports = {
613
663
  ComplianceError: ComplianceError,
614
664
  SmtpPolicyError: SmtpPolicyError,
615
665
  MailAuthError: MailAuthError,
666
+ MailArfError: MailArfError,
667
+ MailBimiError: MailBimiError,
616
668
  SseError: SseError,
617
669
  McpError: McpError,
618
670
  AiInputError: AiInputError,
@@ -641,4 +693,7 @@ module.exports = {
641
693
  ArgParserError: ArgParserError,
642
694
  DaemonError: DaemonError,
643
695
  SelfUpdateError: SelfUpdateError,
696
+ FidoMds3Error: FidoMds3Error,
697
+ PublicSuffixError: PublicSuffixError,
698
+ MailMdnError: MailMdnError,
644
699
  };
package/lib/guard-cidr.js CHANGED
@@ -48,6 +48,7 @@ var lazyRequire = require("./lazy-require");
48
48
  var gateContract = require("./gate-contract");
49
49
  var C = require("./constants");
50
50
  var numericBounds = require("./numeric-bounds");
51
+ var safeBuffer = require("./safe-buffer");
51
52
  var { GuardCidrError } = require("./framework-error");
52
53
 
53
54
  var observability = lazyRequire(function () { return require("./observability"); });
@@ -202,7 +203,7 @@ function _parseIpv6(s) {
202
203
  var out = [];
203
204
  for (var i = 0; i < parts.length; i += 1) {
204
205
  var p = parts[i];
205
- if (!/^[0-9a-fA-F]{1,4}$/.test(p)) return null;
206
+ if (!safeBuffer.IPV6_HEXTET_RE.test(p)) return null;
206
207
  out.push(p.toLowerCase());
207
208
  }
208
209
  return out;
package/lib/guard-jwt.js CHANGED
@@ -437,7 +437,7 @@ function _detectIssues(input, opts) {
437
437
  * @since 0.7.49
438
438
  * @status stable
439
439
  * @compliance hipaa, pci-dss, gdpr, soc2
440
- * @related b.guardJwt.sanitize, b.guardJwt.gate, b.auth.jwt.verifyExternal
440
+ * @related b.guardJwt.sanitize, b.guardJwt.gate
441
441
  *
442
442
  * Apply the full guard-jwt threat catalog to a JWT compact-
443
443
  * serialization string. Returns `{ ok, issues, refusal? }` per
@@ -697,7 +697,7 @@ var loadRulePack = _jwtRulePacks.load;
697
697
  * @since 0.7.49
698
698
  * @status stable
699
699
  * @compliance hipaa, pci-dss, gdpr, soc2
700
- * @related b.guardJwt.validate, b.auth.jwt.verifyExternal
700
+ * @related b.guardJwt.validate
701
701
  *
702
702
  * Throw on any `kid` value that contains path-traversal indicators
703
703
  * (`..`, `/`, `\`, percent-encoded variants) or non-printable
@@ -349,7 +349,7 @@ function _detectIssues(flow, opts) {
349
349
  * @since 0.7.49
350
350
  * @status stable
351
351
  * @compliance hipaa, pci-dss, gdpr, soc2
352
- * @related b.guardOauth.sanitize, b.guardOauth.gate, b.auth.oauth
352
+ * @related b.guardOauth.sanitize, b.guardOauth.gate
353
353
  *
354
354
  * Apply the full guard-oauth threat catalog to a flow bundle.
355
355
  * Returns `{ ok, issues, refusal? }` per
@@ -461,7 +461,7 @@ function sanitize(input, opts) {
461
461
  * @since 0.7.49
462
462
  * @status stable
463
463
  * @compliance hipaa, pci-dss, gdpr, soc2
464
- * @related b.guardOauth.validate, b.guardOauth.sanitize, b.auth.oauth
464
+ * @related b.guardOauth.validate, b.guardOauth.sanitize
465
465
  *
466
466
  * Build a `gateContract.buildGuardGate`-shaped gate that pulls
467
467
  * `ctx.oauthFlow` (or `ctx.flow`) and dispatches to `validate`.