@blamejs/core 0.8.42 → 0.8.49
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-html.js
CHANGED
|
@@ -1,120 +1,95 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* @module b.guardHtml
|
|
4
|
+
* @nav Guards
|
|
5
|
+
* @title Guard Html
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* @intro
|
|
8
|
+
* HTML / XSS defense — DOM-clobbering, mXSS, and entity-encoding
|
|
9
|
+
* bypasses. Tag and attribute allowlists; URL-scheme allowlist on
|
|
10
|
+
* every URL-bearing attribute; bidi / control / zero-width
|
|
11
|
+
* stripping. Threat catalog grounded in 2026 sanitizer research
|
|
12
|
+
* (DOMPurify CVE series, OWASP XSS / DOM-Clobbering / HTML5
|
|
13
|
+
* Security cheat sheets, PortSwigger / Sonar / trace37 mXSS
|
|
14
|
+
* write-ups, html5sec.org).
|
|
8
15
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
16
|
+
* Three profiles ship — `strict` / `balanced` / `permissive` —
|
|
17
|
+
* plus four compliance postures (`hipaa` / `pci-dss` / `gdpr` /
|
|
18
|
+
* `soc2`) that compose on top via the strictest-wins rule.
|
|
19
|
+
* `b.guardHtml.gate(opts)` returns a guard descriptor that plugs
|
|
20
|
+
* into `b.fileUpload.contentSafety` / `b.staticServe.contentSafety`
|
|
21
|
+
* / `b.guardAll`.
|
|
22
|
+
*
|
|
23
|
+
* Threat catalog covered:
|
|
24
|
+
*
|
|
25
|
+
* 1. Dangerous tags — script / style / link / meta / base /
|
|
26
|
+
* iframe / object / embed / applet / form / input / button /
|
|
27
|
+
* textarea / select / isindex / marquee / blink / layer /
|
|
28
|
+
* ilayer / plaintext / listing / xmp / audio / video / source
|
|
29
|
+
* / track / math / svg / template / noscript / noembed /
|
|
30
|
+
* noframes / portal / dialog / keygen / menuitem / command /
|
|
31
|
+
* frame / frameset.
|
|
32
|
+
* 2. on* event-handler attributes — every attribute matching
|
|
33
|
+
* /^on[a-z]/ is denied unconditionally.
|
|
34
|
+
* 3. Form-override attributes — formaction / formmethod /
|
|
35
|
+
* formenctype / formtarget / formnovalidate (CWE-1021).
|
|
36
|
+
* 4. Iframe inline-HTML — srcdoc on iframe always denied.
|
|
37
|
+
* 5. Custom-element registration — `is="..."` always denied.
|
|
38
|
+
* 6. CSP-bypass-shaped attributes — nonce / integrity /
|
|
39
|
+
* crossorigin stripped from sanitized output.
|
|
40
|
+
* 7. URL scheme validation on URL-bearing attributes — href /
|
|
41
|
+
* src / action / cite / longdesc / manifest / archive /
|
|
42
|
+
* codebase / data / classid / code / profile / ping / dynsrc
|
|
43
|
+
* / lowsrc / background / poster / icon / xlink:href.
|
|
44
|
+
* Per-profile allowlist; denied schemes (always):
|
|
45
|
+
* javascript / vbscript / livescript / mocha / data (outside
|
|
46
|
+
* image context) / file / mhtml / jar / intent / view-source.
|
|
47
|
+
* 8. CSS-injection inside style="..." values — expression( (IE),
|
|
48
|
+
* behavior: (IE), -moz-binding (Firefox legacy), javascript: /
|
|
49
|
+
* vbscript: / livescript: inside url(), @import, @namespace.
|
|
50
|
+
* 9. DOM clobbering — id and name attributes whose values match
|
|
51
|
+
* a well-known JS global (document / window / location /
|
|
52
|
+
* cookie / __proto__ / constructor / ...) on clobber-prone
|
|
53
|
+
* elements (form / input / button / a / img / iframe /
|
|
54
|
+
* object / embed / select / textarea).
|
|
55
|
+
* 10. mXSS hint detection — namespace-context-shift parents
|
|
56
|
+
* (svg / math), CDATA inside HTML mode, template content
|
|
57
|
+
* fragments with entity-encoded payloads.
|
|
58
|
+
* 11. Unicode bidi (CVE-2021-42574 Trojan Source) inside text
|
|
59
|
+
* and attribute values.
|
|
60
|
+
* 12. C0 control characters, null bytes, zero-width chars —
|
|
61
|
+
* strip-or-reject per profile.
|
|
62
|
+
* 13. IE conditional comments — <!--[if ...]>...<![endif]-->
|
|
63
|
+
* refused in strict, stripped in balanced.
|
|
64
|
+
* 14. <base href> / <base target> — silently redirects every
|
|
65
|
+
* relative URL on the page; always denied.
|
|
66
|
+
* 15. <meta http-equiv> with refresh / Set-Cookie / X-XSS-
|
|
67
|
+
* Protection — silent navigation + cookie injection.
|
|
68
|
+
* 16. ARIA spoofing — flagged for audit when role mismatches
|
|
69
|
+
* the semantic tag.
|
|
70
|
+
* 17. Image-context data: URLs — sanitize allows data:image/png
|
|
71
|
+
* / jpeg / gif / webp; svg+xml requires explicit opt-in.
|
|
72
|
+
* 18. Total-document size cap, per-attribute-value size cap,
|
|
73
|
+
* max-tag-depth (anti-recursion), max-attribute-count-per-
|
|
74
|
+
* tag.
|
|
14
75
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* 3. Form-override attributes — formaction / formmethod /
|
|
34
|
-
* formenctype / formtarget / formnovalidate on <button> / <input>
|
|
35
|
-
* override the parent form's submission target (CWE-1021); always
|
|
36
|
-
* denied.
|
|
37
|
-
*
|
|
38
|
-
* 4. Iframe inline-HTML — srcdoc on <iframe> ships executable HTML
|
|
39
|
-
* directly into the document; always denied.
|
|
40
|
-
*
|
|
41
|
-
* 5. Custom-element registration — `is="..."` attribute mutates the
|
|
42
|
-
* element class via document.createElement(..., { is }); always
|
|
43
|
-
* denied.
|
|
44
|
-
*
|
|
45
|
-
* 6. CSP-bypass-shaped attributes — nonce, integrity, crossorigin
|
|
46
|
-
* stripped from sanitized output (operator-controlled only).
|
|
47
|
-
*
|
|
48
|
-
* 7. URL scheme validation on URL-bearing attributes — href, src,
|
|
49
|
-
* action, cite, longdesc, manifest, archive, codebase, data,
|
|
50
|
-
* classid, code, profile, ping, dynsrc, lowsrc, background,
|
|
51
|
-
* poster, icon, xlink:href. Per-attribute scheme allowlist:
|
|
52
|
-
* strict → http / https / mailto / tel only;
|
|
53
|
-
* balanced → +data:image/* + ftp;
|
|
54
|
-
* permissive → +ftp / sftp / ws / wss.
|
|
55
|
-
* Denied schemes (always): javascript / vbscript / livescript /
|
|
56
|
-
* mocha / data (outside image context) / file / mhtml / jar /
|
|
57
|
-
* intent / view-source.
|
|
58
|
-
*
|
|
59
|
-
* 8. CSS-injection inside style="..." attribute values — denies
|
|
60
|
-
* expression( (IE), behavior: (IE), -moz-binding (Firefox legacy),
|
|
61
|
-
* javascript: / vbscript: / livescript: inside url(), @import,
|
|
62
|
-
* @charset / @namespace (CSS-source-map confusion).
|
|
63
|
-
*
|
|
64
|
-
* 9. DOM clobbering — id and name attributes whose values match a
|
|
65
|
-
* well-known JS global (document, window, location, cookie,
|
|
66
|
-
* __proto__, constructor, ...) on form / input / button / anchor /
|
|
67
|
-
* img / iframe elements. Catches the form-element + input-name
|
|
68
|
-
* payload + named-property-access exfil chain.
|
|
69
|
-
*
|
|
70
|
-
* 10. mXSS hint detection — common parser-mode-shift vectors:
|
|
71
|
-
* <svg><p>...</svg>, <math><p>...</math>, <noscript>...</noscript>
|
|
72
|
-
* with quote+entity confusion, CDATA inside HTML mode, <template>
|
|
73
|
-
* content fragment with entity-encoded payloads. Surfaced as
|
|
74
|
-
* "mxss-hint" issues; refused in strict, audited in balanced.
|
|
75
|
-
*
|
|
76
|
-
* 11. Unicode bidi (CVE-2021-42574 Trojan Source) inside text and
|
|
77
|
-
* attribute values — same codepoint catalog as guard-csv.
|
|
78
|
-
*
|
|
79
|
-
* 12. C0 control characters, null bytes, zero-width chars in input —
|
|
80
|
-
* strip-or-reject per profile.
|
|
81
|
-
*
|
|
82
|
-
* 13. IE conditional comments — <!--[if ...]>...<![endif]--> can carry
|
|
83
|
-
* executable script in the legacy IE rendering path; refused in
|
|
84
|
-
* strict, stripped in balanced.
|
|
85
|
-
*
|
|
86
|
-
* 14. <base href> / <base target> — silently redirects every relative
|
|
87
|
-
* URL on the page; always denied.
|
|
88
|
-
*
|
|
89
|
-
* 15. <meta http-equiv> with refresh / Set-Cookie / X-XSS-Protection
|
|
90
|
-
* values — silent navigation + cookie injection; always denied.
|
|
91
|
-
*
|
|
92
|
-
* 16. ARIA spoofing — `role="button"` on a non-button element with
|
|
93
|
-
* attached event handlers (caught upstream by the on* handler
|
|
94
|
-
* strip); flagged for audit when role mismatches semantic tag.
|
|
95
|
-
*
|
|
96
|
-
* 17. Image-context data: URLs — sanitize allows data:image/png ;
|
|
97
|
-
* data:image/jpeg ; data:image/gif ; data:image/webp ;
|
|
98
|
-
* data:image/svg+xml requires explicit opt-in (svg embed is its
|
|
99
|
-
* own threat surface).
|
|
100
|
-
*
|
|
101
|
-
* 18. Total-document size cap (anti-DoS), per-attribute-value size
|
|
102
|
-
* cap, max-tag-depth (prevents recursion-shape parsers from
|
|
103
|
-
* stack-blowing), max-attribute-count-per-tag.
|
|
104
|
-
*
|
|
105
|
-
* Threat-detection regex literals are composed PROGRAMMATICALLY from
|
|
106
|
-
* numeric codepoint range tables (BIDI_RANGES / C0_CTRL_RANGES /
|
|
107
|
-
* ZERO_WIDTH_RANGES). Source file never embeds the attack characters
|
|
108
|
-
* themselves.
|
|
109
|
-
*
|
|
110
|
-
* Sanitize discipline: this module ships a token-level rewriter that
|
|
111
|
-
* preserves the allowlisted tag set and strips the rest. For HOSTILE
|
|
112
|
-
* sources, the documented correct response is validate + reject — not
|
|
113
|
-
* sanitize. mXSS bypasses against any non-DOM sanitizer are a known
|
|
114
|
-
* arms-race; the gate's "refuse" path is the one with strong invariants.
|
|
115
|
-
* Operators with display-of-untrusted-html requirements should
|
|
116
|
-
* additionally serve content under a strict CSP (default-src 'none' or
|
|
117
|
-
* sandboxed iframe).
|
|
76
|
+
* Threat-detection regex literals are composed PROGRAMMATICALLY
|
|
77
|
+
* from numeric codepoint range tables (BIDI_RANGES /
|
|
78
|
+
* C0_CTRL_RANGES / ZERO_WIDTH_RANGES). The source file never
|
|
79
|
+
* embeds the attack characters themselves.
|
|
80
|
+
*
|
|
81
|
+
* Sanitize discipline: this module ships a token-level rewriter
|
|
82
|
+
* that preserves the allowlisted tag set and strips the rest.
|
|
83
|
+
* For HOSTILE sources, the documented correct response is
|
|
84
|
+
* validate + reject — not sanitize. mXSS bypasses against any
|
|
85
|
+
* non-DOM sanitizer are a known arms-race; the gate's `refuse`
|
|
86
|
+
* path is the one with strong invariants. Operators with
|
|
87
|
+
* display-of-untrusted-html requirements should additionally
|
|
88
|
+
* serve content under a strict CSP (default-src 'none' or
|
|
89
|
+
* sandboxed iframe).
|
|
90
|
+
*
|
|
91
|
+
* @card
|
|
92
|
+
* HTML / XSS defense — DOM-clobbering, mXSS, and entity-encoding bypasses.
|
|
118
93
|
*/
|
|
119
94
|
|
|
120
95
|
var codepointClass = require("./codepoint-class");
|
|
@@ -376,8 +351,25 @@ function _resolveOpts(opts) {
|
|
|
376
351
|
});
|
|
377
352
|
}
|
|
378
353
|
|
|
379
|
-
|
|
380
|
-
|
|
354
|
+
/**
|
|
355
|
+
* @primitive b.guardHtml.escapeText
|
|
356
|
+
* @signature b.guardHtml.escapeText(value)
|
|
357
|
+
* @since 0.7.6
|
|
358
|
+
* @related b.guardHtml.escapeAttr, b.guardHtml.sanitize
|
|
359
|
+
*
|
|
360
|
+
* HTML entity-escape for text-content context. Encodes the five
|
|
361
|
+
* core characters `&` `<` `>` `"` `'` so the result is safe to
|
|
362
|
+
* embed inside an element's text body. `null` / `undefined` coerce
|
|
363
|
+
* to an empty string. Use this for plain interpolation of
|
|
364
|
+
* untrusted strings into rendered HTML.
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* var html = b.guardHtml.escapeText("<oops>");
|
|
368
|
+
* // → "<oops>"
|
|
369
|
+
*
|
|
370
|
+
* var blank = b.guardHtml.escapeText(null);
|
|
371
|
+
* // → ""
|
|
372
|
+
*/
|
|
381
373
|
function escapeText(value) {
|
|
382
374
|
var s = value == null ? "" : String(value);
|
|
383
375
|
return s
|
|
@@ -388,8 +380,25 @@ function escapeText(value) {
|
|
|
388
380
|
.replace(/'/g, "'");
|
|
389
381
|
}
|
|
390
382
|
|
|
391
|
-
|
|
392
|
-
|
|
383
|
+
/**
|
|
384
|
+
* @primitive b.guardHtml.escapeAttr
|
|
385
|
+
* @signature b.guardHtml.escapeAttr(value)
|
|
386
|
+
* @since 0.7.6
|
|
387
|
+
* @related b.guardHtml.escapeText, b.guardHtml.sanitize
|
|
388
|
+
*
|
|
389
|
+
* HTML entity-escape for attribute-value context. Same five
|
|
390
|
+
* characters as `escapeText` plus backtick (legacy IE attribute
|
|
391
|
+
* terminator) and `=` (unquoted-attribute edge). Use this when
|
|
392
|
+
* interpolating an untrusted string between double-quoted attribute
|
|
393
|
+
* delimiters.
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* var attr = b.guardHtml.escapeAttr('say "hi"');
|
|
397
|
+
* // → "say "hi""
|
|
398
|
+
*
|
|
399
|
+
* var ie = b.guardHtml.escapeAttr("a`b=c");
|
|
400
|
+
* // → "a`b=c"
|
|
401
|
+
*/
|
|
393
402
|
function escapeAttr(value) {
|
|
394
403
|
var s = value == null ? "" : String(value);
|
|
395
404
|
return s
|
|
@@ -905,6 +914,53 @@ function _sanitize(input, opts) {
|
|
|
905
914
|
|
|
906
915
|
// ---- Public surface ----
|
|
907
916
|
|
|
917
|
+
/**
|
|
918
|
+
* @primitive b.guardHtml.validate
|
|
919
|
+
* @signature b.guardHtml.validate(input, opts?)
|
|
920
|
+
* @since 0.7.6
|
|
921
|
+
* @related b.guardHtml.sanitize, b.guardHtml.gate
|
|
922
|
+
*
|
|
923
|
+
* Tokenize `input` (string or Buffer of HTML) and walk every
|
|
924
|
+
* element / attribute against the resolved profile. Returns
|
|
925
|
+
* `{ ok, issues }` where `issues` is an array of
|
|
926
|
+
* `{ kind, severity, ruleId, location, snippet }` records.
|
|
927
|
+
* Never modifies the input — call `sanitize` for that. Anti-DoS
|
|
928
|
+
* caps (`maxBytes` / `maxAttrValueBytes` / `maxTagDepth` /
|
|
929
|
+
* `maxAttrsPerTag`) are validated as positive finite integers;
|
|
930
|
+
* passing `Infinity` throws.
|
|
931
|
+
*
|
|
932
|
+
* @opts
|
|
933
|
+
* profile: string, // "strict" | "balanced" | "permissive"
|
|
934
|
+
* compliancePosture: string, // "hipaa" | "pci-dss" | "gdpr" | "soc2"
|
|
935
|
+
* allowedTags: Array<string>,
|
|
936
|
+
* allowedAttrs: Array<string>,
|
|
937
|
+
* urlSchemes: Array<string>,
|
|
938
|
+
* allowImageData: boolean,
|
|
939
|
+
* allowComments: boolean,
|
|
940
|
+
* bidiPolicy: string, // "reject" | "strip" | "audit"
|
|
941
|
+
* controlPolicy: string,
|
|
942
|
+
* nullBytePolicy: string,
|
|
943
|
+
* zeroWidthPolicy: string,
|
|
944
|
+
* cssPolicy: string,
|
|
945
|
+
* domClobberPolicy: string,
|
|
946
|
+
* mxssHintPolicy: string,
|
|
947
|
+
* maxBytes: number,
|
|
948
|
+
* maxAttrValueBytes: number,
|
|
949
|
+
* maxTagDepth: number,
|
|
950
|
+
* maxAttrsPerTag: number,
|
|
951
|
+
*
|
|
952
|
+
* @example
|
|
953
|
+
* var rv = b.guardHtml.validate("<p>hi</p><script>alert(1)</script>",
|
|
954
|
+
* { profile: "strict" });
|
|
955
|
+
* rv.ok; // → false
|
|
956
|
+
* rv.issues[0].kind; // → "dangerous-tag"
|
|
957
|
+
* rv.issues[0].severity; // → "critical"
|
|
958
|
+
*
|
|
959
|
+
* var clean = b.guardHtml.validate("<p>just text</p>",
|
|
960
|
+
* { profile: "strict" });
|
|
961
|
+
* clean.ok; // → true
|
|
962
|
+
* clean.issues.length; // → 0
|
|
963
|
+
*/
|
|
908
964
|
function validate(input, opts) {
|
|
909
965
|
opts = _resolveOpts(opts);
|
|
910
966
|
numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
|
|
@@ -914,6 +970,46 @@ function validate(input, opts) {
|
|
|
914
970
|
return gateContract.runIssueValidator(input, opts, _detectIssues);
|
|
915
971
|
}
|
|
916
972
|
|
|
973
|
+
/**
|
|
974
|
+
* @primitive b.guardHtml.sanitize
|
|
975
|
+
* @signature b.guardHtml.sanitize(input, opts?)
|
|
976
|
+
* @since 0.7.6
|
|
977
|
+
* @related b.guardHtml.validate, b.guardHtml.gate
|
|
978
|
+
*
|
|
979
|
+
* Token-level rewriter that drops every tag NOT in the resolved
|
|
980
|
+
* profile's `allowedTags`, every attribute NOT in `allowedAttrs`,
|
|
981
|
+
* every URL whose scheme falls outside the profile allowlist,
|
|
982
|
+
* every event-handler / form-override / clobbering attribute, and
|
|
983
|
+
* every body of a body-drop tag (script / style / template / svg
|
|
984
|
+
* / math / iframe / object / embed / applet). For HOSTILE
|
|
985
|
+
* sources, prefer `validate` + `refuse` — `sanitize` is best
|
|
986
|
+
* effort against the documented arms-race.
|
|
987
|
+
*
|
|
988
|
+
* @opts
|
|
989
|
+
* profile: string, // "strict" | "balanced" | "permissive"
|
|
990
|
+
* compliancePosture: string, // "hipaa" | "pci-dss" | "gdpr" | "soc2"
|
|
991
|
+
* allowedTags: Array<string>,
|
|
992
|
+
* allowedAttrs: Array<string>,
|
|
993
|
+
* urlSchemes: Array<string>,
|
|
994
|
+
* allowImageData: boolean,
|
|
995
|
+
* allowComments: boolean,
|
|
996
|
+
* maxBytes: number,
|
|
997
|
+
* maxAttrValueBytes: number,
|
|
998
|
+
* maxTagDepth: number,
|
|
999
|
+
* maxAttrsPerTag: number,
|
|
1000
|
+
*
|
|
1001
|
+
* @example
|
|
1002
|
+
* var clean = b.guardHtml.sanitize(
|
|
1003
|
+
* "<p>hi</p><script>alert(1)</script>",
|
|
1004
|
+
* { profile: "balanced" });
|
|
1005
|
+
* // → "<p>hi</p>"
|
|
1006
|
+
*
|
|
1007
|
+
* // Event-handler attribute is stripped, text content preserved.
|
|
1008
|
+
* var stripped = b.guardHtml.sanitize(
|
|
1009
|
+
* '<a href="https://example.com" onclick="alert(1)">go</a>',
|
|
1010
|
+
* { profile: "balanced" });
|
|
1011
|
+
* // → '<a href="https://example.com">go</a>'
|
|
1012
|
+
*/
|
|
917
1013
|
function sanitize(input, opts) {
|
|
918
1014
|
opts = _resolveOpts(opts);
|
|
919
1015
|
var text = typeof input === "string"
|
|
@@ -925,6 +1021,46 @@ function sanitize(input, opts) {
|
|
|
925
1021
|
return _sanitize(text, opts);
|
|
926
1022
|
}
|
|
927
1023
|
|
|
1024
|
+
/**
|
|
1025
|
+
* @primitive b.guardHtml.gate
|
|
1026
|
+
* @signature b.guardHtml.gate(opts?)
|
|
1027
|
+
* @since 0.7.6
|
|
1028
|
+
* @related b.guardHtml.validate, b.guardHtml.sanitize, b.guardAll
|
|
1029
|
+
*
|
|
1030
|
+
* Returns a guard descriptor that plugs into the framework's
|
|
1031
|
+
* content-safety wiring (`b.fileUpload.contentSafety` /
|
|
1032
|
+
* `b.staticServe.contentSafety` / `b.guardAll`). The descriptor's
|
|
1033
|
+
* `inspect(ctx)` resolves to one of four actions: `serve` (no
|
|
1034
|
+
* issues), `audit-only` (low-severity issues observed), `sanitize`
|
|
1035
|
+
* (sanitized buffer attached when no policy is "reject"), or
|
|
1036
|
+
* `refuse` (critical issue with at least one reject-policy active).
|
|
1037
|
+
*
|
|
1038
|
+
* @opts
|
|
1039
|
+
* name: string,
|
|
1040
|
+
* profile: string, // "strict" | "balanced" | "permissive"
|
|
1041
|
+
* compliancePosture: string, // "hipaa" | "pci-dss" | "gdpr" | "soc2"
|
|
1042
|
+
* mode: string, // "enforce" | "observe"
|
|
1043
|
+
* allowedTags: Array<string>,
|
|
1044
|
+
* allowedAttrs: Array<string>,
|
|
1045
|
+
* urlSchemes: Array<string>,
|
|
1046
|
+
* bidiPolicy: string,
|
|
1047
|
+
* controlPolicy: string,
|
|
1048
|
+
* cssPolicy: string,
|
|
1049
|
+
* domClobberPolicy: string,
|
|
1050
|
+
* mxssHintPolicy: string,
|
|
1051
|
+
* maxBytes: number,
|
|
1052
|
+
* maxRuntimeMs: number,
|
|
1053
|
+
*
|
|
1054
|
+
* @example
|
|
1055
|
+
* var g = b.guardHtml.gate({ profile: "strict" });
|
|
1056
|
+
* g.name; // → "guardHtml:strict"
|
|
1057
|
+
*
|
|
1058
|
+
* // Refuse on tag-budget exceeded — strict profile rejects <script>.
|
|
1059
|
+
* var hostileBuf = Buffer.from("<p>hi</p><script>alert(1)</script>", "utf8");
|
|
1060
|
+
* var rv = await g.inspect({ bytes: hostileBuf, contentType: "text/html" });
|
|
1061
|
+
* rv.ok; // → false
|
|
1062
|
+
* rv.action; // → "refuse"
|
|
1063
|
+
*/
|
|
928
1064
|
function gate(opts) {
|
|
929
1065
|
opts = _resolveOpts(opts);
|
|
930
1066
|
return gateContract.buildGuardGate(
|
|
@@ -960,12 +1096,70 @@ function gate(opts) {
|
|
|
960
1096
|
});
|
|
961
1097
|
}
|
|
962
1098
|
|
|
1099
|
+
/**
|
|
1100
|
+
* @primitive b.guardHtml.buildProfile
|
|
1101
|
+
* @signature b.guardHtml.buildProfile(opts)
|
|
1102
|
+
* @since 0.7.6
|
|
1103
|
+
* @related b.guardHtml.compliancePosture, b.guardHtml.gate
|
|
1104
|
+
*
|
|
1105
|
+
* Resolve a named profile against `PROFILES` and return the merged
|
|
1106
|
+
* options bag. Operators introspecting the active limits (without
|
|
1107
|
+
* calling `validate` / `sanitize` / `gate`) call this. Throws
|
|
1108
|
+
* `GuardHtmlError` with code `html.bad-profile` when the name
|
|
1109
|
+
* doesn't appear in the profile catalog.
|
|
1110
|
+
*
|
|
1111
|
+
* @opts
|
|
1112
|
+
* profile: string, // "strict" | "balanced" | "permissive"
|
|
1113
|
+
*
|
|
1114
|
+
* @example
|
|
1115
|
+
* var resolved = b.guardHtml.buildProfile({ profile: "strict" });
|
|
1116
|
+
* resolved.maxBytes; // → 2097152 (2 MiB)
|
|
1117
|
+
* resolved.maxAttrValueBytes; // → 8192 (8 KiB)
|
|
1118
|
+
*/
|
|
963
1119
|
var buildProfile = gateContract.makeProfileBuilder(PROFILES);
|
|
964
1120
|
|
|
1121
|
+
/**
|
|
1122
|
+
* @primitive b.guardHtml.compliancePosture
|
|
1123
|
+
* @signature b.guardHtml.compliancePosture(name)
|
|
1124
|
+
* @since 0.7.6
|
|
1125
|
+
* @related b.guardHtml.buildProfile, b.guardHtml.gate
|
|
1126
|
+
*
|
|
1127
|
+
* Return the option overlay for a named compliance posture
|
|
1128
|
+
* (`hipaa` / `pci-dss` / `gdpr` / `soc2`). Operators compose this
|
|
1129
|
+
* over a base profile to harden the default per regulatory regime.
|
|
1130
|
+
* Throws `GuardHtmlError` with code `html.bad-posture` on unknown
|
|
1131
|
+
* names.
|
|
1132
|
+
*
|
|
1133
|
+
* @example
|
|
1134
|
+
* var hipaa = b.guardHtml.compliancePosture("hipaa");
|
|
1135
|
+
* hipaa.bidiPolicy; // → "reject"
|
|
1136
|
+
* hipaa.cssPolicy; // → "reject"
|
|
1137
|
+
* hipaa.mxssHintPolicy; // → "reject"
|
|
1138
|
+
*/
|
|
965
1139
|
function compliancePosture(name) {
|
|
966
1140
|
return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES, _err, "html");
|
|
967
1141
|
}
|
|
968
1142
|
|
|
1143
|
+
/**
|
|
1144
|
+
* @primitive b.guardHtml.loadRulePack
|
|
1145
|
+
* @signature b.guardHtml.loadRulePack(pack)
|
|
1146
|
+
* @since 0.7.6
|
|
1147
|
+
* @related b.guardHtml.gate, b.guardHtml.buildProfile
|
|
1148
|
+
*
|
|
1149
|
+
* Register an operator-supplied rule pack (a versioned bundle of
|
|
1150
|
+
* extra tag / attribute / scheme overrides) into the guard's
|
|
1151
|
+
* private store. Subsequent `gate` calls referencing the pack by
|
|
1152
|
+
* its `id` overlay these rules on top of the resolved profile.
|
|
1153
|
+
* Validates pack shape and throws `GuardHtmlError` on malformed
|
|
1154
|
+
* input.
|
|
1155
|
+
*
|
|
1156
|
+
* @example
|
|
1157
|
+
* b.guardHtml.loadRulePack({
|
|
1158
|
+
* id: "kb-2026-html",
|
|
1159
|
+
* version: "1.0.0",
|
|
1160
|
+
* extraDangerousTags: ["custom-element"],
|
|
1161
|
+
* });
|
|
1162
|
+
*/
|
|
969
1163
|
var _htmlRulePacks = gateContract.makeRulePackLoader(GuardHtmlError, "html");
|
|
970
1164
|
var loadRulePack = _htmlRulePacks.load;
|
|
971
1165
|
|