@blamejs/core 0.8.43 → 0.8.50
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 +93 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
package/lib/guard-jwt.js
CHANGED
|
@@ -1,36 +1,74 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.guardJwt
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Jwt
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* JWT identifier-safety guard — validates user-supplied JWT
|
|
9
|
+
* compact-serialization strings against the canonical CVE-class
|
|
10
|
+
* refuse list BEFORE hand-off to a signature verifier. KIND is
|
|
11
|
+
* `identifier`; the gate consumes `ctx.identifier` (or
|
|
12
|
+
* `ctx.token` / `ctx.jwt`). Pair with `b.auth.jwt.verifyExternal`
|
|
13
|
+
* for cryptographic verification — this layer is the shape /
|
|
14
|
+
* header / claims contract that runs before any HMAC or signature
|
|
15
|
+
* work.
|
|
8
16
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* unexpected alg.
|
|
18
|
-
* - kid path traversal — kid header used by some operators to
|
|
19
|
-
* resolve key files; `..` / `/` / null-byte in kid would escape
|
|
20
|
-
* the keystore directory.
|
|
21
|
-
* - typ confusion — typ != "jwt" / "JWT" / "JWS" indicates a non-
|
|
22
|
-
* JWT token coerced into the slot.
|
|
23
|
-
* - Oversized header / payload / signature — defense against
|
|
24
|
-
* decompression bombs and parser DoS.
|
|
25
|
-
* - exp / nbf / iat sanity — exp in the past, nbf in the far
|
|
26
|
-
* future, iat way in the future all indicate replay or clock-
|
|
27
|
-
* skew issues.
|
|
28
|
-
* - Unknown crit fields — RFC 7515 §4.1.11 — operator MUST refuse
|
|
29
|
-
* tokens carrying crit headers it doesn't understand.
|
|
30
|
-
* - BIDI / null / control / zero-width universal refuse.
|
|
17
|
+
* Algorithm-confusion defense: `alg=none` is universally refused
|
|
18
|
+
* at every profile (RFC 7518 §3.6 explicit-no-signature, the
|
|
19
|
+
* canonical CVE-2015-9235 jsonwebtoken / CVE-2018-0114 java-jwt
|
|
20
|
+
* class). The operator-supplied `allowedAlgs` allowlist defaults
|
|
21
|
+
* to the framework's PQC-first set (ML-DSA-87 / ML-DSA-65 /
|
|
22
|
+
* ML-DSA-44 / SLH-DSA-SHAKE-256{f,s} / SLH-DSA-SHA2-256{f,s} /
|
|
23
|
+
* EdDSA / ES* / RS* / PS*) so HS256-against-RSA-public-key
|
|
24
|
+
* forgery is blocked before the verifier sees the token.
|
|
31
25
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
26
|
+
* `kid` path-traversal defense: the gate refuses any header `kid`
|
|
27
|
+
* that contains `..`, `/`, `\`, or percent-encoded variants —
|
|
28
|
+
* operators that resolve `kid` to a filesystem path can't escape
|
|
29
|
+
* the keystore directory. The standalone `b.guardJwt.kidSafe(kid)`
|
|
30
|
+
* helper throws on the same indicators and is the contract every
|
|
31
|
+
* `keyResolver` implementation must enforce before reading a key
|
|
32
|
+
* file.
|
|
33
|
+
*
|
|
34
|
+
* Bounded shape: header / payload / signature segments each have
|
|
35
|
+
* their own byte cap (`maxHeaderBytes` / `maxPayloadBytes` /
|
|
36
|
+
* `maxSignatureBytes`) and the total token is bounded by
|
|
37
|
+
* `maxBytes`. Decompression-bomb-shaped tokens fail at the cap
|
|
38
|
+
* check before any base64url decode. Header JSON is parsed
|
|
39
|
+
* through `b.safeJson.parse({ rejectProto: true })` so prototype
|
|
40
|
+
* pollution can't ride a forged header.
|
|
41
|
+
*
|
|
42
|
+
* Claim sanity: `exp` in the past, `nbf` more than
|
|
43
|
+
* `nbfFutureSlackMs` in the future, and `iat` more than
|
|
44
|
+
* `iatFutureSlackMs` in the future all surface as issues —
|
|
45
|
+
* replay / clock-skew detection that doesn't require pulling in
|
|
46
|
+
* a verifier. Required-claims (`iss` / `exp` / `iat` at strict;
|
|
47
|
+
* `iss` / `exp` at balanced) are enforced before the verifier
|
|
48
|
+
* so missing-claim refusals fail fast.
|
|
49
|
+
*
|
|
50
|
+
* `typ` confusion: any `typ` outside `jwt` / `jws` / `at+jwt` /
|
|
51
|
+
* `id_token` flags as suspect — non-JWT tokens coerced into a
|
|
52
|
+
* JWT slot are refused under strict, audited under balanced.
|
|
53
|
+
*
|
|
54
|
+
* `crit` discipline: RFC 7515 §4.1.11 mandates refusing tokens
|
|
55
|
+
* that carry `crit` headers the verifier doesn't understand. The
|
|
56
|
+
* gate's `knownCrit` allowlist is empty by default — every
|
|
57
|
+
* `crit` field is unknown unless the operator opts a name in.
|
|
58
|
+
*
|
|
59
|
+
* Audience verification is the operator's responsibility (the
|
|
60
|
+
* verifier handles it); the guard's required-claims list ensures
|
|
61
|
+
* the operator can't forget to populate `aud` in their verifier
|
|
62
|
+
* config because the claim must be present at validate time.
|
|
63
|
+
*
|
|
64
|
+
* Profiles: `strict` / `balanced` / `permissive`. Compliance
|
|
65
|
+
* postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`. BIDI / null /
|
|
66
|
+
* control / zero-width universal-refuse applies on the raw input
|
|
67
|
+
* string at every profile so trojan-source codepoints can't ride
|
|
68
|
+
* inside a base64url segment.
|
|
69
|
+
*
|
|
70
|
+
* @card
|
|
71
|
+
* JWT identifier-safety guard — validates user-supplied JWT compact-serialization strings against the canonical CVE-class refuse list BEFORE hand-off to a signature verifier.
|
|
34
72
|
*/
|
|
35
73
|
|
|
36
74
|
var codepointClass = require("./codepoint-class");
|
|
@@ -393,6 +431,64 @@ function _detectIssues(input, opts) {
|
|
|
393
431
|
return issues;
|
|
394
432
|
}
|
|
395
433
|
|
|
434
|
+
/**
|
|
435
|
+
* @primitive b.guardJwt.validate
|
|
436
|
+
* @signature b.guardJwt.validate(input, opts?)
|
|
437
|
+
* @since 0.7.49
|
|
438
|
+
* @status stable
|
|
439
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
440
|
+
* @related b.guardJwt.sanitize, b.guardJwt.gate, b.auth.jwt.verifyExternal
|
|
441
|
+
*
|
|
442
|
+
* Apply the full guard-jwt threat catalog to a JWT compact-
|
|
443
|
+
* serialization string. Returns `{ ok, issues, refusal? }` per
|
|
444
|
+
* `gateContract.aggregateIssues`. Detected classes include
|
|
445
|
+
* `alg-none` (always critical), `kid-traversal` (always critical),
|
|
446
|
+
* `alg-not-allowed`, `typ-confusion`, `crit-unknown`, `exp-past`,
|
|
447
|
+
* `nbf-far-future`, `iat-far-future`, `claim-missing`, plus the
|
|
448
|
+
* shape (`jwt-shape`) / segment-cap (`header-cap` / `payload-cap`
|
|
449
|
+
* / `signature-cap`) / total-cap (`jwt-cap`) / codepoint-class
|
|
450
|
+
* issues. Header JSON is decoded through
|
|
451
|
+
* `b.safeJson.parse({ rejectProto: true })` so prototype-pollution
|
|
452
|
+
* keys are refused before any policy check runs. Operator-supplied
|
|
453
|
+
* opts are bounds-checked; bad opts throw
|
|
454
|
+
* `GuardJwtError("jwt.bad-opt")`.
|
|
455
|
+
*
|
|
456
|
+
* @opts
|
|
457
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
458
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
459
|
+
* allowedAlgs: string[],
|
|
460
|
+
* requiredClaims: string[],
|
|
461
|
+
* knownCrit: string[],
|
|
462
|
+
* algNonePolicy: "reject"|"audit"|"allow",
|
|
463
|
+
* algAllowlistPolicy: "reject"|"audit"|"allow",
|
|
464
|
+
* kidTraversalPolicy: "reject"|"audit"|"allow",
|
|
465
|
+
* typConfusionPolicy: "reject"|"audit"|"allow",
|
|
466
|
+
* expSanityPolicy: "reject"|"audit"|"allow",
|
|
467
|
+
* nbfSanityPolicy: "reject"|"audit"|"allow",
|
|
468
|
+
* iatSanityPolicy: "reject"|"audit"|"allow",
|
|
469
|
+
* critUnknownPolicy: "reject"|"audit"|"allow",
|
|
470
|
+
* nbfFutureSlackMs: number,
|
|
471
|
+
* iatFutureSlackMs: number,
|
|
472
|
+
* maxHeaderBytes: number,
|
|
473
|
+
* maxPayloadBytes: number,
|
|
474
|
+
* maxSignatureBytes: number,
|
|
475
|
+
* maxBytes: number,
|
|
476
|
+
*
|
|
477
|
+
* @example
|
|
478
|
+
* var algNoneToken =
|
|
479
|
+
* "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0." +
|
|
480
|
+
* "eyJzdWIiOiJhdHRhY2tlciJ9.";
|
|
481
|
+
* var rv = b.guardJwt.validate(algNoneToken, { profile: "strict" });
|
|
482
|
+
* rv.ok; // → false
|
|
483
|
+
* rv.issues[0].ruleId; // → "jwt.alg-none"
|
|
484
|
+
*
|
|
485
|
+
* var benign =
|
|
486
|
+
* "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9." +
|
|
487
|
+
* "eyJpc3MiOiJleGFtcGxlIiwiZXhwIjo5OTk5OTk5OTk5LCJpYXQiOjE3MDAwMDAwMDB9." +
|
|
488
|
+
* "sig";
|
|
489
|
+
* var ok = b.guardJwt.validate(benign, { profile: "strict" });
|
|
490
|
+
* ok.ok; // → true
|
|
491
|
+
*/
|
|
396
492
|
function validate(input, opts) {
|
|
397
493
|
opts = _resolveOpts(opts);
|
|
398
494
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -410,6 +506,36 @@ function validate(input, opts) {
|
|
|
410
506
|
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
411
507
|
}
|
|
412
508
|
|
|
509
|
+
/**
|
|
510
|
+
* @primitive b.guardJwt.sanitize
|
|
511
|
+
* @signature b.guardJwt.sanitize(input, opts?)
|
|
512
|
+
* @since 0.7.49
|
|
513
|
+
* @status stable
|
|
514
|
+
* @related b.guardJwt.validate, b.guardJwt.gate
|
|
515
|
+
*
|
|
516
|
+
* Pass-through-or-throw form of `validate`. JWT compact
|
|
517
|
+
* serialization can't be repaired (every byte feeds the signature)
|
|
518
|
+
* so sanitize either returns the input unchanged when the issue
|
|
519
|
+
* list contains no `critical` / `high` entries, or throws
|
|
520
|
+
* `GuardJwtError` carrying the offending `ruleId`. Use this when
|
|
521
|
+
* the caller wants a single try/catch boundary instead of an
|
|
522
|
+
* issue-list switch.
|
|
523
|
+
*
|
|
524
|
+
* @opts
|
|
525
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
526
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
527
|
+
* ...: every guardJwt.validate opt is honored,
|
|
528
|
+
*
|
|
529
|
+
* @example
|
|
530
|
+
* var algNoneToken =
|
|
531
|
+
* "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0." +
|
|
532
|
+
* "eyJzdWIiOiJhdHRhY2tlciJ9.";
|
|
533
|
+
* try {
|
|
534
|
+
* b.guardJwt.sanitize(algNoneToken, { profile: "strict" });
|
|
535
|
+
* } catch (e) {
|
|
536
|
+
* e.code; // → "jwt.alg-none"
|
|
537
|
+
* }
|
|
538
|
+
*/
|
|
413
539
|
function sanitize(input, opts) {
|
|
414
540
|
opts = _resolveOpts(opts);
|
|
415
541
|
if (typeof input !== "string") {
|
|
@@ -427,6 +553,39 @@ function sanitize(input, opts) {
|
|
|
427
553
|
return input;
|
|
428
554
|
}
|
|
429
555
|
|
|
556
|
+
/**
|
|
557
|
+
* @primitive b.guardJwt.gate
|
|
558
|
+
* @signature b.guardJwt.gate(opts?)
|
|
559
|
+
* @since 0.7.49
|
|
560
|
+
* @status stable
|
|
561
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
562
|
+
* @related b.guardJwt.validate, b.guardJwt.sanitize, b.middleware.bearerAuth
|
|
563
|
+
*
|
|
564
|
+
* Build a `gateContract.buildGuardGate`-shaped gate that pulls
|
|
565
|
+
* `ctx.identifier` (or `ctx.token` / `ctx.jwt`) and dispatches to
|
|
566
|
+
* `validate`. Returns `{ ok: true, action: "serve" }` when the
|
|
567
|
+
* issue list is empty, `{ ok: true, action: "audit-only", issues }`
|
|
568
|
+
* when only low-severity issues fire, and `{ ok: false, action:
|
|
569
|
+
* "refuse", issues }` on any `critical` / `high` issue. Compose
|
|
570
|
+
* into auth pipelines via `b.middleware.bearerAuth` so every
|
|
571
|
+
* bearer token is shape-checked before signature verification.
|
|
572
|
+
*
|
|
573
|
+
* @opts
|
|
574
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
575
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
576
|
+
* name: string, // gate label for audit trails
|
|
577
|
+
* ...: every guardJwt.validate opt is honored,
|
|
578
|
+
*
|
|
579
|
+
* @example
|
|
580
|
+
* var jwtGate = b.guardJwt.gate({ profile: "strict" });
|
|
581
|
+
* var rv = await jwtGate.run({
|
|
582
|
+
* identifier:
|
|
583
|
+
* "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0." +
|
|
584
|
+
* "eyJzdWIiOiJhdHRhY2tlciJ9.",
|
|
585
|
+
* });
|
|
586
|
+
* rv.action; // → "refuse"
|
|
587
|
+
* rv.issues[0].ruleId; // → "jwt.alg-none"
|
|
588
|
+
*/
|
|
430
589
|
function gate(opts) {
|
|
431
590
|
opts = _resolveOpts(opts);
|
|
432
591
|
return gateContract.buildGuardGate(
|
|
@@ -450,18 +609,113 @@ function gate(opts) {
|
|
|
450
609
|
});
|
|
451
610
|
}
|
|
452
611
|
|
|
612
|
+
/**
|
|
613
|
+
* @primitive b.guardJwt.buildProfile
|
|
614
|
+
* @signature b.guardJwt.buildProfile(opts)
|
|
615
|
+
* @since 0.7.49
|
|
616
|
+
* @status stable
|
|
617
|
+
* @related b.guardJwt.gate, b.guardJwt.compliancePosture
|
|
618
|
+
*
|
|
619
|
+
* Compose a derived profile from one or more named bases plus
|
|
620
|
+
* inline overrides. `opts.extends` is a profile name (`"strict"` /
|
|
621
|
+
* `"balanced"` / `"permissive"`) or an array of names; later
|
|
622
|
+
* entries shadow earlier ones, and inline `opts` keys win last.
|
|
623
|
+
* Operators stage profile overlays here so the final shape is
|
|
624
|
+
* traceable to a baseline rather than a hand-typed dictionary.
|
|
625
|
+
*
|
|
626
|
+
* @opts
|
|
627
|
+
* extends: string|string[], // base profile name(s) to compose
|
|
628
|
+
* ...: any guardJwt key, // inline override of resolved keys
|
|
629
|
+
*
|
|
630
|
+
* @example
|
|
631
|
+
* var custom = b.guardJwt.buildProfile({
|
|
632
|
+
* extends: "balanced",
|
|
633
|
+
* algAllowlistPolicy: "reject",
|
|
634
|
+
* allowedAlgs: ["ES256", "EdDSA"],
|
|
635
|
+
* });
|
|
636
|
+
* custom.algAllowlistPolicy; // → "reject"
|
|
637
|
+
* custom.allowedAlgs.indexOf("ES256"); // → 0
|
|
638
|
+
*/
|
|
453
639
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
454
640
|
|
|
641
|
+
/**
|
|
642
|
+
* @primitive b.guardJwt.compliancePosture
|
|
643
|
+
* @signature b.guardJwt.compliancePosture(name)
|
|
644
|
+
* @since 0.7.49
|
|
645
|
+
* @status stable
|
|
646
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
647
|
+
* @related b.guardJwt.gate, b.guardJwt.buildProfile
|
|
648
|
+
*
|
|
649
|
+
* Look up a compliance-posture overlay by name (`"hipaa"` /
|
|
650
|
+
* `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
|
|
651
|
+
* the posture object — the caller may mutate freely. Throws
|
|
652
|
+
* `GuardJwtError("jwt.bad-posture")` on unknown name. Postures
|
|
653
|
+
* extend the strict profile (or balanced for `gdpr`) with a
|
|
654
|
+
* `forensicSnippetBytes` cap appropriate to the regime.
|
|
655
|
+
*
|
|
656
|
+
* @example
|
|
657
|
+
* var posture = b.guardJwt.compliancePosture("hipaa");
|
|
658
|
+
* posture.algNonePolicy; // → "reject"
|
|
659
|
+
* posture.forensicSnippetBytes; // → 256
|
|
660
|
+
*/
|
|
455
661
|
function compliancePosture(name) {
|
|
456
662
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
|
|
457
663
|
_err, "jwt");
|
|
458
664
|
}
|
|
459
665
|
|
|
460
666
|
var _jwtRulePacks = gateContract.makeRulePackLoader(GuardJwtError, "jwt");
|
|
667
|
+
/**
|
|
668
|
+
* @primitive b.guardJwt.loadRulePack
|
|
669
|
+
* @signature b.guardJwt.loadRulePack(pack)
|
|
670
|
+
* @since 0.7.49
|
|
671
|
+
* @status stable
|
|
672
|
+
* @related b.guardJwt.gate
|
|
673
|
+
*
|
|
674
|
+
* Register an operator-supplied rule pack with the guard-jwt
|
|
675
|
+
* registry. The pack is identified by `pack.id` (non-empty
|
|
676
|
+
* string) and stored for later inspection / dispatch by gates
|
|
677
|
+
* that opt in via `opts.rulePackId`. Returns the pack object
|
|
678
|
+
* unchanged on success; throws `GuardJwtError("jwt.bad-opt")`
|
|
679
|
+
* when `pack` is missing or `pack.id` is not a non-empty string.
|
|
680
|
+
*
|
|
681
|
+
* @example
|
|
682
|
+
* var pack = b.guardJwt.loadRulePack({
|
|
683
|
+
* id: "tenant-issuer-pin",
|
|
684
|
+
* rules: [
|
|
685
|
+
* { id: "iss-pin", severity: "high",
|
|
686
|
+
* detect: function (claims) { return claims.iss !== "https://idp.example/"; },
|
|
687
|
+
* reason: "tenant pins iss to a single IdP" },
|
|
688
|
+
* ],
|
|
689
|
+
* });
|
|
690
|
+
* pack.id; // → "tenant-issuer-pin"
|
|
691
|
+
*/
|
|
461
692
|
var loadRulePack = _jwtRulePacks.load;
|
|
462
693
|
|
|
463
|
-
|
|
464
|
-
|
|
694
|
+
/**
|
|
695
|
+
* @primitive b.guardJwt.kidSafe
|
|
696
|
+
* @signature b.guardJwt.kidSafe(kid)
|
|
697
|
+
* @since 0.7.49
|
|
698
|
+
* @status stable
|
|
699
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
700
|
+
* @related b.guardJwt.validate, b.auth.jwt.verifyExternal
|
|
701
|
+
*
|
|
702
|
+
* Throw on any `kid` value that contains path-traversal indicators
|
|
703
|
+
* (`..`, `/`, `\`, percent-encoded variants) or non-printable
|
|
704
|
+
* control bytes. Returns the input unchanged on success. This is
|
|
705
|
+
* the contract every operator `keyResolver` MUST run before
|
|
706
|
+
* resolving `kid` to a filesystem path or KMS key handle —
|
|
707
|
+
* without it, a forged token's `kid` can escape the keystore
|
|
708
|
+
* directory.
|
|
709
|
+
*
|
|
710
|
+
* @example
|
|
711
|
+
* b.guardJwt.kidSafe("tenant-1-2026-05"); // → "tenant-1-2026-05"
|
|
712
|
+
*
|
|
713
|
+
* try {
|
|
714
|
+
* b.guardJwt.kidSafe("../../etc/passwd");
|
|
715
|
+
* } catch (e) {
|
|
716
|
+
* e.code; // → "jwt.kid-traversal"
|
|
717
|
+
* }
|
|
718
|
+
*/
|
|
465
719
|
function kidSafe(kid) {
|
|
466
720
|
if (typeof kid !== "string" || kid.length === 0) {
|
|
467
721
|
throw _err("jwt.kid-empty", "kid must be a non-empty string");
|
package/lib/guard-markdown.js
CHANGED
|
@@ -1,30 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.guardMarkdown
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Markdown
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* - CVE-2025-24981 — MDC Markdown XSS via unsanitized autolinks.
|
|
13
|
-
* - CVE-2026-33500 — AVideo Parsedown link XSS via inlineLink/inlineUrlTag
|
|
14
|
-
* bypass (ParsedownSafeWithLinks didn't override link emitters).
|
|
15
|
-
* - GHSA-gwjh-c548-f787 — NuGetGallery autolink XSS.
|
|
16
|
-
* - Joplin GHSA-hff8-hjwv-j9q7 — RCE via untrusted markdown link.
|
|
7
|
+
* @intro
|
|
8
|
+
* CommonMark validator + sanitizer for user-supplied markdown.
|
|
9
|
+
* Refuses raw HTML by default, applies a URL-scheme allowlist on
|
|
10
|
+
* inline links / autolinks / images / reference defs, and caps
|
|
11
|
+
* image dimensions and structural depth to defang renderer DoS.
|
|
12
|
+
* KIND="content" — the gate consumes `ctx.bytes` /
|
|
13
|
+
* `ctx.bodyText`.
|
|
17
14
|
*
|
|
18
|
-
*
|
|
19
|
-
* BEFORE any downstream renderer (marked / markdown-it /
|
|
20
|
-
* remark / parsedown) sees it. Source-level
|
|
21
|
-
* the most dangerous shapes —
|
|
22
|
-
*
|
|
23
|
-
* post-parse tree
|
|
15
|
+
* The primitive is a SOURCE-LEVEL gate: it inspects raw markdown
|
|
16
|
+
* text BEFORE any downstream renderer (marked / markdown-it /
|
|
17
|
+
* commonmark / remark / parsedown) sees it. Source-level
|
|
18
|
+
* discipline matters because the most dangerous shapes —
|
|
19
|
+
* `__proto__` in JSON, `<script\n>` in markdown — exploit
|
|
20
|
+
* specific parser internals; sanitizing on the post-parse tree
|
|
21
|
+
* is too late.
|
|
24
22
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* Threat catalog grounded in current CVE research:
|
|
24
|
+
* CVE-2026-30838 (CommonMark DisallowedRawHtml whitespace-tag
|
|
25
|
+
* bypass — `<script\n>` / `<script\t>` evades naive `<script>`
|
|
26
|
+
* matchers); CVE-2025-9540 (Markup Markdown stored XSS via
|
|
27
|
+
* `javascript:` link); CVE-2025-7969 (markdown-it ReDoS class);
|
|
28
|
+
* CVE-2025-6493 (CodeMirror Markdown Mode catastrophic
|
|
29
|
+
* backtracking); CVE-2025-24981 (MDC autolink XSS);
|
|
30
|
+
* CVE-2026-33500 (AVideo Parsedown inlineLink/inlineUrlTag
|
|
31
|
+
* bypass); GHSA-gwjh-c548-f787 (NuGetGallery autolink XSS);
|
|
32
|
+
* Joplin GHSA-hff8-hjwv-j9q7 (RCE via untrusted markdown link).
|
|
33
|
+
*
|
|
34
|
+
* Profiles: `strict` / `balanced` / `permissive`. Compliance
|
|
35
|
+
* postures: `hipaa` / `pci-dss` / `gdpr` / `soc2`.
|
|
36
|
+
*
|
|
37
|
+
* @card
|
|
38
|
+
* CommonMark validator + sanitizer for user-supplied markdown.
|
|
28
39
|
*/
|
|
29
40
|
|
|
30
41
|
var codepointClass = require("./codepoint-class");
|
|
@@ -477,6 +488,55 @@ function _detectIssues(input, opts) {
|
|
|
477
488
|
|
|
478
489
|
// ---- Public surface ----
|
|
479
490
|
|
|
491
|
+
/**
|
|
492
|
+
* @primitive b.guardMarkdown.validate
|
|
493
|
+
* @signature b.guardMarkdown.validate(input, opts?)
|
|
494
|
+
* @since 0.7.16
|
|
495
|
+
* @status stable
|
|
496
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
497
|
+
* @related b.guardMarkdown.sanitize, b.guardMarkdown.gate
|
|
498
|
+
*
|
|
499
|
+
* Inspect raw markdown source against the resolved profile and
|
|
500
|
+
* return `{ ok, issues }`. Each issue carries `kind` / `severity`
|
|
501
|
+
* (`critical` | `high` | `medium` | `low`) / `ruleId` / `snippet`.
|
|
502
|
+
* Non-string input returns a single `markdown.bad-input` issue
|
|
503
|
+
* rather than throwing — callers that prefer an exception use
|
|
504
|
+
* `b.guardMarkdown.sanitize`.
|
|
505
|
+
*
|
|
506
|
+
* @opts
|
|
507
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
508
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
509
|
+
* bidiPolicy: "reject"|"strip"|"audit"|"allow",
|
|
510
|
+
* controlPolicy: "reject"|"strip"|"allow",
|
|
511
|
+
* nullBytePolicy: "reject"|"strip"|"allow",
|
|
512
|
+
* zeroWidthPolicy: "reject"|"strip"|"allow",
|
|
513
|
+
* dangerousTagPolicy: "reject"|"strip"|"audit"|"allow",
|
|
514
|
+
* dangerousSchemePolicy: "reject"|"strip"|"audit"|"allow",
|
|
515
|
+
* imageSchemePolicy: "reject"|"strip"|"audit"|"allow",
|
|
516
|
+
* autolinkSchemePolicy: "reject"|"strip"|"audit"|"allow",
|
|
517
|
+
* referenceLinkPolicy: "reject"|"strip"|"audit"|"allow",
|
|
518
|
+
* codeFenceLangPolicy: "reject"|"strip"|"audit"|"allow",
|
|
519
|
+
* doctypePolicy: "reject"|"strip"|"audit"|"allow",
|
|
520
|
+
* schemeAllowlist: string[], // default ["http","https","mailto"]
|
|
521
|
+
* maxBytes: number,
|
|
522
|
+
* maxLines: number,
|
|
523
|
+
* maxLinks: number,
|
|
524
|
+
* maxImages: number,
|
|
525
|
+
* maxAutolinks: number,
|
|
526
|
+
* maxRefDefs: number,
|
|
527
|
+
* maxListDepth: number,
|
|
528
|
+
* maxBlockquoteDepth: number,
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* var rv = b.guardMarkdown.validate("# hello\n\n[link](https://example.com)",
|
|
532
|
+
* { profile: "strict" });
|
|
533
|
+
* rv.ok; // → true
|
|
534
|
+
*
|
|
535
|
+
* var bad = b.guardMarkdown.validate("[click](javascript:alert(1))",
|
|
536
|
+
* { profile: "strict" });
|
|
537
|
+
* bad.ok; // → false
|
|
538
|
+
* bad.issues[0].ruleId; // → "markdown.dangerous-scheme"
|
|
539
|
+
*/
|
|
480
540
|
function validate(input, opts) {
|
|
481
541
|
opts = _resolveOpts(opts);
|
|
482
542
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -493,6 +553,36 @@ function validate(input, opts) {
|
|
|
493
553
|
return gateContract.aggregateIssues(_detectIssues(input, opts));
|
|
494
554
|
}
|
|
495
555
|
|
|
556
|
+
/**
|
|
557
|
+
* @primitive b.guardMarkdown.sanitize
|
|
558
|
+
* @signature b.guardMarkdown.sanitize(input, opts?)
|
|
559
|
+
* @since 0.7.16
|
|
560
|
+
* @status stable
|
|
561
|
+
* @related b.guardMarkdown.validate, b.guardMarkdown.gate
|
|
562
|
+
*
|
|
563
|
+
* Strip BIDI / zero-width / control / null-byte codepoints under
|
|
564
|
+
* their resolved policies and return the cleaned markdown source.
|
|
565
|
+
* Throws `GuardMarkdownError` when any `critical` issue fires
|
|
566
|
+
* (raw `<script>`, `javascript:` link, doctype injection). Use
|
|
567
|
+
* `validate` to inspect issues without throwing.
|
|
568
|
+
*
|
|
569
|
+
* @opts
|
|
570
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
571
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
572
|
+
* ...: same shape as b.guardMarkdown.validate opts,
|
|
573
|
+
*
|
|
574
|
+
* @example
|
|
575
|
+
* var clean = b.guardMarkdown.sanitize("hello\u200Bworld",
|
|
576
|
+
* { profile: "balanced" });
|
|
577
|
+
* clean; // → "helloworld"
|
|
578
|
+
*
|
|
579
|
+
* try {
|
|
580
|
+
* b.guardMarkdown.sanitize("<script>alert(1)</script>",
|
|
581
|
+
* { profile: "strict" });
|
|
582
|
+
* } catch (e) {
|
|
583
|
+
* e.code; // → "markdown.dangerous-tag"
|
|
584
|
+
* }
|
|
585
|
+
*/
|
|
496
586
|
function sanitize(input, opts) {
|
|
497
587
|
opts = _resolveOpts(opts);
|
|
498
588
|
if (typeof input !== "string") {
|
|
@@ -508,6 +598,36 @@ function sanitize(input, opts) {
|
|
|
508
598
|
return codepointClass.applyCharStripPolicies(input, opts);
|
|
509
599
|
}
|
|
510
600
|
|
|
601
|
+
/**
|
|
602
|
+
* @primitive b.guardMarkdown.gate
|
|
603
|
+
* @signature b.guardMarkdown.gate(opts?)
|
|
604
|
+
* @since 0.7.16
|
|
605
|
+
* @status stable
|
|
606
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
607
|
+
* @related b.guardMarkdown.validate, b.guardMarkdown.sanitize, b.guardAll.gate, b.staticServe.create
|
|
608
|
+
*
|
|
609
|
+
* Build an async gate `(ctx) -> { ok, action, issues }` consumable
|
|
610
|
+
* by `b.guardAll`, `b.staticServe`, `b.fileUpload`, and any host
|
|
611
|
+
* that ingests user-supplied markdown. The gate decodes
|
|
612
|
+
* `ctx.bytes` / `ctx.bodyText`, runs `validate`, and maps
|
|
613
|
+
* severity to action: zero issues `serve`; only low/medium
|
|
614
|
+
* `audit-only`; sanitizable issues `sanitize` (returning the
|
|
615
|
+
* cleaned bytes); any unfixable critical `refuse`.
|
|
616
|
+
*
|
|
617
|
+
* @opts
|
|
618
|
+
* name: string, // gate label for audit / observability
|
|
619
|
+
* profile: "strict"|"balanced"|"permissive",
|
|
620
|
+
* compliance: "hipaa"|"pci-dss"|"gdpr"|"soc2",
|
|
621
|
+
* ...: same shape as b.guardMarkdown.validate opts,
|
|
622
|
+
*
|
|
623
|
+
* @example
|
|
624
|
+
* var g = b.guardMarkdown.gate({ profile: "strict" });
|
|
625
|
+
* var rv = await g({ bytes: Buffer.from("# hello\n", "utf8") });
|
|
626
|
+
* rv.action; // → "serve"
|
|
627
|
+
*
|
|
628
|
+
* var bad = await g({ bytes: Buffer.from("[x](javascript:1)", "utf8") });
|
|
629
|
+
* bad.action; // → "refuse"
|
|
630
|
+
*/
|
|
511
631
|
function gate(opts) {
|
|
512
632
|
opts = _resolveOpts(opts);
|
|
513
633
|
return gateContract.buildGuardGate(
|
|
@@ -547,13 +667,75 @@ function gate(opts) {
|
|
|
547
667
|
});
|
|
548
668
|
}
|
|
549
669
|
|
|
670
|
+
/**
|
|
671
|
+
* @primitive b.guardMarkdown.buildProfile
|
|
672
|
+
* @signature b.guardMarkdown.buildProfile(opts)
|
|
673
|
+
* @since 0.7.16
|
|
674
|
+
* @status stable
|
|
675
|
+
* @related b.guardMarkdown.gate, b.guardMarkdown.compliancePosture
|
|
676
|
+
*
|
|
677
|
+
* Compose a derived profile from one or more named bases plus
|
|
678
|
+
* inline overrides. `opts.extends` is a profile name or array of
|
|
679
|
+
* names (later entries shadow earlier ones); inline keys win last.
|
|
680
|
+
*
|
|
681
|
+
* @opts
|
|
682
|
+
* extends: string|string[], // base profile name(s) to compose
|
|
683
|
+
* ...: any guard-markdown key, // inline override of resolved keys
|
|
684
|
+
*
|
|
685
|
+
* @example
|
|
686
|
+
* var custom = b.guardMarkdown.buildProfile({
|
|
687
|
+
* extends: "balanced",
|
|
688
|
+
* dangerousTagPolicy: "strip",
|
|
689
|
+
* schemeAllowlist: ["http", "https"],
|
|
690
|
+
* });
|
|
691
|
+
* custom.dangerousTagPolicy; // → "strip"
|
|
692
|
+
* custom.schemeAllowlist.indexOf("mailto"); // → -1
|
|
693
|
+
*/
|
|
550
694
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
551
695
|
|
|
696
|
+
/**
|
|
697
|
+
* @primitive b.guardMarkdown.compliancePosture
|
|
698
|
+
* @signature b.guardMarkdown.compliancePosture(name)
|
|
699
|
+
* @since 0.7.16
|
|
700
|
+
* @status stable
|
|
701
|
+
* @compliance hipaa, pci-dss, gdpr, soc2
|
|
702
|
+
* @related b.guardMarkdown.gate, b.guardMarkdown.buildProfile
|
|
703
|
+
*
|
|
704
|
+
* Look up a compliance-posture overlay by name (`"hipaa"` /
|
|
705
|
+
* `"pci-dss"` / `"gdpr"` / `"soc2"`). Returns a shallow clone of
|
|
706
|
+
* the posture object — the caller may mutate freely. Throws
|
|
707
|
+
* `GuardMarkdownError("markdown.bad-posture")` on unknown name.
|
|
708
|
+
*
|
|
709
|
+
* @example
|
|
710
|
+
* var posture = b.guardMarkdown.compliancePosture("hipaa");
|
|
711
|
+
* posture.dangerousTagPolicy; // → "reject"
|
|
712
|
+
*/
|
|
552
713
|
function compliancePosture(name) {
|
|
553
714
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES, _err, "markdown");
|
|
554
715
|
}
|
|
555
716
|
|
|
556
717
|
var _markdownRulePacks = gateContract.makeRulePackLoader(GuardMarkdownError, "markdown");
|
|
718
|
+
/**
|
|
719
|
+
* @primitive b.guardMarkdown.loadRulePack
|
|
720
|
+
* @signature b.guardMarkdown.loadRulePack(pack)
|
|
721
|
+
* @since 0.7.16
|
|
722
|
+
* @status stable
|
|
723
|
+
* @related b.guardMarkdown.gate
|
|
724
|
+
*
|
|
725
|
+
* Register an operator-supplied rule pack with the guard-markdown
|
|
726
|
+
* registry. The pack is identified by `pack.id` (non-empty
|
|
727
|
+
* string) and stored for later inspection / dispatch by gates
|
|
728
|
+
* that opt in via `opts.rulePackId`. Throws
|
|
729
|
+
* `GuardMarkdownError("markdown.bad-opt")` when `pack` is missing
|
|
730
|
+
* or `pack.id` is not a non-empty string.
|
|
731
|
+
*
|
|
732
|
+
* @example
|
|
733
|
+
* var pack = b.guardMarkdown.loadRulePack({
|
|
734
|
+
* id: "wiki-internal",
|
|
735
|
+
* extraSchemeAllowlist: ["wiki"],
|
|
736
|
+
* });
|
|
737
|
+
* pack.id; // → "wiki-internal"
|
|
738
|
+
*/
|
|
557
739
|
var loadRulePack = _markdownRulePacks.load;
|
|
558
740
|
|
|
559
741
|
module.exports = {
|