diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index fe64bef..0149f6a 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,9 +2,9 @@ name: Build & Test on: push: - branches: [main] + branches: [main, '0.x'] pull_request: - branches: [main] + branches: [main, '0.x'] # Default token to read-only; jobs widen only what they need. permissions: @@ -21,7 +21,7 @@ jobs: with: egress-policy: audit - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Node @@ -48,7 +48,7 @@ jobs: with: egress-policy: audit - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Node @@ -58,5 +58,5 @@ jobs: cache: npm - run: npm ci - run: npm run build - - run: npx playwright install --with-deps chromium + - run: npx playwright install --with-deps chromium firefox webkit - run: npm run test:browser diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 845d112..1b1325f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -2,9 +2,9 @@ name: CodeQL on: push: - branches: [main] + branches: [main, '0.x'] pull_request: - branches: [main] + branches: [main, '0.x'] schedule: - cron: '0 19 * * 4' @@ -29,7 +29,7 @@ jobs: with: egress-policy: audit - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Initialize CodeQL diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index bc3d48d..fd3b827 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -7,11 +7,12 @@ name: Scorecard supply-chain security on: # For the Branch-Protection check; only the default branch is supported. branch_protection_rule: - # Keeps the Maintained check fresh and re-evaluates after CodeQL has settled. + # Keeps the Maintained check fresh and re-evaluates after CodeQL has settled. Weekly is enough: + # the score signals do not move day to day, and Friday lands just after the Thursday CodeQL run. schedule: - - cron: '24 17 * * *' + - cron: '24 17 * * 5' # Lets a maintainer trigger the first run (and re-runs) by hand from the Actions - # tab, instead of waiting for the daily cron - useful to publish the first report. + # tab, instead of waiting for the weekly cron - useful to publish the first report. workflow_dispatch: # NOTE: no 'push' trigger on purpose - running at merge time races the CodeQL # scan of the just-merged commit and makes the SAST check read as unchecked. @@ -38,7 +39,7 @@ jobs: with: egress-policy: audit - name: Checkout code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Run analysis diff --git a/.github/workflows/sign-release.yml b/.github/workflows/sign-release.yml index 72136ac..b4eb596 100644 --- a/.github/workflows/sign-release.yml +++ b/.github/workflows/sign-release.yml @@ -16,7 +16,7 @@ jobs: - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.release.tag_name }} persist-credentials: false diff --git a/.github/workflows/slsa-provenance.yml b/.github/workflows/slsa-provenance.yml index a3f3616..8ba244b 100644 --- a/.github/workflows/slsa-provenance.yml +++ b/.github/workflows/slsa-provenance.yml @@ -17,7 +17,7 @@ jobs: - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: egress-policy: audit - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.release.tag_name }} persist-credentials: false diff --git a/.gitignore b/.gitignore index 6599235..7de2b1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules/ +dist/fortify.cov.* coverage/ .nyc_output/ *.log diff --git a/README.md b/README.md index 312a061..edfc7bd 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,59 @@ # DOMFortify -[![npm](https://img.shields.io/npm/v/domfortify.svg)](https://www.npmjs.com/package/domfortify) [![License](https://img.shields.io/badge/license-MPL--2.0%20OR%20Apache--2.0-blue.svg)](https://github.com/cure53/DOMFortify/blob/main/LICENSE) ![npm package minimized gzipped size (select exports)](https://img.shields.io/bundlejs/size/domfortify?color=%233C1&label=gzip) [![Build & Test](https://github.com/cure53/DOMFortify/actions/workflows/build-and-test.yml/badge.svg?branch=main)](https://github.com/cure53/DOMFortify/actions/workflows/build-and-test.yml) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/cure53/DOMFortify/badge)](https://scorecard.dev/viewer/?uri=github.com/cure53/DOMFortify) [![Socket Badge](https://badge.socket.dev/npm/package/domfortify/latest)](https://badge.socket.dev/npm/package/domfortify/latest) +[![npm](https://img.shields.io/npm/v/domfortify.svg)](https://www.npmjs.com/package/domfortify) [![License](https://img.shields.io/badge/license-MPL--2.0%20OR%20Apache--2.0-blue.svg)](https://github.com/cure53/DOMFortify/blob/main/LICENSE) [![Downloads](https://img.shields.io/npm/dm/domfortify.svg)](https://www.npmjs.com/package/domfortify) [![dependents](https://badgen.net/github/dependents-repo/cure53/domfortify?color=green&label=dependents)](https://github.com/cure53/DOMFortify/network/dependents) ![npm package minimized gzipped size](https://img.shields.io/bundlejs/size/domfortify?color=%233C1&label=gzip) -DOMFortify turns on Trusted Types for a page and quietly takes over the browser's `default` policy, -so that old, vulnerable code like `el.innerHTML = location.hash` gets sanitized before it ever hits -the DOM. You don't touch the code. You don't even need to know where the bug is. +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/13287/badge)](https://www.bestpractices.dev/projects/13287) [![Build & Test](https://github.com/cure53/DOMFortify/actions/workflows/build-and-test.yml/badge.svg?branch=main)](https://github.com/cure53/DOMFortify/actions/workflows/build-and-test.yml) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/cure53/DOMFortify/badge)](https://scorecard.dev/viewer/?uri=github.com/cure53/DOMFortify) [![Socket Badge](https://badge.socket.dev/npm/package/domfortify/latest)](https://badge.socket.dev/npm/package/domfortify/latest) -It's for the sites you can't easily fix: complex apps or legacy apps nobody wants to touch, the third-party widget you -can't patch, the 2000+ `innerHTML` sinks written before anyone had heard of XSS. +DOMFortify turns Trusted Types on for a page and quietly takes over the browser's `default` policy, so +that old, vulnerable code like `el.innerHTML = location.hash` gets sanitized before it ever reaches the +DOM. You don't touch the code. You don't even need to know where the bug is. -**Just ship the policy, and the browser automatically protects every HTML sink with DOMPurify or other sanitizers.** +It's for the sites you can't easily fix: sprawling apps and legacy code nobody wants to touch, the +third-party widget you can't patch, the 2000-plus `innerHTML` sinks written before anyone had heard of +XSS. + +**Ship the policy, and the browser routes every HTML sink through DOMPurify (or any sanitizer you give +it) on its way into the DOM.** + +New here? The [wiki](https://github.com/cure53/DOMFortify/wiki) has the deeper docs: +[Installation and Usage](https://github.com/cure53/DOMFortify/wiki/Installation-and-Usage), +[How It Works](https://github.com/cure53/DOMFortify/wiki/How-It-Works) with data-flow diagrams, the +[Security Goals and Threat Model](https://github.com/cure53/DOMFortify/wiki/Security-Goals-and-Threat-Model), +and [Risks and Footguns](https://github.com/cure53/DOMFortify/wiki/Risks-and-Footguns). ## Is there a demo? -Of course there is. [Play with DOMFortify](https://cure53.de/fortify) - throw payloads at a -deliberately broken page and watch the browser neutralize them before they reach the DOM. +Of course. [Play with DOMFortify](https://cure53.de/fortify) - throw payloads at a deliberately broken +page and watch the browser neutralize them before they reach the DOM. + +There's also a [collection of standalone demos](demos/) you can read or serve locally, one per feature, +including [URL scoping with EXCLUDE / URL_CONFIG](demos/url-config-demo.html) and the +[INCLUDE allow-list](demos/include-demo.html). ## How it works -Trusted Types lets a page register one `default` policy that the browser calls for every dangerous +Trusted Types lets a page register one `default` policy that the browser consults for every dangerous sink. DOMFortify is that policy. -HTML goes through [DOMPurify](https://github.com/cure53/DOMPurify) -(or any sanitizer you hand it); script sinks like `eval` and `script.src` are refused outright, -because there is no safe way to sanitize executable code. +HTML goes through [DOMPurify](https://github.com/cure53/DOMPurify) (or any sanitizer you hand it). Script +sinks like `eval` and `script.src` are refused outright, because there is no safe way to sanitize +executable code. -## Usage +It does two jobs and no more: own the `default` policy, and route sinks. Whether enforcement is on comes +from a CSP - a response header, a parse-time ``, or DOMFortify's opt-in `INJECT_META` - and either +way DOMFortify reports honestly, through `status()`, whether the page is actually protected. For the full +mental model with data-flow diagrams, see +[How It Works](https://github.com/cure53/DOMFortify/wiki/How-It-Works) in the wiki. -Two parts. First, turn enforcement on with a CSP - a response header if you can set one: +## Quick start (CDN) + +Two parts. First, turn enforcement on with a CSP. A response header is the sturdiest option: ``` Content-Security-Policy: require-trusted-types-for 'script'; trusted-types default dompurify; ``` -...or via `` tag if you cannot set any headers: +...or a `` tag when you cannot set headers (it must be present at parse time): ```html ``` -Second, load the sanitizer and then DOMFortify, **first thing in ``**, before anything an -attacker could reach. Pin both with SRI so a bad CDN day fails closed instead of open: +...or, when you can place neither, let DOMFortify inject that `` for you with one config flag: + +```js +window.DOMFortifyConfig = { INJECT_META: true }; +``` + +This is best-effort and only takes when DOMFortify runs during the initial parse (inline, first thing +in ``); a header or hand-placed `` is still sturdier. Confirm it took with +`status().enforcementActive`. Details in [Turning enforcement on](#turning-enforcement-on-advanced). + +Second, load the sanitizer and then DOMFortify **first thing in ``**, before anything an attacker +could reach. Pin both with SRI so a bad CDN day fails closed instead of open: ```html ``` -That's it. The script installs itself on load. Want to check it actually worked? +That's it. This build installs itself on load. Check it actually worked: ```js -DOMFortify.status().protected; // true when enforced, owning the policy, and sanitizer ready +DOMFortify.status().protected; // true when enforced, owning the policy, and the sanitizer is ready ``` -If you have to go through a bundler, import the module build and call `init()` as early as you can - -but understand that a bundler will not place your code first, which is the one thing this needs: +> Pin a version you have vetted and regenerate the SRI hash whenever you change it, for example +> `openssl dgst -sha384 -binary purify.min.js | openssl base64 -A`. The two hashes above are for the +> exact versions named in the URLs. + +## Using it from npm + +```sh +npm install domfortify +``` + +The package ships three builds and TypeScript types, picked automatically by your tooling: + +| Build | File | What it does | +| ------------------- | --------------------- | ------------------------------------------------------------ | +| ESM | `dist/fortify.es.mjs` | `import { init } from 'domfortify'` - you call `init()` | +| CommonJS | `dist/fortify.cjs.js` | `const { init } = require('domfortify')` - you call `init()` | +| IIFE (auto-install) | `dist/fortify.min.js` | self-installs on load; this is the ` + + + + + +
+ +

Scoping with INCLUDE

+

+ INCLUDE is the allow-list complement of EXCLUDE: DOMFortify activates + only on matching URLs and stays inactive everywhere else. This page keys off the query + string. ?admin is in scope, so DOMFortify claims the policy, injects the enabling + CSP, and sanitizes. The baseline URL is out of scope, so DOMFortify stands down and leaves the + page untouched. +

+ +
+

Pick a URL

+

+ baseline (out of scope)  |  + ?admin (in scope) +

+

Current:

+

excluded =   metaInjected = +   protected =

+
+ +
+

Same payload on both URLs

+ + +
Resulting HTML:
+

+        

+
+
+ + + diff --git a/dist/fortify.cjs.js b/dist/fortify.cjs.js index a8dc968..e044502 100644 --- a/dist/fortify.cjs.js +++ b/dist/fortify.cjs.js @@ -1,44 +1,53 @@ -/*! DOMFortify 0.1.0 | (c) Cure53 and contributors | (MPL-2.0 OR Apache-2.0) */ +/*! DOMFortify 0.5.0 | (c) Cure53 and contributors | (MPL-2.0 OR Apache-2.0) */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -const VERSION = '0.1.0'; -// Grab natives up front so later prototype-pollution or clobbering can't swap them out. +// Cached up front so later prototype pollution or clobbering can't swap hasOwnProperty out. const hasOwn = Object.prototype.hasOwnProperty; -const root = typeof globalThis !== 'undefined' ? globalThis : window; -const doc = typeof document !== 'undefined' ? document : undefined; -const loc = root.location; -const own = (obj, key) => obj != null && hasOwn.call(obj, key); -const cfg = (obj, key) => (own(obj, key) ? obj[key] : undefined); -const clip = (s) => String(s).slice(0, 80); -const emsg = (e) => String(e?.message); -const TT = root.trustedTypes; -let installed = false; -let cachedStatus = null; -// Are we actually enforced? Under enforcement with no default policy yet, a sink write throws. -// Run this BEFORE we install our policy, or it would always read as "off". -function enforcementActive() { +/** True only for an own (non-inherited) property, so a polluted prototype is never consulted. */ +function own(obj, key) { + return obj != null && hasOwn.call(obj, key); +} +/** Read an own key off a config-like object, else undefined. Never walks the prototype chain. */ +function cfg(obj, key) { + return own(obj, key) ? obj[key] : undefined; +} +/** A short, safe preview of an arbitrary value, for violation reports. */ +function clip(s) { + return String(s).slice(0, 80); +} +/** + * Best-effort error message, tolerant of non-Error throws. Must never throw itself: it runs inside + * init()'s catch and several sink catches, so a hostile error whose `message` is a throwing getter + * must not be able to re-throw from here and brick init(). Falls back to a constant. + */ +function emsg(e) { try { - doc.createElement('div').innerHTML = 'x'; - return false; + return String(e?.message); } catch { - return true; + return 'unknown error'; } } -// Copy config off the caller's object, skipping keys that could pollute. Don't JSON-clone - that -// would corrupt RegExp and functions. +/** + * Copy an object's own keys, dropping the three that could pollute a prototype. Deliberately not a + * JSON clone: that would corrupt the RegExps and functions a sanitizer config may carry. + */ function shallowCopy(obj) { const out = {}; for (const k in obj) { - if (hasOwn.call(obj, k) && k !== '__proto__' && k !== 'constructor' && k !== 'prototype') + if (hasOwn.call(obj, k) && k !== '__proto__' && k !== 'constructor' && k !== 'prototype') { out[k] = obj[k]; + } } return out; } -// Test a URL against one or more patterns. String = substring match; RegExp = test. Used for both -// EXCLUDE and URL_CONFIG, always against the realm's own location.href. +/** + * Test a URL against one or more patterns. A string matches as a substring (the empty string never + * matches); a RegExp is test()ed, and a pattern that throws is treated as no match. Used for both + * EXCLUDE and URL_CONFIG, always against the realm's own location.href. + */ function urlMatches(pattern, url) { if (pattern == null) return false; @@ -55,24 +64,53 @@ function urlMatches(pattern, url) { return true; } catch { - /* ignore a pattern that throws */ + /* a pattern that throws is treated as no match */ } } } return false; } -// Best-effort CSP injection (opt-in). IMPORTANT: a CSP is honored only when the PARSER -// inserts it, so document.write during the initial parse is the only path that can actually switch -// enforcement on - and only for content parsed afterwards. A node appended after parsing is ignored by -// the CSP engine; we still add it (harmless) but report that injection did NOT take. Returns true only -// when written during parse. + +/** + * DOMFortify - bolt Trusted Types onto a legacy page so old DOM-XSS sinks get sanitized + * without touching the code. See README for the full picture; the short version: + * + * - Claims the realm's `default` Trusted Types policy and routes every HTML sink through a + * sanitizer. Script sinks (eval, javascript: URLs, script.src) are refused. + * - Does NOT switch enforcement on; a CSP does (header best, `` works). + * - Must load FIRST: the default policy is winner-takes-all. + * - Fails closed: no sanitizer means sinks throw, never leak. + * - Only covers Trusted Types sinks; inline handlers / style / URL props stay open. + */ +const VERSION = '0.5.0'; +// Natives captured up front, so later prototype pollution or clobbering can't swap them out. +const root = typeof globalThis !== 'undefined' ? globalThis : window; +const doc = typeof document !== 'undefined' ? document : undefined; +const loc = root.location; +const TT = root.trustedTypes; +let installed = false; +let cachedStatus = null; +// --- environment probes -------------------------------------------------------------------------- +// Are we actually enforced? Under enforcement with no default policy yet, a sink write throws. Must +// run BEFORE we install our policy, or it would always read as "off". +function enforcementActive() { + try { + doc.createElement('div').innerHTML = 'x'; + return false; + } + catch { + return true; + } +} +// Best-effort CSP injection (opt-in). A CSP is honored only when the PARSER inserts it, +// so document.write during the initial parse is the one path that can switch enforcement on - and only +// for content parsed afterwards. We return true only on that path. After parse we still append the node +// (harmless) but report that it did NOT take. // -// `content` is the trusted CSP directive built from config (the derived default, or META_DIRECTIVE). -// META_DIRECTIVE is developer-controlled and is expected to be trusted, but since this path reaches -// document.write we still strip the characters that could break out of the content="..." attribute or -// the tag. A real CSP directive never contains ", <, >, or newlines (single quotes, e.g. -// 'script', are kept - they are harmless inside the double-quoted attribute), so this is lossless for -// valid input and neutralizes a hostile or malformed directive. Defense in depth. +// `content` is the trusted directive built from config. META_DIRECTIVE is developer-controlled, but +// because this path reaches document.write we still strip the characters that could break out of the +// content="..." attribute. A valid directive never contains ", <, >, or newlines, so the strip is +// lossless for good input and neutralizes a hostile or malformed one. Defense in depth. function injectMeta(content) { if (!doc) return false; @@ -99,108 +137,80 @@ function injectMeta(content) { } return false; } -function init(options = {}) { - if (installed) - return cachedStatus; - installed = true; - const onv = cfg(options, 'ON_VIOLATION'); - const report = (typeof onv === 'function' ? onv : () => { }); - const status = { - version: VERSION, - ttSupported: !!TT, - enforcementActive: false, - defaultPolicyOwned: false, - sanitizerReady: false, - excluded: false, - metaInjected: false, - protected: false, - reason: '', - }; - const done = (reason, code) => { - status.protected = status.defaultPolicyOwned && status.enforcementActive && status.sanitizerReady; - status.reason = reason; - if (code) - report(code, status); - cachedStatus = Object.freeze({ ...status }); - return cachedStatus; - }; - const url = loc && typeof loc.href !== 'undefined' ? String(loc.href) : ''; - // EXCLUDE: on a matching URL, DOMFortify stays completely out of the way - no policy, no meta. It - // does NOT install a passthrough (that would be a silent XSS hole); under globally delivered - // enforcement, excluded pages are the developer's responsibility. Reported via status.excluded. - if (urlMatches(cfg(options, 'EXCLUDE'), url)) { - status.excluded = true; - return done('URL matched EXCLUDE; DOMFortify is intentionally inactive on this page.', 'excluded-by-url'); - } - if (!TT || typeof TT.createPolicy !== 'function') { - return done('Trusted Types not supported; library is inert. Sinks are NOT routed.', 'tt-unsupported'); - } - // URL_CONFIG: the first rule whose `match` hits supplies per-URL overrides. `eff(key)` reads that - // rule's own key when present, else falls back to the base config - both own-key only, so a polluted - // prototype can neither inject a rule nor loosen a refusal. - let override = null; +// --- config resolution (all own-key only, so a polluted prototype can't loosen anything) --------- +// First URL_CONFIG rule whose `match` hits, else null. Own-key reads only, so a polluted prototype +// can neither inject a rule nor reach one. +function selectOverride(options, url) { const rules = cfg(options, 'URL_CONFIG'); - if (Array.isArray(rules)) { - for (let i = 0; i < rules.length; i++) { - const r = rules[i]; - if (r && urlMatches(r.match, url)) { - override = r; - break; - } + if (!Array.isArray(rules)) + return null; + for (let i = 0; i < rules.length; i++) { + const r = rules[i]; + // Read `match` own-key only, so a polluted Object.prototype.match can't make a rule that lacks + // its own match apply to every URL. + if (r && typeof r === 'object' && urlMatches(cfg(r, 'match'), url)) { + return r; } } - const eff = (key) => (override && own(override, key) ? override[key] : cfg(options, key)); - // INJECT_META (opt-in, best-effort - see injectMeta and the README). We only attempt it when TT is - // supported; the directive lists the policies that will exist: our own `default`, plus `dompurify` - // unless a bare-function sanitizer (e.g. the native Sanitizer API) is in use. META_DIRECTIVE overrides. - if (cfg(options, 'INJECT_META') === true) { - const md = cfg(options, 'META_DIRECTIVE'); - const ttNames = typeof eff('SANITIZER') === 'function' ? 'default' : 'default dompurify'; - const directive = typeof md === 'string' && md ? md : `require-trusted-types-for 'script'; trusted-types ${ttNames};`; - status.metaInjected = injectMeta(directive); - report('meta-injection-attempted', { directive, written: status.metaInjected }); - } - status.enforcementActive = enforcementActive(); - // Resolve config once, reading own keys only so a polluted prototype can't supply a value - and, - // most importantly, can't loosen a refusal. Nothing is re-read later, so runtime clobbering can't - // retarget the policy either. URL_CONFIG overrides are applied here via `eff`. - let rawSan = eff('SANITIZER'); - if (rawSan === undefined) - rawSan = root.DOMPurify; - // DOMPurify's export is itself a callable function (the factory) that also exposes `.sanitize`, so - // check for a `.sanitize` method FIRST - otherwise we'd wrap the factory and call the wrong thing. A - // bare function (e.g. a Sanitizer-API adapter) has no `.sanitize` and falls through to the function case. - const DP = rawSan && typeof rawSan.sanitize === 'function' - ? rawSan - : typeof rawSan === 'function' - ? { sanitize: rawSan } - : null; - const rawCfg = eff('SANITIZER_CONFIG'); - const sanitizeConfig = rawCfg && typeof rawCfg === 'object' ? shallowCopy(rawCfg) : undefined; - // Sink openers count only if they're own functions, so prototype pollution can never open a sink. - const asCand = eff('ALLOW_SCRIPT'); - const asuCand = eff('ALLOW_SCRIPT_URL'); - const allowScript = typeof asCand === 'function' ? asCand : null; - const allowScriptURL = typeof asuCand === 'function' ? asuCand : null; - // Smoke-test once so a broken sanitizer fails loudly here, not silently on the first real write. It - // must return a string - a sanitizer that returns anything else would otherwise inject junk. - let sanitizerReady = false; - if (DP && typeof DP.sanitize === 'function') { - try { - sanitizerReady = typeof DP.sanitize('x', sanitizeConfig) === 'string'; - if (!sanitizerReady) - report('sanitizer-smoketest-failed', { error: 'sanitize() did not return a string' }); - } - catch (e) { - report('sanitizer-smoketest-failed', { error: emsg(e) }); + return null; +} +// Does `raw` carry a `.sanitize` method of its own (or on its own class prototype), as opposed to one +// merely inherited from Object.prototype? We walk the chain but STOP before Object.prototype, so a +// polluted Object.prototype.sanitize is never mistaken for a real sanitizer. Class-based sanitizers, +// whose method lives on their own prototype below Object.prototype, still qualify. Tolerant of a +// hostile getter on the lookup path, which is treated as "not a sanitizer". +function looksLikeSanitizer(raw) { + try { + for (let o = raw; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) { + if (own(o, 'sanitize')) + return typeof o.sanitize === 'function'; } } - status.sanitizerReady = sanitizerReady; - // `reentry` is true only while the sanitizer parses our input internally - inert and synchronous - so - // handing the raw string straight back is safe, and keeps us alive if its own sink re-enters us. + catch { + /* a throwing getter on the chain means we cannot trust it as a sanitizer */ + } + return false; +} +// Normalize whatever the caller handed us into a sanitizer with a `.sanitize` method, or null. +// DOMPurify's export is itself a callable factory that ALSO carries `.sanitize`, so we must check for +// `.sanitize` FIRST - otherwise we'd wrap the factory and call the wrong thing. A bare function (e.g. a +// Sanitizer-API adapter) has no `.sanitize` and falls through to the function case. +function resolveSanitizer(raw) { + if (raw && looksLikeSanitizer(raw)) + return raw; + if (typeof raw === 'function') + return { sanitize: raw }; + return null; +} +// The trusted-types directive for INJECT_META. META_DIRECTIVE wins; otherwise we list the policies +// that will exist: our own `default`, plus `dompurify` unless a bare-function sanitizer is in use. +function metaDirective(md, functionSanitizer) { + if (typeof md === 'string' && md) + return md; + const ttNames = functionSanitizer ? 'default' : 'default dompurify'; + return `require-trusted-types-for 'script'; trusted-types ${ttNames};`; +} +// Exercise the sanitizer once so a broken one fails loudly here, not silently on the first real write. +// It must return a string; anything else would inject junk into every sink. +function smokeTest(sanitizer, config) { + try { + const out = sanitizer.sanitize('x', config); + return typeof out === 'string' + ? { ready: true, error: null } + : { ready: false, error: 'sanitize() did not return a string' }; + } + catch (e) { + return { ready: false, error: emsg(e) }; + } +} +// --- the default policy -------------------------------------------------------------------------- +// createHTML: route through the sanitizer, fail closed on any problem. `reentry` is true only while +// the sanitizer parses our input internally (inert and synchronous), so handing the raw string back +// is safe and keeps us alive if the sanitizer's own sink re-enters us. +function makeSanitizeHTML(sanitizer, config, ready, report) { let reentry = false; - const sanitizeHTML = (s) => { - if (!sanitizerReady) { + return (s) => { + if (!ready) { report('sanitizer-unavailable', { sink: 'createHTML' }); return null; // fail closed } @@ -208,7 +218,7 @@ function init(options = {}) { return s; try { reentry = true; - return DP.sanitize(s, sanitizeConfig); + return sanitizer.sanitize(s, config); } catch (e) { report('sanitize-threw', { error: emsg(e) }); @@ -218,9 +228,11 @@ function init(options = {}) { reentry = false; } }; - // Code has no safe subset, so refuse by default. A caller hook may allow specific values; if it throws - // or returns a non-string, we refuse. - const scriptHook = (kind, fn) => (s) => { +} +// createScript / createScriptURL: code has no safe subset, so refuse by default. A caller hook may +// allow specific values; if it throws or returns a non-string, we refuse. +function makeScriptHook(kind, fn, report) { + return (s) => { if (fn) { let r; try { @@ -238,39 +250,141 @@ function init(options = {}) { report('script-sink-refused', { sink: kind, sample: clip(s) }); return null; }; - const policyDef = { - createHTML: sanitizeHTML, - createScript: scriptHook('createScript', allowScript), - createScriptURL: scriptHook('createScriptURL', allowScriptURL), +} +// --- public entry point -------------------------------------------------------------------------- +function init(options = {}) { + if (installed) + return cachedStatus; + installed = true; + // The violation reporter is observability, never control flow. Wrap it so a throwing ON_VIOLATION + // can neither abort init() (which would leave us installed with a null status) nor turn a + // fail-closed sink - one that should quietly return null - into a thrown exception. + const onv = cfg(options, 'ON_VIOLATION'); + const report = typeof onv === 'function' + ? (code, detail) => { + try { + onv(code, detail); + } + catch { + /* a misbehaving reporter must never break the policy */ + } + } + : () => { }; + const status = { + version: VERSION, + ttSupported: !!TT, + enforcementActive: false, + defaultPolicyOwned: false, + sanitizerReady: false, + excluded: false, + metaInjected: false, + protected: false, + reason: '', + }; + const done = (reason, code) => { + status.protected = status.defaultPolicyOwned && status.enforcementActive && status.sanitizerReady; + status.reason = reason; + // Freeze the snapshot first, then report it: the reporter sees exactly the authoritative status + // that gets cached and returned, and has no window to mutate the cached copy. + cachedStatus = Object.freeze({ ...status }); + if (code) + report(code, cachedStatus); + return cachedStatus; }; - // Did someone grab the default slot first? We can't evict them and won't vouch for them. - if (TT.defaultPolicy) { - return done('A default Trusted Types policy already exists; DOMFortify did NOT install and cannot vouch for it. ' + - 'Load DOMFortify first, inline in .', 'preexisting-default-policy'); - } - let ours; try { - ours = TT.createPolicy('default', policyDef); + const url = loc && typeof loc.href !== 'undefined' ? String(loc.href) : ''; + // EXCLUDE: on a match, stay completely out of the way - no policy, no meta. We do NOT install a + // passthrough (that would be a silent XSS hole); under globally delivered enforcement, excluded + // pages are the developer's responsibility. Reported via status.excluded. + if (urlMatches(cfg(options, 'EXCLUDE'), url)) { + status.excluded = true; + return done('URL matched EXCLUDE; DOMFortify is intentionally inactive on this page.', 'excluded-by-url'); + } + // INCLUDE: the allow-list complement of EXCLUDE. When set, activate ONLY on matching URLs and stay + // inactive (no policy, no meta) elsewhere. EXCLUDE is checked first, so it wins for URLs matching + // both. Like EXCLUDE, this only scopes activation safely when enforcement is page-scoped too. + const include = cfg(options, 'INCLUDE'); + if (include != null && !urlMatches(include, url)) { + status.excluded = true; + return done('URL is outside INCLUDE scope; DOMFortify is intentionally inactive on this page.', 'outside-include-scope'); + } + if (!TT || typeof TT.createPolicy !== 'function') { + return done('Trusted Types not supported; library is inert. Sinks are NOT routed.', 'tt-unsupported'); + } + // Resolve config once. `eff(key)` reads the matching URL_CONFIG rule's own key when present, else the + // base config - both own-key only. Nothing is re-read later, so runtime clobbering can't retarget + // the policy after this point either. + const override = selectOverride(options, url); + const eff = (key) => (override && own(override, key) ? override[key] : cfg(options, key)); + // INJECT_META (opt-in, best-effort - see injectMeta and the README). + if (cfg(options, 'INJECT_META') === true) { + const directive = metaDirective(cfg(options, 'META_DIRECTIVE'), typeof eff('SANITIZER') === 'function'); + status.metaInjected = injectMeta(directive); + report('meta-injection-attempted', { directive, written: status.metaInjected }); + } + status.enforcementActive = enforcementActive(); + // Sanitizer: explicit SANITIZER (possibly per-URL), else window.DOMPurify. Config is forwarded + // verbatim as the second argument, copied to drop pollution-prone keys. + let rawSan = eff('SANITIZER'); + if (rawSan === undefined) + rawSan = root.DOMPurify; + const sanitizer = resolveSanitizer(rawSan); + const rawCfg = eff('SANITIZER_CONFIG'); + const sanitizeConfig = rawCfg && typeof rawCfg === 'object' ? shallowCopy(rawCfg) : undefined; + // Sink openers count only if they're own functions, so prototype pollution can never open a sink. + const asCand = eff('ALLOW_SCRIPT'); + const asuCand = eff('ALLOW_SCRIPT_URL'); + const allowScript = typeof asCand === 'function' ? asCand : null; + const allowScriptURL = typeof asuCand === 'function' ? asuCand : null; + let sanitizerReady = false; + if (sanitizer) { + const result = smokeTest(sanitizer, sanitizeConfig); + sanitizerReady = result.ready; + if (!result.ready) + report('sanitizer-smoketest-failed', { error: result.error }); + } + status.sanitizerReady = sanitizerReady; + // createHTML closes over sanitizeConfig; the script hooks refuse unless an own-function hook allows. + const policyDef = { + createHTML: makeSanitizeHTML(sanitizer, sanitizeConfig, sanitizerReady, report), + createScript: makeScriptHook('createScript', allowScript, report), + createScriptURL: makeScriptHook('createScriptURL', allowScriptURL, report), + }; + // Did someone grab the default slot first? We can't evict them and won't vouch for them. + if (TT.defaultPolicy) { + return done('A default Trusted Types policy already exists; DOMFortify did NOT install and cannot vouch for it. ' + + 'Load DOMFortify first, inline in .', 'preexisting-default-policy'); + } + let ours; + try { + ours = TT.createPolicy('default', policyDef); + } + catch (e) { + // Throws when a default policy exists and 'allow-duplicates' is off - someone won the race. + return done(`createPolicy("default") threw (${emsg(e)}); another default policy won the race.`, 'default-policy-lost'); + } + // With 'allow-duplicates' the create can succeed yet not be the active default. + if (TT.defaultPolicy && TT.defaultPolicy !== ours) { + return done('Our policy was created but is not the active default (allow-duplicates race lost). ' + + 'Remove "allow-duplicates" from the trusted-types directive.', 'default-policy-not-active'); + } + status.defaultPolicyOwned = true; + if (!status.enforcementActive) { + return done('Default policy installed and slot locked, but TT enforcement is NOT active - sinks are not routed. ' + + 'Deliver require-trusted-types-for (header preferred).', 'enforcement-inactive'); + } + if (!sanitizerReady) { + return done('Enforcement active and slot locked, but the sanitizer is unavailable - HTML sinks will THROW ' + + '(failing closed). Bundle DOMPurify and load it before DOMFortify.', 'failing-closed'); + } + return done(`Active: HTML sinks sanitized, script sinks ${allowScript || allowScriptURL ? 'partly allowed by hooks' : 'refused'}.`); } catch (e) { - // Throws when a default policy exists and 'allow-duplicates' is off - someone won the race. - return done(`createPolicy("default") threw (${emsg(e)}); another default policy won the race.`, 'default-policy-lost'); - } - // With 'allow-duplicates' the create can succeed yet not be the active default. - if (TT.defaultPolicy && TT.defaultPolicy !== ours) { - return done('Our policy was created but is not the active default (allow-duplicates race lost). ' + - 'Remove "allow-duplicates" from the trusted-types directive.', 'default-policy-not-active'); - } - status.defaultPolicyOwned = true; - if (!status.enforcementActive) { - return done('Default policy installed and slot locked, but TT enforcement is NOT active - sinks are not routed. ' + - 'Deliver require-trusted-types-for (header preferred).', 'enforcement-inactive'); - } - if (!sanitizerReady) { - return done('Enforcement active and slot locked, but the sanitizer is unavailable - HTML sinks will THROW ' + - '(failing closed). Bundle DOMPurify and load it before DOMFortify.', 'failing-closed'); + // Defense in depth: init() must never throw or leave the library bricked with a null status. A + // hostile getter or exotic environment that slips past the guards above fails closed here, with a + // real status object still cached and returned. + return done(`init() hit an unexpected error (${emsg(e)}); failing closed.`, 'failing-closed'); } - return done(`Active: HTML sinks sanitized, script sinks ${allowScript || allowScriptURL ? 'partly allowed by hooks' : 'refused'}.`); } function status() { return cachedStatus; diff --git a/dist/fortify.cjs.js.map b/dist/fortify.cjs.js.map index c52529f..2548294 100644 --- a/dist/fortify.cjs.js.map +++ b/dist/fortify.cjs.js.map @@ -1 +1 @@ -{"version":3,"file":"fortify.cjs.js","sources":["../src/fortify.ts"],"sourcesContent":[null],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"fortify.cjs.js","sources":["../src/internal.ts","../src/fortify.ts"],"sourcesContent":[null,null],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/fortify.d.ts b/dist/fortify.d.ts index c05e889..558ff4b 100644 --- a/dist/fortify.d.ts +++ b/dist/fortify.d.ts @@ -25,7 +25,7 @@ interface UrlConfigRule { ALLOW_SCRIPT_URL?: ScriptHook; } /** Notable events emitted to `ON_VIOLATION`. */ -type ViolationCode = 'tt-unsupported' | 'sanitizer-smoketest-failed' | 'sanitizer-unavailable' | 'sanitize-threw' | 'script-hook-threw' | 'script-sink-allowed' | 'script-sink-refused' | 'preexisting-default-policy' | 'default-policy-lost' | 'default-policy-not-active' | 'enforcement-inactive' | 'excluded-by-url' | 'meta-injection-attempted' | 'failing-closed'; +type ViolationCode = 'tt-unsupported' | 'sanitizer-smoketest-failed' | 'sanitizer-unavailable' | 'sanitize-threw' | 'script-hook-threw' | 'script-sink-allowed' | 'script-sink-refused' | 'preexisting-default-policy' | 'default-policy-lost' | 'default-policy-not-active' | 'enforcement-inactive' | 'excluded-by-url' | 'outside-include-scope' | 'meta-injection-attempted' | 'failing-closed'; interface DOMFortifyConfig { /** Object with `.sanitize`, or a bare function. Defaults to `window.DOMPurify`. */ SANITIZER?: Sanitizer | SanitizeFn; @@ -42,6 +42,15 @@ interface DOMFortifyConfig { * meta. Matched against `location.href` (string = substring, RegExp = test). */ EXCLUDE?: UrlPattern | UrlPattern[]; + /** + * Allow-list complement of `EXCLUDE`. When set, DOMFortify activates ONLY on URLs that match and + * stays completely inactive (no policy, no meta) everywhere else - useful for scoping a rollout to + * specific routes. `EXCLUDE` still wins for a URL that matches both. Matched against `location.href` + * (string = substring, RegExp = test). Best paired with page-scoped enforcement (e.g. INJECT_META): + * under a globally delivered enforcement header, non-included pages have enforcement on but no + * default policy, so their sinks fail closed. + */ + INCLUDE?: UrlPattern | UrlPattern[]; /** Per-URL configuration overrides; the first matching rule's keys override the base config. */ URL_CONFIG?: UrlConfigRule[]; /** @@ -64,7 +73,7 @@ interface DOMFortifyStatus { defaultPolicyOwned: boolean; /** Whether the sanitizer passed its smoke test. */ sanitizerReady: boolean; - /** Whether the current URL matched `EXCLUDE` (DOMFortify intentionally inactive). */ + /** Whether the URL is out of scope (matched `EXCLUDE`, or fell outside `INCLUDE`); inactive here. */ excluded: boolean; /** Whether a CSP `` injection was attempted via document.write this load. */ metaInjected: boolean; @@ -78,18 +87,6 @@ interface DOMFortifyApi { status(): Readonly | null; } -/** - * DOMFortify - bolt Trusted Types onto a legacy page so old DOM-XSS sinks get sanitized - * without touching the code. See README for the full picture; the short version: - * - * - Claims the realm's `default` Trusted Types policy and routes every HTML sink through a - * sanitizer. Script sinks (eval, javascript: URLs, script.src) are refused. - * - Does NOT switch enforcement on; a CSP does (header best, `` works). - * - Must load FIRST: the default policy is winner-takes-all. - * - Fails closed: no sanitizer means sinks throw, never leak. - * - Only covers Trusted Types sinks; inline handlers / style / URL props stay open. - */ - declare function init(options?: DOMFortifyConfig): Readonly; declare function status(): Readonly | null; declare const DOMFortify: DOMFortifyApi; diff --git a/dist/fortify.es.mjs b/dist/fortify.es.mjs index 53d3432..30eae02 100644 --- a/dist/fortify.es.mjs +++ b/dist/fortify.es.mjs @@ -1,40 +1,49 @@ -/*! DOMFortify 0.1.0 | (c) Cure53 and contributors | (MPL-2.0 OR Apache-2.0) */ -const VERSION = '0.1.0'; -// Grab natives up front so later prototype-pollution or clobbering can't swap them out. +/*! DOMFortify 0.5.0 | (c) Cure53 and contributors | (MPL-2.0 OR Apache-2.0) */ +// Cached up front so later prototype pollution or clobbering can't swap hasOwnProperty out. const hasOwn = Object.prototype.hasOwnProperty; -const root = typeof globalThis !== 'undefined' ? globalThis : window; -const doc = typeof document !== 'undefined' ? document : undefined; -const loc = root.location; -const own = (obj, key) => obj != null && hasOwn.call(obj, key); -const cfg = (obj, key) => (own(obj, key) ? obj[key] : undefined); -const clip = (s) => String(s).slice(0, 80); -const emsg = (e) => String(e?.message); -const TT = root.trustedTypes; -let installed = false; -let cachedStatus = null; -// Are we actually enforced? Under enforcement with no default policy yet, a sink write throws. -// Run this BEFORE we install our policy, or it would always read as "off". -function enforcementActive() { +/** True only for an own (non-inherited) property, so a polluted prototype is never consulted. */ +function own(obj, key) { + return obj != null && hasOwn.call(obj, key); +} +/** Read an own key off a config-like object, else undefined. Never walks the prototype chain. */ +function cfg(obj, key) { + return own(obj, key) ? obj[key] : undefined; +} +/** A short, safe preview of an arbitrary value, for violation reports. */ +function clip(s) { + return String(s).slice(0, 80); +} +/** + * Best-effort error message, tolerant of non-Error throws. Must never throw itself: it runs inside + * init()'s catch and several sink catches, so a hostile error whose `message` is a throwing getter + * must not be able to re-throw from here and brick init(). Falls back to a constant. + */ +function emsg(e) { try { - doc.createElement('div').innerHTML = 'x'; - return false; + return String(e?.message); } catch { - return true; + return 'unknown error'; } } -// Copy config off the caller's object, skipping keys that could pollute. Don't JSON-clone - that -// would corrupt RegExp and functions. +/** + * Copy an object's own keys, dropping the three that could pollute a prototype. Deliberately not a + * JSON clone: that would corrupt the RegExps and functions a sanitizer config may carry. + */ function shallowCopy(obj) { const out = {}; for (const k in obj) { - if (hasOwn.call(obj, k) && k !== '__proto__' && k !== 'constructor' && k !== 'prototype') + if (hasOwn.call(obj, k) && k !== '__proto__' && k !== 'constructor' && k !== 'prototype') { out[k] = obj[k]; + } } return out; } -// Test a URL against one or more patterns. String = substring match; RegExp = test. Used for both -// EXCLUDE and URL_CONFIG, always against the realm's own location.href. +/** + * Test a URL against one or more patterns. A string matches as a substring (the empty string never + * matches); a RegExp is test()ed, and a pattern that throws is treated as no match. Used for both + * EXCLUDE and URL_CONFIG, always against the realm's own location.href. + */ function urlMatches(pattern, url) { if (pattern == null) return false; @@ -51,24 +60,53 @@ function urlMatches(pattern, url) { return true; } catch { - /* ignore a pattern that throws */ + /* a pattern that throws is treated as no match */ } } } return false; } -// Best-effort CSP injection (opt-in). IMPORTANT: a CSP is honored only when the PARSER -// inserts it, so document.write during the initial parse is the only path that can actually switch -// enforcement on - and only for content parsed afterwards. A node appended after parsing is ignored by -// the CSP engine; we still add it (harmless) but report that injection did NOT take. Returns true only -// when written during parse. + +/** + * DOMFortify - bolt Trusted Types onto a legacy page so old DOM-XSS sinks get sanitized + * without touching the code. See README for the full picture; the short version: + * + * - Claims the realm's `default` Trusted Types policy and routes every HTML sink through a + * sanitizer. Script sinks (eval, javascript: URLs, script.src) are refused. + * - Does NOT switch enforcement on; a CSP does (header best, `` works). + * - Must load FIRST: the default policy is winner-takes-all. + * - Fails closed: no sanitizer means sinks throw, never leak. + * - Only covers Trusted Types sinks; inline handlers / style / URL props stay open. + */ +const VERSION = '0.5.0'; +// Natives captured up front, so later prototype pollution or clobbering can't swap them out. +const root = typeof globalThis !== 'undefined' ? globalThis : window; +const doc = typeof document !== 'undefined' ? document : undefined; +const loc = root.location; +const TT = root.trustedTypes; +let installed = false; +let cachedStatus = null; +// --- environment probes -------------------------------------------------------------------------- +// Are we actually enforced? Under enforcement with no default policy yet, a sink write throws. Must +// run BEFORE we install our policy, or it would always read as "off". +function enforcementActive() { + try { + doc.createElement('div').innerHTML = 'x'; + return false; + } + catch { + return true; + } +} +// Best-effort CSP injection (opt-in). A CSP is honored only when the PARSER inserts it, +// so document.write during the initial parse is the one path that can switch enforcement on - and only +// for content parsed afterwards. We return true only on that path. After parse we still append the node +// (harmless) but report that it did NOT take. // -// `content` is the trusted CSP directive built from config (the derived default, or META_DIRECTIVE). -// META_DIRECTIVE is developer-controlled and is expected to be trusted, but since this path reaches -// document.write we still strip the characters that could break out of the content="..." attribute or -// the tag. A real CSP directive never contains ", <, >, or newlines (single quotes, e.g. -// 'script', are kept - they are harmless inside the double-quoted attribute), so this is lossless for -// valid input and neutralizes a hostile or malformed directive. Defense in depth. +// `content` is the trusted directive built from config. META_DIRECTIVE is developer-controlled, but +// because this path reaches document.write we still strip the characters that could break out of the +// content="..." attribute. A valid directive never contains ", <, >, or newlines, so the strip is +// lossless for good input and neutralizes a hostile or malformed one. Defense in depth. function injectMeta(content) { if (!doc) return false; @@ -95,108 +133,80 @@ function injectMeta(content) { } return false; } -function init(options = {}) { - if (installed) - return cachedStatus; - installed = true; - const onv = cfg(options, 'ON_VIOLATION'); - const report = (typeof onv === 'function' ? onv : () => { }); - const status = { - version: VERSION, - ttSupported: !!TT, - enforcementActive: false, - defaultPolicyOwned: false, - sanitizerReady: false, - excluded: false, - metaInjected: false, - protected: false, - reason: '', - }; - const done = (reason, code) => { - status.protected = status.defaultPolicyOwned && status.enforcementActive && status.sanitizerReady; - status.reason = reason; - if (code) - report(code, status); - cachedStatus = Object.freeze({ ...status }); - return cachedStatus; - }; - const url = loc && typeof loc.href !== 'undefined' ? String(loc.href) : ''; - // EXCLUDE: on a matching URL, DOMFortify stays completely out of the way - no policy, no meta. It - // does NOT install a passthrough (that would be a silent XSS hole); under globally delivered - // enforcement, excluded pages are the developer's responsibility. Reported via status.excluded. - if (urlMatches(cfg(options, 'EXCLUDE'), url)) { - status.excluded = true; - return done('URL matched EXCLUDE; DOMFortify is intentionally inactive on this page.', 'excluded-by-url'); - } - if (!TT || typeof TT.createPolicy !== 'function') { - return done('Trusted Types not supported; library is inert. Sinks are NOT routed.', 'tt-unsupported'); - } - // URL_CONFIG: the first rule whose `match` hits supplies per-URL overrides. `eff(key)` reads that - // rule's own key when present, else falls back to the base config - both own-key only, so a polluted - // prototype can neither inject a rule nor loosen a refusal. - let override = null; +// --- config resolution (all own-key only, so a polluted prototype can't loosen anything) --------- +// First URL_CONFIG rule whose `match` hits, else null. Own-key reads only, so a polluted prototype +// can neither inject a rule nor reach one. +function selectOverride(options, url) { const rules = cfg(options, 'URL_CONFIG'); - if (Array.isArray(rules)) { - for (let i = 0; i < rules.length; i++) { - const r = rules[i]; - if (r && urlMatches(r.match, url)) { - override = r; - break; - } + if (!Array.isArray(rules)) + return null; + for (let i = 0; i < rules.length; i++) { + const r = rules[i]; + // Read `match` own-key only, so a polluted Object.prototype.match can't make a rule that lacks + // its own match apply to every URL. + if (r && typeof r === 'object' && urlMatches(cfg(r, 'match'), url)) { + return r; } } - const eff = (key) => (override && own(override, key) ? override[key] : cfg(options, key)); - // INJECT_META (opt-in, best-effort - see injectMeta and the README). We only attempt it when TT is - // supported; the directive lists the policies that will exist: our own `default`, plus `dompurify` - // unless a bare-function sanitizer (e.g. the native Sanitizer API) is in use. META_DIRECTIVE overrides. - if (cfg(options, 'INJECT_META') === true) { - const md = cfg(options, 'META_DIRECTIVE'); - const ttNames = typeof eff('SANITIZER') === 'function' ? 'default' : 'default dompurify'; - const directive = typeof md === 'string' && md ? md : `require-trusted-types-for 'script'; trusted-types ${ttNames};`; - status.metaInjected = injectMeta(directive); - report('meta-injection-attempted', { directive, written: status.metaInjected }); - } - status.enforcementActive = enforcementActive(); - // Resolve config once, reading own keys only so a polluted prototype can't supply a value - and, - // most importantly, can't loosen a refusal. Nothing is re-read later, so runtime clobbering can't - // retarget the policy either. URL_CONFIG overrides are applied here via `eff`. - let rawSan = eff('SANITIZER'); - if (rawSan === undefined) - rawSan = root.DOMPurify; - // DOMPurify's export is itself a callable function (the factory) that also exposes `.sanitize`, so - // check for a `.sanitize` method FIRST - otherwise we'd wrap the factory and call the wrong thing. A - // bare function (e.g. a Sanitizer-API adapter) has no `.sanitize` and falls through to the function case. - const DP = rawSan && typeof rawSan.sanitize === 'function' - ? rawSan - : typeof rawSan === 'function' - ? { sanitize: rawSan } - : null; - const rawCfg = eff('SANITIZER_CONFIG'); - const sanitizeConfig = rawCfg && typeof rawCfg === 'object' ? shallowCopy(rawCfg) : undefined; - // Sink openers count only if they're own functions, so prototype pollution can never open a sink. - const asCand = eff('ALLOW_SCRIPT'); - const asuCand = eff('ALLOW_SCRIPT_URL'); - const allowScript = typeof asCand === 'function' ? asCand : null; - const allowScriptURL = typeof asuCand === 'function' ? asuCand : null; - // Smoke-test once so a broken sanitizer fails loudly here, not silently on the first real write. It - // must return a string - a sanitizer that returns anything else would otherwise inject junk. - let sanitizerReady = false; - if (DP && typeof DP.sanitize === 'function') { - try { - sanitizerReady = typeof DP.sanitize('x', sanitizeConfig) === 'string'; - if (!sanitizerReady) - report('sanitizer-smoketest-failed', { error: 'sanitize() did not return a string' }); - } - catch (e) { - report('sanitizer-smoketest-failed', { error: emsg(e) }); + return null; +} +// Does `raw` carry a `.sanitize` method of its own (or on its own class prototype), as opposed to one +// merely inherited from Object.prototype? We walk the chain but STOP before Object.prototype, so a +// polluted Object.prototype.sanitize is never mistaken for a real sanitizer. Class-based sanitizers, +// whose method lives on their own prototype below Object.prototype, still qualify. Tolerant of a +// hostile getter on the lookup path, which is treated as "not a sanitizer". +function looksLikeSanitizer(raw) { + try { + for (let o = raw; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) { + if (own(o, 'sanitize')) + return typeof o.sanitize === 'function'; } } - status.sanitizerReady = sanitizerReady; - // `reentry` is true only while the sanitizer parses our input internally - inert and synchronous - so - // handing the raw string straight back is safe, and keeps us alive if its own sink re-enters us. + catch { + /* a throwing getter on the chain means we cannot trust it as a sanitizer */ + } + return false; +} +// Normalize whatever the caller handed us into a sanitizer with a `.sanitize` method, or null. +// DOMPurify's export is itself a callable factory that ALSO carries `.sanitize`, so we must check for +// `.sanitize` FIRST - otherwise we'd wrap the factory and call the wrong thing. A bare function (e.g. a +// Sanitizer-API adapter) has no `.sanitize` and falls through to the function case. +function resolveSanitizer(raw) { + if (raw && looksLikeSanitizer(raw)) + return raw; + if (typeof raw === 'function') + return { sanitize: raw }; + return null; +} +// The trusted-types directive for INJECT_META. META_DIRECTIVE wins; otherwise we list the policies +// that will exist: our own `default`, plus `dompurify` unless a bare-function sanitizer is in use. +function metaDirective(md, functionSanitizer) { + if (typeof md === 'string' && md) + return md; + const ttNames = functionSanitizer ? 'default' : 'default dompurify'; + return `require-trusted-types-for 'script'; trusted-types ${ttNames};`; +} +// Exercise the sanitizer once so a broken one fails loudly here, not silently on the first real write. +// It must return a string; anything else would inject junk into every sink. +function smokeTest(sanitizer, config) { + try { + const out = sanitizer.sanitize('x', config); + return typeof out === 'string' + ? { ready: true, error: null } + : { ready: false, error: 'sanitize() did not return a string' }; + } + catch (e) { + return { ready: false, error: emsg(e) }; + } +} +// --- the default policy -------------------------------------------------------------------------- +// createHTML: route through the sanitizer, fail closed on any problem. `reentry` is true only while +// the sanitizer parses our input internally (inert and synchronous), so handing the raw string back +// is safe and keeps us alive if the sanitizer's own sink re-enters us. +function makeSanitizeHTML(sanitizer, config, ready, report) { let reentry = false; - const sanitizeHTML = (s) => { - if (!sanitizerReady) { + return (s) => { + if (!ready) { report('sanitizer-unavailable', { sink: 'createHTML' }); return null; // fail closed } @@ -204,7 +214,7 @@ function init(options = {}) { return s; try { reentry = true; - return DP.sanitize(s, sanitizeConfig); + return sanitizer.sanitize(s, config); } catch (e) { report('sanitize-threw', { error: emsg(e) }); @@ -214,9 +224,11 @@ function init(options = {}) { reentry = false; } }; - // Code has no safe subset, so refuse by default. A caller hook may allow specific values; if it throws - // or returns a non-string, we refuse. - const scriptHook = (kind, fn) => (s) => { +} +// createScript / createScriptURL: code has no safe subset, so refuse by default. A caller hook may +// allow specific values; if it throws or returns a non-string, we refuse. +function makeScriptHook(kind, fn, report) { + return (s) => { if (fn) { let r; try { @@ -234,39 +246,141 @@ function init(options = {}) { report('script-sink-refused', { sink: kind, sample: clip(s) }); return null; }; - const policyDef = { - createHTML: sanitizeHTML, - createScript: scriptHook('createScript', allowScript), - createScriptURL: scriptHook('createScriptURL', allowScriptURL), +} +// --- public entry point -------------------------------------------------------------------------- +function init(options = {}) { + if (installed) + return cachedStatus; + installed = true; + // The violation reporter is observability, never control flow. Wrap it so a throwing ON_VIOLATION + // can neither abort init() (which would leave us installed with a null status) nor turn a + // fail-closed sink - one that should quietly return null - into a thrown exception. + const onv = cfg(options, 'ON_VIOLATION'); + const report = typeof onv === 'function' + ? (code, detail) => { + try { + onv(code, detail); + } + catch { + /* a misbehaving reporter must never break the policy */ + } + } + : () => { }; + const status = { + version: VERSION, + ttSupported: !!TT, + enforcementActive: false, + defaultPolicyOwned: false, + sanitizerReady: false, + excluded: false, + metaInjected: false, + protected: false, + reason: '', + }; + const done = (reason, code) => { + status.protected = status.defaultPolicyOwned && status.enforcementActive && status.sanitizerReady; + status.reason = reason; + // Freeze the snapshot first, then report it: the reporter sees exactly the authoritative status + // that gets cached and returned, and has no window to mutate the cached copy. + cachedStatus = Object.freeze({ ...status }); + if (code) + report(code, cachedStatus); + return cachedStatus; }; - // Did someone grab the default slot first? We can't evict them and won't vouch for them. - if (TT.defaultPolicy) { - return done('A default Trusted Types policy already exists; DOMFortify did NOT install and cannot vouch for it. ' + - 'Load DOMFortify first, inline in .', 'preexisting-default-policy'); - } - let ours; try { - ours = TT.createPolicy('default', policyDef); + const url = loc && typeof loc.href !== 'undefined' ? String(loc.href) : ''; + // EXCLUDE: on a match, stay completely out of the way - no policy, no meta. We do NOT install a + // passthrough (that would be a silent XSS hole); under globally delivered enforcement, excluded + // pages are the developer's responsibility. Reported via status.excluded. + if (urlMatches(cfg(options, 'EXCLUDE'), url)) { + status.excluded = true; + return done('URL matched EXCLUDE; DOMFortify is intentionally inactive on this page.', 'excluded-by-url'); + } + // INCLUDE: the allow-list complement of EXCLUDE. When set, activate ONLY on matching URLs and stay + // inactive (no policy, no meta) elsewhere. EXCLUDE is checked first, so it wins for URLs matching + // both. Like EXCLUDE, this only scopes activation safely when enforcement is page-scoped too. + const include = cfg(options, 'INCLUDE'); + if (include != null && !urlMatches(include, url)) { + status.excluded = true; + return done('URL is outside INCLUDE scope; DOMFortify is intentionally inactive on this page.', 'outside-include-scope'); + } + if (!TT || typeof TT.createPolicy !== 'function') { + return done('Trusted Types not supported; library is inert. Sinks are NOT routed.', 'tt-unsupported'); + } + // Resolve config once. `eff(key)` reads the matching URL_CONFIG rule's own key when present, else the + // base config - both own-key only. Nothing is re-read later, so runtime clobbering can't retarget + // the policy after this point either. + const override = selectOverride(options, url); + const eff = (key) => (override && own(override, key) ? override[key] : cfg(options, key)); + // INJECT_META (opt-in, best-effort - see injectMeta and the README). + if (cfg(options, 'INJECT_META') === true) { + const directive = metaDirective(cfg(options, 'META_DIRECTIVE'), typeof eff('SANITIZER') === 'function'); + status.metaInjected = injectMeta(directive); + report('meta-injection-attempted', { directive, written: status.metaInjected }); + } + status.enforcementActive = enforcementActive(); + // Sanitizer: explicit SANITIZER (possibly per-URL), else window.DOMPurify. Config is forwarded + // verbatim as the second argument, copied to drop pollution-prone keys. + let rawSan = eff('SANITIZER'); + if (rawSan === undefined) + rawSan = root.DOMPurify; + const sanitizer = resolveSanitizer(rawSan); + const rawCfg = eff('SANITIZER_CONFIG'); + const sanitizeConfig = rawCfg && typeof rawCfg === 'object' ? shallowCopy(rawCfg) : undefined; + // Sink openers count only if they're own functions, so prototype pollution can never open a sink. + const asCand = eff('ALLOW_SCRIPT'); + const asuCand = eff('ALLOW_SCRIPT_URL'); + const allowScript = typeof asCand === 'function' ? asCand : null; + const allowScriptURL = typeof asuCand === 'function' ? asuCand : null; + let sanitizerReady = false; + if (sanitizer) { + const result = smokeTest(sanitizer, sanitizeConfig); + sanitizerReady = result.ready; + if (!result.ready) + report('sanitizer-smoketest-failed', { error: result.error }); + } + status.sanitizerReady = sanitizerReady; + // createHTML closes over sanitizeConfig; the script hooks refuse unless an own-function hook allows. + const policyDef = { + createHTML: makeSanitizeHTML(sanitizer, sanitizeConfig, sanitizerReady, report), + createScript: makeScriptHook('createScript', allowScript, report), + createScriptURL: makeScriptHook('createScriptURL', allowScriptURL, report), + }; + // Did someone grab the default slot first? We can't evict them and won't vouch for them. + if (TT.defaultPolicy) { + return done('A default Trusted Types policy already exists; DOMFortify did NOT install and cannot vouch for it. ' + + 'Load DOMFortify first, inline in .', 'preexisting-default-policy'); + } + let ours; + try { + ours = TT.createPolicy('default', policyDef); + } + catch (e) { + // Throws when a default policy exists and 'allow-duplicates' is off - someone won the race. + return done(`createPolicy("default") threw (${emsg(e)}); another default policy won the race.`, 'default-policy-lost'); + } + // With 'allow-duplicates' the create can succeed yet not be the active default. + if (TT.defaultPolicy && TT.defaultPolicy !== ours) { + return done('Our policy was created but is not the active default (allow-duplicates race lost). ' + + 'Remove "allow-duplicates" from the trusted-types directive.', 'default-policy-not-active'); + } + status.defaultPolicyOwned = true; + if (!status.enforcementActive) { + return done('Default policy installed and slot locked, but TT enforcement is NOT active - sinks are not routed. ' + + 'Deliver require-trusted-types-for (header preferred).', 'enforcement-inactive'); + } + if (!sanitizerReady) { + return done('Enforcement active and slot locked, but the sanitizer is unavailable - HTML sinks will THROW ' + + '(failing closed). Bundle DOMPurify and load it before DOMFortify.', 'failing-closed'); + } + return done(`Active: HTML sinks sanitized, script sinks ${allowScript || allowScriptURL ? 'partly allowed by hooks' : 'refused'}.`); } catch (e) { - // Throws when a default policy exists and 'allow-duplicates' is off - someone won the race. - return done(`createPolicy("default") threw (${emsg(e)}); another default policy won the race.`, 'default-policy-lost'); - } - // With 'allow-duplicates' the create can succeed yet not be the active default. - if (TT.defaultPolicy && TT.defaultPolicy !== ours) { - return done('Our policy was created but is not the active default (allow-duplicates race lost). ' + - 'Remove "allow-duplicates" from the trusted-types directive.', 'default-policy-not-active'); - } - status.defaultPolicyOwned = true; - if (!status.enforcementActive) { - return done('Default policy installed and slot locked, but TT enforcement is NOT active - sinks are not routed. ' + - 'Deliver require-trusted-types-for (header preferred).', 'enforcement-inactive'); - } - if (!sanitizerReady) { - return done('Enforcement active and slot locked, but the sanitizer is unavailable - HTML sinks will THROW ' + - '(failing closed). Bundle DOMPurify and load it before DOMFortify.', 'failing-closed'); + // Defense in depth: init() must never throw or leave the library bricked with a null status. A + // hostile getter or exotic environment that slips past the guards above fails closed here, with a + // real status object still cached and returned. + return done(`init() hit an unexpected error (${emsg(e)}); failing closed.`, 'failing-closed'); } - return done(`Active: HTML sinks sanitized, script sinks ${allowScript || allowScriptURL ? 'partly allowed by hooks' : 'refused'}.`); } function status() { return cachedStatus; diff --git a/dist/fortify.es.mjs.map b/dist/fortify.es.mjs.map index c28b7ab..7ed7599 100644 --- a/dist/fortify.es.mjs.map +++ b/dist/fortify.es.mjs.map @@ -1 +1 @@ -{"version":3,"file":"fortify.es.mjs","sources":["../src/fortify.ts"],"sourcesContent":[null],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"fortify.es.mjs","sources":["../src/internal.ts","../src/fortify.ts"],"sourcesContent":[null,null],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/fortify.js b/dist/fortify.js index afa7c59..6aaff18 100644 --- a/dist/fortify.js +++ b/dist/fortify.js @@ -1,43 +1,52 @@ -/*! DOMFortify 0.1.0 | (c) Cure53 and contributors | (MPL-2.0 OR Apache-2.0) */ +/*! DOMFortify 0.5.0 | (c) Cure53 and contributors | (MPL-2.0 OR Apache-2.0) */ (function () { 'use strict'; - const VERSION = '0.1.0'; - // Grab natives up front so later prototype-pollution or clobbering can't swap them out. + // Cached up front so later prototype pollution or clobbering can't swap hasOwnProperty out. const hasOwn = Object.prototype.hasOwnProperty; - const root = typeof globalThis !== 'undefined' ? globalThis : window; - const doc = typeof document !== 'undefined' ? document : undefined; - const loc = root.location; - const own = (obj, key) => obj != null && hasOwn.call(obj, key); - const cfg = (obj, key) => (own(obj, key) ? obj[key] : undefined); - const clip = (s) => String(s).slice(0, 80); - const emsg = (e) => String(e?.message); - const TT = root.trustedTypes; - let installed = false; - let cachedStatus = null; - // Are we actually enforced? Under enforcement with no default policy yet, a sink write throws. - // Run this BEFORE we install our policy, or it would always read as "off". - function enforcementActive() { + /** True only for an own (non-inherited) property, so a polluted prototype is never consulted. */ + function own(obj, key) { + return obj != null && hasOwn.call(obj, key); + } + /** Read an own key off a config-like object, else undefined. Never walks the prototype chain. */ + function cfg(obj, key) { + return own(obj, key) ? obj[key] : undefined; + } + /** A short, safe preview of an arbitrary value, for violation reports. */ + function clip(s) { + return String(s).slice(0, 80); + } + /** + * Best-effort error message, tolerant of non-Error throws. Must never throw itself: it runs inside + * init()'s catch and several sink catches, so a hostile error whose `message` is a throwing getter + * must not be able to re-throw from here and brick init(). Falls back to a constant. + */ + function emsg(e) { try { - doc.createElement('div').innerHTML = 'x'; - return false; + return String(e?.message); } catch { - return true; + return 'unknown error'; } } - // Copy config off the caller's object, skipping keys that could pollute. Don't JSON-clone - that - // would corrupt RegExp and functions. + /** + * Copy an object's own keys, dropping the three that could pollute a prototype. Deliberately not a + * JSON clone: that would corrupt the RegExps and functions a sanitizer config may carry. + */ function shallowCopy(obj) { const out = {}; for (const k in obj) { - if (hasOwn.call(obj, k) && k !== '__proto__' && k !== 'constructor' && k !== 'prototype') + if (hasOwn.call(obj, k) && k !== '__proto__' && k !== 'constructor' && k !== 'prototype') { out[k] = obj[k]; + } } return out; } - // Test a URL against one or more patterns. String = substring match; RegExp = test. Used for both - // EXCLUDE and URL_CONFIG, always against the realm's own location.href. + /** + * Test a URL against one or more patterns. A string matches as a substring (the empty string never + * matches); a RegExp is test()ed, and a pattern that throws is treated as no match. Used for both + * EXCLUDE and URL_CONFIG, always against the realm's own location.href. + */ function urlMatches(pattern, url) { if (pattern == null) return false; @@ -54,24 +63,53 @@ return true; } catch { - /* ignore a pattern that throws */ + /* a pattern that throws is treated as no match */ } } } return false; } - // Best-effort CSP injection (opt-in). IMPORTANT: a CSP is honored only when the PARSER - // inserts it, so document.write during the initial parse is the only path that can actually switch - // enforcement on - and only for content parsed afterwards. A node appended after parsing is ignored by - // the CSP engine; we still add it (harmless) but report that injection did NOT take. Returns true only - // when written during parse. + + /** + * DOMFortify - bolt Trusted Types onto a legacy page so old DOM-XSS sinks get sanitized + * without touching the code. See README for the full picture; the short version: + * + * - Claims the realm's `default` Trusted Types policy and routes every HTML sink through a + * sanitizer. Script sinks (eval, javascript: URLs, script.src) are refused. + * - Does NOT switch enforcement on; a CSP does (header best, `` works). + * - Must load FIRST: the default policy is winner-takes-all. + * - Fails closed: no sanitizer means sinks throw, never leak. + * - Only covers Trusted Types sinks; inline handlers / style / URL props stay open. + */ + const VERSION = '0.5.0'; + // Natives captured up front, so later prototype pollution or clobbering can't swap them out. + const root = typeof globalThis !== 'undefined' ? globalThis : window; + const doc = typeof document !== 'undefined' ? document : undefined; + const loc = root.location; + const TT = root.trustedTypes; + let installed = false; + let cachedStatus = null; + // --- environment probes -------------------------------------------------------------------------- + // Are we actually enforced? Under enforcement with no default policy yet, a sink write throws. Must + // run BEFORE we install our policy, or it would always read as "off". + function enforcementActive() { + try { + doc.createElement('div').innerHTML = 'x'; + return false; + } + catch { + return true; + } + } + // Best-effort CSP injection (opt-in). A CSP is honored only when the PARSER inserts it, + // so document.write during the initial parse is the one path that can switch enforcement on - and only + // for content parsed afterwards. We return true only on that path. After parse we still append the node + // (harmless) but report that it did NOT take. // - // `content` is the trusted CSP directive built from config (the derived default, or META_DIRECTIVE). - // META_DIRECTIVE is developer-controlled and is expected to be trusted, but since this path reaches - // document.write we still strip the characters that could break out of the content="..." attribute or - // the tag. A real CSP directive never contains ", <, >, or newlines (single quotes, e.g. - // 'script', are kept - they are harmless inside the double-quoted attribute), so this is lossless for - // valid input and neutralizes a hostile or malformed directive. Defense in depth. + // `content` is the trusted directive built from config. META_DIRECTIVE is developer-controlled, but + // because this path reaches document.write we still strip the characters that could break out of the + // content="..." attribute. A valid directive never contains ", <, >, or newlines, so the strip is + // lossless for good input and neutralizes a hostile or malformed one. Defense in depth. function injectMeta(content) { if (!doc) return false; @@ -98,108 +136,80 @@ } return false; } - function init(options = {}) { - if (installed) - return cachedStatus; - installed = true; - const onv = cfg(options, 'ON_VIOLATION'); - const report = (typeof onv === 'function' ? onv : () => { }); - const status = { - version: VERSION, - ttSupported: !!TT, - enforcementActive: false, - defaultPolicyOwned: false, - sanitizerReady: false, - excluded: false, - metaInjected: false, - protected: false, - reason: '', - }; - const done = (reason, code) => { - status.protected = status.defaultPolicyOwned && status.enforcementActive && status.sanitizerReady; - status.reason = reason; - if (code) - report(code, status); - cachedStatus = Object.freeze({ ...status }); - return cachedStatus; - }; - const url = loc && typeof loc.href !== 'undefined' ? String(loc.href) : ''; - // EXCLUDE: on a matching URL, DOMFortify stays completely out of the way - no policy, no meta. It - // does NOT install a passthrough (that would be a silent XSS hole); under globally delivered - // enforcement, excluded pages are the developer's responsibility. Reported via status.excluded. - if (urlMatches(cfg(options, 'EXCLUDE'), url)) { - status.excluded = true; - return done('URL matched EXCLUDE; DOMFortify is intentionally inactive on this page.', 'excluded-by-url'); - } - if (!TT || typeof TT.createPolicy !== 'function') { - return done('Trusted Types not supported; library is inert. Sinks are NOT routed.', 'tt-unsupported'); - } - // URL_CONFIG: the first rule whose `match` hits supplies per-URL overrides. `eff(key)` reads that - // rule's own key when present, else falls back to the base config - both own-key only, so a polluted - // prototype can neither inject a rule nor loosen a refusal. - let override = null; + // --- config resolution (all own-key only, so a polluted prototype can't loosen anything) --------- + // First URL_CONFIG rule whose `match` hits, else null. Own-key reads only, so a polluted prototype + // can neither inject a rule nor reach one. + function selectOverride(options, url) { const rules = cfg(options, 'URL_CONFIG'); - if (Array.isArray(rules)) { - for (let i = 0; i < rules.length; i++) { - const r = rules[i]; - if (r && urlMatches(r.match, url)) { - override = r; - break; - } + if (!Array.isArray(rules)) + return null; + for (let i = 0; i < rules.length; i++) { + const r = rules[i]; + // Read `match` own-key only, so a polluted Object.prototype.match can't make a rule that lacks + // its own match apply to every URL. + if (r && typeof r === 'object' && urlMatches(cfg(r, 'match'), url)) { + return r; } } - const eff = (key) => (override && own(override, key) ? override[key] : cfg(options, key)); - // INJECT_META (opt-in, best-effort - see injectMeta and the README). We only attempt it when TT is - // supported; the directive lists the policies that will exist: our own `default`, plus `dompurify` - // unless a bare-function sanitizer (e.g. the native Sanitizer API) is in use. META_DIRECTIVE overrides. - if (cfg(options, 'INJECT_META') === true) { - const md = cfg(options, 'META_DIRECTIVE'); - const ttNames = typeof eff('SANITIZER') === 'function' ? 'default' : 'default dompurify'; - const directive = typeof md === 'string' && md ? md : `require-trusted-types-for 'script'; trusted-types ${ttNames};`; - status.metaInjected = injectMeta(directive); - report('meta-injection-attempted', { directive, written: status.metaInjected }); - } - status.enforcementActive = enforcementActive(); - // Resolve config once, reading own keys only so a polluted prototype can't supply a value - and, - // most importantly, can't loosen a refusal. Nothing is re-read later, so runtime clobbering can't - // retarget the policy either. URL_CONFIG overrides are applied here via `eff`. - let rawSan = eff('SANITIZER'); - if (rawSan === undefined) - rawSan = root.DOMPurify; - // DOMPurify's export is itself a callable function (the factory) that also exposes `.sanitize`, so - // check for a `.sanitize` method FIRST - otherwise we'd wrap the factory and call the wrong thing. A - // bare function (e.g. a Sanitizer-API adapter) has no `.sanitize` and falls through to the function case. - const DP = rawSan && typeof rawSan.sanitize === 'function' - ? rawSan - : typeof rawSan === 'function' - ? { sanitize: rawSan } - : null; - const rawCfg = eff('SANITIZER_CONFIG'); - const sanitizeConfig = rawCfg && typeof rawCfg === 'object' ? shallowCopy(rawCfg) : undefined; - // Sink openers count only if they're own functions, so prototype pollution can never open a sink. - const asCand = eff('ALLOW_SCRIPT'); - const asuCand = eff('ALLOW_SCRIPT_URL'); - const allowScript = typeof asCand === 'function' ? asCand : null; - const allowScriptURL = typeof asuCand === 'function' ? asuCand : null; - // Smoke-test once so a broken sanitizer fails loudly here, not silently on the first real write. It - // must return a string - a sanitizer that returns anything else would otherwise inject junk. - let sanitizerReady = false; - if (DP && typeof DP.sanitize === 'function') { - try { - sanitizerReady = typeof DP.sanitize('x', sanitizeConfig) === 'string'; - if (!sanitizerReady) - report('sanitizer-smoketest-failed', { error: 'sanitize() did not return a string' }); - } - catch (e) { - report('sanitizer-smoketest-failed', { error: emsg(e) }); + return null; + } + // Does `raw` carry a `.sanitize` method of its own (or on its own class prototype), as opposed to one + // merely inherited from Object.prototype? We walk the chain but STOP before Object.prototype, so a + // polluted Object.prototype.sanitize is never mistaken for a real sanitizer. Class-based sanitizers, + // whose method lives on their own prototype below Object.prototype, still qualify. Tolerant of a + // hostile getter on the lookup path, which is treated as "not a sanitizer". + function looksLikeSanitizer(raw) { + try { + for (let o = raw; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) { + if (own(o, 'sanitize')) + return typeof o.sanitize === 'function'; } } - status.sanitizerReady = sanitizerReady; - // `reentry` is true only while the sanitizer parses our input internally - inert and synchronous - so - // handing the raw string straight back is safe, and keeps us alive if its own sink re-enters us. + catch { + /* a throwing getter on the chain means we cannot trust it as a sanitizer */ + } + return false; + } + // Normalize whatever the caller handed us into a sanitizer with a `.sanitize` method, or null. + // DOMPurify's export is itself a callable factory that ALSO carries `.sanitize`, so we must check for + // `.sanitize` FIRST - otherwise we'd wrap the factory and call the wrong thing. A bare function (e.g. a + // Sanitizer-API adapter) has no `.sanitize` and falls through to the function case. + function resolveSanitizer(raw) { + if (raw && looksLikeSanitizer(raw)) + return raw; + if (typeof raw === 'function') + return { sanitize: raw }; + return null; + } + // The trusted-types directive for INJECT_META. META_DIRECTIVE wins; otherwise we list the policies + // that will exist: our own `default`, plus `dompurify` unless a bare-function sanitizer is in use. + function metaDirective(md, functionSanitizer) { + if (typeof md === 'string' && md) + return md; + const ttNames = functionSanitizer ? 'default' : 'default dompurify'; + return `require-trusted-types-for 'script'; trusted-types ${ttNames};`; + } + // Exercise the sanitizer once so a broken one fails loudly here, not silently on the first real write. + // It must return a string; anything else would inject junk into every sink. + function smokeTest(sanitizer, config) { + try { + const out = sanitizer.sanitize('x', config); + return typeof out === 'string' + ? { ready: true, error: null } + : { ready: false, error: 'sanitize() did not return a string' }; + } + catch (e) { + return { ready: false, error: emsg(e) }; + } + } + // --- the default policy -------------------------------------------------------------------------- + // createHTML: route through the sanitizer, fail closed on any problem. `reentry` is true only while + // the sanitizer parses our input internally (inert and synchronous), so handing the raw string back + // is safe and keeps us alive if the sanitizer's own sink re-enters us. + function makeSanitizeHTML(sanitizer, config, ready, report) { let reentry = false; - const sanitizeHTML = (s) => { - if (!sanitizerReady) { + return (s) => { + if (!ready) { report('sanitizer-unavailable', { sink: 'createHTML' }); return null; // fail closed } @@ -207,7 +217,7 @@ return s; try { reentry = true; - return DP.sanitize(s, sanitizeConfig); + return sanitizer.sanitize(s, config); } catch (e) { report('sanitize-threw', { error: emsg(e) }); @@ -217,9 +227,11 @@ reentry = false; } }; - // Code has no safe subset, so refuse by default. A caller hook may allow specific values; if it throws - // or returns a non-string, we refuse. - const scriptHook = (kind, fn) => (s) => { + } + // createScript / createScriptURL: code has no safe subset, so refuse by default. A caller hook may + // allow specific values; if it throws or returns a non-string, we refuse. + function makeScriptHook(kind, fn, report) { + return (s) => { if (fn) { let r; try { @@ -237,39 +249,141 @@ report('script-sink-refused', { sink: kind, sample: clip(s) }); return null; }; - const policyDef = { - createHTML: sanitizeHTML, - createScript: scriptHook('createScript', allowScript), - createScriptURL: scriptHook('createScriptURL', allowScriptURL), + } + // --- public entry point -------------------------------------------------------------------------- + function init(options = {}) { + if (installed) + return cachedStatus; + installed = true; + // The violation reporter is observability, never control flow. Wrap it so a throwing ON_VIOLATION + // can neither abort init() (which would leave us installed with a null status) nor turn a + // fail-closed sink - one that should quietly return null - into a thrown exception. + const onv = cfg(options, 'ON_VIOLATION'); + const report = typeof onv === 'function' + ? (code, detail) => { + try { + onv(code, detail); + } + catch { + /* a misbehaving reporter must never break the policy */ + } + } + : () => { }; + const status = { + version: VERSION, + ttSupported: !!TT, + enforcementActive: false, + defaultPolicyOwned: false, + sanitizerReady: false, + excluded: false, + metaInjected: false, + protected: false, + reason: '', + }; + const done = (reason, code) => { + status.protected = status.defaultPolicyOwned && status.enforcementActive && status.sanitizerReady; + status.reason = reason; + // Freeze the snapshot first, then report it: the reporter sees exactly the authoritative status + // that gets cached and returned, and has no window to mutate the cached copy. + cachedStatus = Object.freeze({ ...status }); + if (code) + report(code, cachedStatus); + return cachedStatus; }; - // Did someone grab the default slot first? We can't evict them and won't vouch for them. - if (TT.defaultPolicy) { - return done('A default Trusted Types policy already exists; DOMFortify did NOT install and cannot vouch for it. ' + - 'Load DOMFortify first, inline in .', 'preexisting-default-policy'); - } - let ours; try { - ours = TT.createPolicy('default', policyDef); + const url = loc && typeof loc.href !== 'undefined' ? String(loc.href) : ''; + // EXCLUDE: on a match, stay completely out of the way - no policy, no meta. We do NOT install a + // passthrough (that would be a silent XSS hole); under globally delivered enforcement, excluded + // pages are the developer's responsibility. Reported via status.excluded. + if (urlMatches(cfg(options, 'EXCLUDE'), url)) { + status.excluded = true; + return done('URL matched EXCLUDE; DOMFortify is intentionally inactive on this page.', 'excluded-by-url'); + } + // INCLUDE: the allow-list complement of EXCLUDE. When set, activate ONLY on matching URLs and stay + // inactive (no policy, no meta) elsewhere. EXCLUDE is checked first, so it wins for URLs matching + // both. Like EXCLUDE, this only scopes activation safely when enforcement is page-scoped too. + const include = cfg(options, 'INCLUDE'); + if (include != null && !urlMatches(include, url)) { + status.excluded = true; + return done('URL is outside INCLUDE scope; DOMFortify is intentionally inactive on this page.', 'outside-include-scope'); + } + if (!TT || typeof TT.createPolicy !== 'function') { + return done('Trusted Types not supported; library is inert. Sinks are NOT routed.', 'tt-unsupported'); + } + // Resolve config once. `eff(key)` reads the matching URL_CONFIG rule's own key when present, else the + // base config - both own-key only. Nothing is re-read later, so runtime clobbering can't retarget + // the policy after this point either. + const override = selectOverride(options, url); + const eff = (key) => (override && own(override, key) ? override[key] : cfg(options, key)); + // INJECT_META (opt-in, best-effort - see injectMeta and the README). + if (cfg(options, 'INJECT_META') === true) { + const directive = metaDirective(cfg(options, 'META_DIRECTIVE'), typeof eff('SANITIZER') === 'function'); + status.metaInjected = injectMeta(directive); + report('meta-injection-attempted', { directive, written: status.metaInjected }); + } + status.enforcementActive = enforcementActive(); + // Sanitizer: explicit SANITIZER (possibly per-URL), else window.DOMPurify. Config is forwarded + // verbatim as the second argument, copied to drop pollution-prone keys. + let rawSan = eff('SANITIZER'); + if (rawSan === undefined) + rawSan = root.DOMPurify; + const sanitizer = resolveSanitizer(rawSan); + const rawCfg = eff('SANITIZER_CONFIG'); + const sanitizeConfig = rawCfg && typeof rawCfg === 'object' ? shallowCopy(rawCfg) : undefined; + // Sink openers count only if they're own functions, so prototype pollution can never open a sink. + const asCand = eff('ALLOW_SCRIPT'); + const asuCand = eff('ALLOW_SCRIPT_URL'); + const allowScript = typeof asCand === 'function' ? asCand : null; + const allowScriptURL = typeof asuCand === 'function' ? asuCand : null; + let sanitizerReady = false; + if (sanitizer) { + const result = smokeTest(sanitizer, sanitizeConfig); + sanitizerReady = result.ready; + if (!result.ready) + report('sanitizer-smoketest-failed', { error: result.error }); + } + status.sanitizerReady = sanitizerReady; + // createHTML closes over sanitizeConfig; the script hooks refuse unless an own-function hook allows. + const policyDef = { + createHTML: makeSanitizeHTML(sanitizer, sanitizeConfig, sanitizerReady, report), + createScript: makeScriptHook('createScript', allowScript, report), + createScriptURL: makeScriptHook('createScriptURL', allowScriptURL, report), + }; + // Did someone grab the default slot first? We can't evict them and won't vouch for them. + if (TT.defaultPolicy) { + return done('A default Trusted Types policy already exists; DOMFortify did NOT install and cannot vouch for it. ' + + 'Load DOMFortify first, inline in .', 'preexisting-default-policy'); + } + let ours; + try { + ours = TT.createPolicy('default', policyDef); + } + catch (e) { + // Throws when a default policy exists and 'allow-duplicates' is off - someone won the race. + return done(`createPolicy("default") threw (${emsg(e)}); another default policy won the race.`, 'default-policy-lost'); + } + // With 'allow-duplicates' the create can succeed yet not be the active default. + if (TT.defaultPolicy && TT.defaultPolicy !== ours) { + return done('Our policy was created but is not the active default (allow-duplicates race lost). ' + + 'Remove "allow-duplicates" from the trusted-types directive.', 'default-policy-not-active'); + } + status.defaultPolicyOwned = true; + if (!status.enforcementActive) { + return done('Default policy installed and slot locked, but TT enforcement is NOT active - sinks are not routed. ' + + 'Deliver require-trusted-types-for (header preferred).', 'enforcement-inactive'); + } + if (!sanitizerReady) { + return done('Enforcement active and slot locked, but the sanitizer is unavailable - HTML sinks will THROW ' + + '(failing closed). Bundle DOMPurify and load it before DOMFortify.', 'failing-closed'); + } + return done(`Active: HTML sinks sanitized, script sinks ${allowScript || allowScriptURL ? 'partly allowed by hooks' : 'refused'}.`); } catch (e) { - // Throws when a default policy exists and 'allow-duplicates' is off - someone won the race. - return done(`createPolicy("default") threw (${emsg(e)}); another default policy won the race.`, 'default-policy-lost'); - } - // With 'allow-duplicates' the create can succeed yet not be the active default. - if (TT.defaultPolicy && TT.defaultPolicy !== ours) { - return done('Our policy was created but is not the active default (allow-duplicates race lost). ' + - 'Remove "allow-duplicates" from the trusted-types directive.', 'default-policy-not-active'); - } - status.defaultPolicyOwned = true; - if (!status.enforcementActive) { - return done('Default policy installed and slot locked, but TT enforcement is NOT active - sinks are not routed. ' + - 'Deliver require-trusted-types-for (header preferred).', 'enforcement-inactive'); - } - if (!sanitizerReady) { - return done('Enforcement active and slot locked, but the sanitizer is unavailable - HTML sinks will THROW ' + - '(failing closed). Bundle DOMPurify and load it before DOMFortify.', 'failing-closed'); + // Defense in depth: init() must never throw or leave the library bricked with a null status. A + // hostile getter or exotic environment that slips past the guards above fails closed here, with a + // real status object still cached and returned. + return done(`init() hit an unexpected error (${emsg(e)}); failing closed.`, 'failing-closed'); } - return done(`Active: HTML sinks sanitized, script sinks ${allowScript || allowScriptURL ? 'partly allowed by hooks' : 'refused'}.`); } function status() { return cachedStatus; diff --git a/dist/fortify.js.map b/dist/fortify.js.map index 8f9f552..c13415f 100644 --- a/dist/fortify.js.map +++ b/dist/fortify.js.map @@ -1 +1 @@ -{"version":3,"file":"fortify.js","sources":["../src/fortify.ts","../src/auto.ts"],"sourcesContent":[null,null],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"fortify.js","sources":["../src/internal.ts","../src/fortify.ts","../src/auto.ts"],"sourcesContent":[null,null,null],"names":[],"mappings":";;;;IAOA;IACA,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,cAAc;IAE9C;IACM,SAAU,GAAG,CAAC,GAAY,EAAE,GAAW,EAAA;IAC3C,IAAA,OAAO,GAAG,IAAI,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC;IAC7C;IAEA;IACM,SAAU,GAAG,CAAC,GAAY,EAAE,GAAW,EAAA;IAC3C,IAAA,OAAO,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,GAAI,GAA+B,CAAC,GAAG,CAAC,GAAG,SAAS;IAC1E;IAEA;IACM,SAAU,IAAI,CAAC,CAAU,EAAA;QAC7B,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;IAC/B;IAEA;;;;IAIG;IACG,SAAU,IAAI,CAAC,CAAU,EAAA;IAC7B,IAAA,IAAI;IACF,QAAA,OAAO,MAAM,CAAE,CAAuC,EAAE,OAAO,CAAC;QAClE;IAAE,IAAA,MAAM;IACN,QAAA,OAAO,eAAe;QACxB;IACF;IAEA;;;IAGG;IACG,SAAU,WAAW,CAAC,GAA4B,EAAA;QACtD,MAAM,GAAG,GAA4B,EAAE;IACvC,IAAA,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE;YACnB,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC,KAAK,aAAa,IAAI,CAAC,KAAK,WAAW,EAAE;gBACxF,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YACjB;QACF;IACA,IAAA,OAAO,GAAG;IACZ;IAEA;;;;IAIG;IACG,SAAU,UAAU,CAAC,OAA8C,EAAE,GAAW,EAAA;QACpF,IAAI,OAAO,IAAI,IAAI;IAAE,QAAA,OAAO,KAAK;IACjC,IAAA,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,CAAC,OAAO,CAAC;IACzD,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;IACpC,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IACjB,QAAA,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE;IACzB,YAAA,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE;IAAE,gBAAA,OAAO,IAAI;YACpD;IAAO,aAAA,IAAI,CAAC,YAAY,MAAM,EAAE;IAC9B,YAAA,IAAI;IACF,gBAAA,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;IAAE,oBAAA,OAAO,IAAI;gBAC9B;IAAE,YAAA,MAAM;;gBAER;YACF;QACF;IACA,IAAA,OAAO,KAAK;IACd;;ICzEA;;;;;;;;;;IAUG;IAaH,MAAM,OAAO,GAAG,OAAa;IAS7B;IACA,MAAM,IAAI,GACR,OAAO,UAAU,KAAK,WAAW,GAAG,UAAU,GAAI,MAAuC;IAC3F,MAAM,GAAG,GAAyB,OAAO,QAAQ,KAAK,WAAW,GAAG,QAAQ,GAAG,SAAS;IACxF,MAAM,GAAG,GAAoC,IAAqD,CAAC,QAAQ;IAC3G,MAAM,EAAE,GAAI,IAAgD,CAAC,YAAY;IAEzE,IAAI,SAAS,GAAG,KAAK;IACrB,IAAI,YAAY,GAAsC,IAAI;IAE1D;IAEA;IACA;IACA,SAAS,iBAAiB,GAAA;IACxB,IAAA,IAAI;YACD,GAAgB,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,GAAG;IACtD,QAAA,OAAO,KAAK;QACd;IAAE,IAAA,MAAM;IACN,QAAA,OAAO,IAAI;QACb;IACF;IAEA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA,SAAS,UAAU,CAAC,OAAe,EAAA;IACjC,IAAA,IAAI,CAAC,GAAG;IAAE,QAAA,OAAO,KAAK;QACtB,MAAM,CAAC,GAAG,GAAsE;QAChF,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;IAC9C,IAAA,MAAM,GAAG,GAAG,sDAAsD,GAAG,IAAI,GAAG,IAAI;IAChF,IAAA,IAAI,CAAC,CAAC,UAAU,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,UAAU,EAAE;IAC/D,QAAA,IAAI;IACF,YAAA,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;IACZ,YAAA,OAAO,IAAI;YACb;IAAE,QAAA,MAAM;;YAER;QACF;IACA,IAAA,IAAI;YACF,MAAM,CAAC,GAAG,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC;IACjC,QAAA,CAAC,CAAC,YAAY,CAAC,YAAY,EAAE,yBAAyB,CAAC;IACvD,QAAA,CAAC,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC;IAClC,QAAA,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC,CAAC;QAC9C;IAAE,IAAA,MAAM;;QAER;IACA,IAAA,OAAO,KAAK;IACd;IAEA;IAEA;IACA;IACA,SAAS,cAAc,CAAC,OAAyB,EAAE,GAAW,EAAA;QAC5D,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,YAAY,CAAC;IACxC,IAAA,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;IAAE,QAAA,OAAO,IAAI;IACtC,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;IACrC,QAAA,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;;;IAGlB,QAAA,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAA0C,EAAE,GAAG,CAAC,EAAE;IAC3G,YAAA,OAAO,CAA4B;YACrC;QACF;IACA,IAAA,OAAO,IAAI;IACb;IAEA;IACA;IACA;IACA;IACA;IACA,SAAS,kBAAkB,CAAC,GAAY,EAAA;IACtC,IAAA,IAAI;YACF,KAAK,IAAI,CAAC,GAAY,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,SAAS,EAAE,CAAC,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE;IACpF,YAAA,IAAI,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC;IAAE,gBAAA,OAAO,OAAQ,CAA4B,CAAC,QAAQ,KAAK,UAAU;YAC7F;QACF;IAAE,IAAA,MAAM;;QAER;IACA,IAAA,OAAO,KAAK;IACd;IAEA;IACA;IACA;IACA;IACA,SAAS,gBAAgB,CAAC,GAAY,EAAA;IACpC,IAAA,IAAI,GAAG,IAAI,kBAAkB,CAAC,GAAG,CAAC;IAAE,QAAA,OAAO,GAAgB;QAC3D,IAAI,OAAO,GAAG,KAAK,UAAU;IAAE,QAAA,OAAO,EAAE,QAAQ,EAAE,GAAiB,EAAE;IACrE,IAAA,OAAO,IAAI;IACb;IAEA;IACA;IACA,SAAS,aAAa,CAAC,EAAW,EAAE,iBAA0B,EAAA;IAC5D,IAAA,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE;IAAE,QAAA,OAAO,EAAE;QAC3C,MAAM,OAAO,GAAG,iBAAiB,GAAG,SAAS,GAAG,mBAAmB;QACnE,OAAO,CAAA,kDAAA,EAAqD,OAAO,CAAA,CAAA,CAAG;IACxE;IAEA;IACA;IACA,SAAS,SAAS,CAAC,SAAoB,EAAE,MAAe,EAAA;IACtD,IAAA,IAAI;YACF,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;YAClD,OAAO,OAAO,GAAG,KAAK;kBAClB,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI;kBAC1B,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,oCAAoC,EAAE;QACnE;QAAE,OAAO,CAAC,EAAE;IACV,QAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE;QACzC;IACF;IAEA;IAEA;IACA;IACA;IACA,SAAS,gBAAgB,CACvB,SAA2B,EAC3B,MAAe,EACf,KAAc,EACd,MAAc,EAAA;QAEd,IAAI,OAAO,GAAG,KAAK;QACnB,OAAO,CAAC,CAAS,KAAmB;YAClC,IAAI,CAAC,KAAK,EAAE;gBACV,MAAM,CAAC,uBAAuB,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;gBACvD,OAAO,IAAI,CAAC;YACd;IACA,QAAA,IAAI,OAAO;IAAE,YAAA,OAAO,CAAC;IACrB,QAAA,IAAI;gBACF,OAAO,GAAG,IAAI;gBACd,OAAQ,SAAuB,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAW;YAC/D;YAAE,OAAO,CAAC,EAAE;IACV,YAAA,MAAM,CAAC,gBAAgB,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC5C,OAAO,IAAI,CAAC;YACd;oBAAU;gBACR,OAAO,GAAG,KAAK;YACjB;IACF,IAAA,CAAC;IACH;IAEA;IACA;IACA,SAAS,cAAc,CACrB,IAAwC,EACxC,EAAqB,EACrB,MAAc,EAAA;QAEd,OAAO,CAAC,CAAS,KAAmB;YAClC,IAAI,EAAE,EAAE;IACN,YAAA,IAAI,CAAU;IACd,YAAA,IAAI;IACF,gBAAA,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;gBACX;gBAAE,OAAO,CAAC,EAAE;IACV,gBAAA,MAAM,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC3D,OAAO,IAAI,CAAC;gBACd;IACA,YAAA,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE;oBACzB,MAAM,CAAC,qBAAqB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC7C,gBAAA,OAAO,CAAC;gBACV;YACF;IACA,QAAA,MAAM,CAAC,qBAAqB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9D,QAAA,OAAO,IAAI;IACb,IAAA,CAAC;IACH;IAEA;IAEM,SAAU,IAAI,CAAC,OAAA,GAA4B,EAAE,EAAA;IACjD,IAAA,IAAI,SAAS;IAAE,QAAA,OAAO,YAA0C;QAChE,SAAS,GAAG,IAAI;;;;QAKhB,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC;IACxC,IAAA,MAAM,MAAM,GACV,OAAO,GAAG,KAAK;IACb,UAAE,CAAC,IAAI,EAAE,MAAM,KAAI;IACf,YAAA,IAAI;IACD,gBAAA,GAAc,CAAC,IAAI,EAAE,MAAM,CAAC;gBAC/B;IAAE,YAAA,MAAM;;gBAER;YACF;IACF,UAAE,MAAK,EAAE,CAAC;IAEd,IAAA,MAAM,MAAM,GAAqB;IAC/B,QAAA,OAAO,EAAE,OAAO;YAChB,WAAW,EAAE,CAAC,CAAC,EAAE;IACjB,QAAA,iBAAiB,EAAE,KAAK;IACxB,QAAA,kBAAkB,EAAE,KAAK;IACzB,QAAA,cAAc,EAAE,KAAK;IACrB,QAAA,QAAQ,EAAE,KAAK;IACf,QAAA,YAAY,EAAE,KAAK;IACnB,QAAA,SAAS,EAAE,KAAK;IAChB,QAAA,MAAM,EAAE,EAAE;SACX;IACD,IAAA,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,IAAoB,KAAgC;IAChF,QAAA,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC,kBAAkB,IAAI,MAAM,CAAC,iBAAiB,IAAI,MAAM,CAAC,cAAc;IACjG,QAAA,MAAM,CAAC,MAAM,GAAG,MAAM;;;YAGtB,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC;IAC3C,QAAA,IAAI,IAAI;IAAE,YAAA,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;IACpC,QAAA,OAAO,YAAY;IACrB,IAAA,CAAC;IAED,IAAA,IAAI;YACF,MAAM,GAAG,GAAG,GAAG,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE;;;;IAK1E,QAAA,IAAI,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAA0C,EAAE,GAAG,CAAC,EAAE;IACrF,YAAA,MAAM,CAAC,QAAQ,GAAG,IAAI;IACtB,YAAA,OAAO,IAAI,CAAC,yEAAyE,EAAE,iBAAiB,CAAC;YAC3G;;;;YAKA,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,EAAE,SAAS,CAA0C;IAChF,QAAA,IAAI,OAAO,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE;IAChD,YAAA,MAAM,CAAC,QAAQ,GAAG,IAAI;IACtB,YAAA,OAAO,IAAI,CACT,kFAAkF,EAClF,uBAAuB,CACxB;YACH;YAEA,IAAI,CAAC,EAAE,IAAI,OAAO,EAAE,CAAC,YAAY,KAAK,UAAU,EAAE;IAChD,YAAA,OAAO,IAAI,CAAC,sEAAsE,EAAE,gBAAgB,CAAC;YACvG;;;;YAKA,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC;IAC7C,QAAA,MAAM,GAAG,GAAG,CAAC,GAAW,MAAe,QAAQ,IAAI,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;;YAG1G,IAAI,GAAG,CAAC,OAAO,EAAE,aAAa,CAAC,KAAK,IAAI,EAAE;IACxC,YAAA,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,gBAAgB,CAAC,EAAE,OAAO,GAAG,CAAC,WAAW,CAAC,KAAK,UAAU,CAAC;IACvG,YAAA,MAAM,CAAC,YAAY,GAAG,UAAU,CAAC,SAAS,CAAC;IAC3C,YAAA,MAAM,CAAC,0BAA0B,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC;YACjF;IAEA,QAAA,MAAM,CAAC,iBAAiB,GAAG,iBAAiB,EAAE;;;IAI9C,QAAA,IAAI,MAAM,GAAY,GAAG,CAAC,WAAW,CAAC;YACtC,IAAI,MAAM,KAAK,SAAS;IAAE,YAAA,MAAM,GAAI,IAA2C,CAAC,SAAS;IACzF,QAAA,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,CAAC;IAC1C,QAAA,MAAM,MAAM,GAAG,GAAG,CAAC,kBAAkB,CAAC;IACtC,QAAA,MAAM,cAAc,GAClB,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,GAAG,WAAW,CAAC,MAAiC,CAAC,GAAG,SAAS;;IAGnG,QAAA,MAAM,MAAM,GAAG,GAAG,CAAC,cAAc,CAAC;IAClC,QAAA,MAAM,OAAO,GAAG,GAAG,CAAC,kBAAkB,CAAC;IACvC,QAAA,MAAM,WAAW,GAAG,OAAO,MAAM,KAAK,UAAU,GAAI,MAAqB,GAAG,IAAI;IAChF,QAAA,MAAM,cAAc,GAAG,OAAO,OAAO,KAAK,UAAU,GAAI,OAAsB,GAAG,IAAI;YAErF,IAAI,cAAc,GAAG,KAAK;YAC1B,IAAI,SAAS,EAAE;gBACb,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,EAAE,cAAc,CAAC;IACnD,YAAA,cAAc,GAAG,MAAM,CAAC,KAAK;gBAC7B,IAAI,CAAC,MAAM,CAAC,KAAK;oBAAE,MAAM,CAAC,4BAA4B,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;YAClF;IACA,QAAA,MAAM,CAAC,cAAc,GAAG,cAAc;;IAGtC,QAAA,MAAM,SAAS,GAAG;gBAChB,UAAU,EAAE,gBAAgB,CAAC,SAAS,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,CAAC;gBAC/E,YAAY,EAAE,cAAc,CAAC,cAAc,EAAE,WAAW,EAAE,MAAM,CAAC;gBACjE,eAAe,EAAE,cAAc,CAAC,iBAAiB,EAAE,cAAc,EAAE,MAAM,CAAC;aAC3E;;IAGD,QAAA,IAAI,EAAE,CAAC,aAAa,EAAE;gBACpB,OAAO,IAAI,CACT,qGAAqG;oBACnG,0CAA0C,EAC5C,4BAA4B,CAC7B;YACH;IAEA,QAAA,IAAI,IAAa;IACjB,QAAA,IAAI;gBACF,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,SAAS,CAAC;YAC9C;YAAE,OAAO,CAAC,EAAE;;gBAEV,OAAO,IAAI,CACT,CAAA,+BAAA,EAAkC,IAAI,CAAC,CAAC,CAAC,CAAA,uCAAA,CAAyC,EAClF,qBAAqB,CACtB;YACH;;YAGA,IAAI,EAAE,CAAC,aAAa,IAAI,EAAE,CAAC,aAAa,KAAK,IAAI,EAAE;gBACjD,OAAO,IAAI,CACT,qFAAqF;oBACnF,6DAA6D,EAC/D,2BAA2B,CAC5B;YACH;IAEA,QAAA,MAAM,CAAC,kBAAkB,GAAG,IAAI;IAEhC,QAAA,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE;gBAC7B,OAAO,IAAI,CACT,qGAAqG;oBACnG,uDAAuD,EACzD,sBAAsB,CACvB;YACH;YACA,IAAI,CAAC,cAAc,EAAE;gBACnB,OAAO,IAAI,CACT,+FAA+F;oBAC7F,mEAAmE,EACrE,gBAAgB,CACjB;YACH;IACA,QAAA,OAAO,IAAI,CACT,CAAA,2CAAA,EAA8C,WAAW,IAAI,cAAc,GAAG,yBAAyB,GAAG,SAAS,CAAA,CAAA,CAAG,CACvH;QACH;QAAE,OAAO,CAAC,EAAE;;;;YAIV,OAAO,IAAI,CAAC,CAAA,gCAAA,EAAmC,IAAI,CAAC,CAAC,CAAC,CAAA,kBAAA,CAAoB,EAAE,gBAAgB,CAAC;QAC/F;IACF;aAEgB,MAAM,GAAA;IACpB,IAAA,OAAO,YAAY;IACrB;IAEO,MAAM,UAAU,GAAkB,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;IC/XxE;;;;IAIG;IAWH,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;IACjC,IAAA,MAAM,CAAC,UAAU,GAAG,UAAU;QAC9B,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,gBAAgB,IAAI,EAAE,CAAC;IAChD;;;;;;"} \ No newline at end of file diff --git a/dist/fortify.min.js b/dist/fortify.min.js index 4cba428..e8800b8 100644 --- a/dist/fortify.min.js +++ b/dist/fortify.min.js @@ -1,3 +1,3 @@ -/*! DOMFortify 0.1.0 | (c) Cure53 and contributors | (MPL-2.0 OR Apache-2.0) */ -!function(){"use strict";const t=Object.prototype.hasOwnProperty,e="undefined"!=typeof globalThis?globalThis:window,i="undefined"!=typeof document?document:void 0,n=e.location,r=(e,i)=>null!=e&&t.call(e,i),o=(t,e)=>r(t,e)?t[e]:void 0,c=t=>String(t).slice(0,80),a=t=>String(t?.message),l=e.trustedTypes;let s=!1,u=null;function f(t,e){if(null==t)return!1;const i=Array.isArray(t)?t:[t];for(let t=0;t{},h={version:"0.1.0",ttSupported:!!l,enforcementActive:!1,defaultPolicyOwned:!1,sanitizerReady:!1,excluded:!1,metaInjected:!1,protected:!1,reason:""},m=(t,e)=>(h.protected=h.defaultPolicyOwned&&h.enforcementActive&&h.sanitizerReady,h.reason=t,e&&p(e,h),u=Object.freeze({...h}),u),O=n&&void 0!==n.href?String(n.href):"";if(f(o(d,"EXCLUDE"),O))return h.excluded=!0,m("URL matched EXCLUDE; DOMFortify is intentionally inactive on this page.","excluded-by-url");if(!l||"function"!=typeof l.createPolicy)return m("Trusted Types not supported; library is inert. Sinks are NOT routed.","tt-unsupported");let T=null;const v=o(d,"URL_CONFIG");if(Array.isArray(v))for(let t=0;tT&&r(T,t)?T[t]:o(d,t);if(!0===o(d,"INJECT_META")){const t=o(d,"META_DIRECTIVE"),e="function"==typeof w("SANITIZER")?"default":"default dompurify",n="string"==typeof t&&t?t:`require-trusted-types-for 'script'; trusted-types ${e};`;h.metaInjected=function(t){if(!i)return!1;const e=i,n='\r\n]/g,"")+'">';if("loading"===e.readyState&&"function"==typeof e.write)try{return e.write(n),!0}catch{}try{const i=e.createElement("meta");i.setAttribute("http-equiv","Content-Security-Policy"),i.setAttribute("content",t),(e.head||e.documentElement).appendChild(i)}catch{}return!1}(n),p("meta-injection-attempted",{directive:n,written:h.metaInjected})}h.enforcementActive=function(){try{return i.createElement("div").innerHTML="x",!1}catch{return!0}}();let g=w("SANITIZER");void 0===g&&(g=e.DOMPurify);const b=g&&"function"==typeof g.sanitize?g:"function"==typeof g?{sanitize:g}:null,A=w("SANITIZER_CONFIG"),k=A&&"object"==typeof A?function(e){const i={};for(const n in e)t.call(e,n)&&"__proto__"!==n&&"constructor"!==n&&"prototype"!==n&&(i[n]=e[n]);return i}(A):void 0,I=w("ALLOW_SCRIPT"),L=w("ALLOW_SCRIPT_URL"),z="function"==typeof I?I:null,E="function"==typeof L?L:null;let R=!1;if(b&&"function"==typeof b.sanitize)try{R="string"==typeof b.sanitize("x",k),R||p("sanitizer-smoketest-failed",{error:"sanitize() did not return a string"})}catch(t){p("sanitizer-smoketest-failed",{error:a(t)})}h.sanitizerReady=R;let S=!1;const P=(t,e)=>i=>{if(e){let n;try{n=e(i)}catch(e){return p("script-hook-threw",{sink:t,error:a(e)}),null}if("string"==typeof n)return p("script-sink-allowed",{sink:t}),n}return p("script-sink-refused",{sink:t,sample:c(i)}),null},M={createHTML:t=>{if(!R)return p("sanitizer-unavailable",{sink:"createHTML"}),null;if(S)return t;try{return S=!0,b.sanitize(t,k)}catch(t){return p("sanitize-threw",{error:a(t)}),null}finally{S=!1}},createScript:P("createScript",z),createScriptURL:P("createScriptURL",E)};if(l.defaultPolicy)return m("A default Trusted Types policy already exists; DOMFortify did NOT install and cannot vouch for it. Load DOMFortify first, inline in .","preexisting-default-policy");let D;try{D=l.createPolicy("default",M)}catch(t){return m(`createPolicy("default") threw (${a(t)}); another default policy won the race.`,"default-policy-lost")}return l.defaultPolicy&&l.defaultPolicy!==D?m('Our policy was created but is not the active default (allow-duplicates race lost). Remove "allow-duplicates" from the trusted-types directive.',"default-policy-not-active"):(h.defaultPolicyOwned=!0,h.enforcementActive?R?m(`Active: HTML sinks sanitized, script sinks ${z||E?"partly allowed by hooks":"refused"}.`):m("Enforcement active and slot locked, but the sanitizer is unavailable - HTML sinks will THROW (failing closed). Bundle DOMPurify and load it before DOMFortify.","failing-closed"):m("Default policy installed and slot locked, but TT enforcement is NOT active - sinks are not routed. Deliver require-trusted-types-for (header preferred).","enforcement-inactive"))},status:function(){return u}});"undefined"!=typeof window&&(window.DOMFortify=d,d.init(window.DOMFortifyConfig||{}))}(); +/*! DOMFortify 0.5.0 | (c) Cure53 and contributors | (MPL-2.0 OR Apache-2.0) */ +!function(){"use strict";const t=Object.prototype.hasOwnProperty;function e(e,n){return null!=e&&t.call(e,n)}function n(t,n){return e(t,n)?t[n]:void 0}function r(t){return String(t).slice(0,80)}function i(t){try{return String(t?.message)}catch{return"unknown error"}}function o(t,e){if(null==t)return!1;const n=Array.isArray(t)?t:[t];for(let t=0;t{if(!n)return r("sanitizer-unavailable",{sink:"createHTML"}),null;if(o)return c;try{return o=!0,t.sanitize(c,e)}catch(t){return r("sanitize-threw",{error:i(t)}),null}finally{o=!1}}}function p(t,e,n){return o=>{if(e){let r;try{r=e(o)}catch(e){return n("script-hook-threw",{sink:t,error:i(e)}),null}if("string"==typeof r)return n("script-sink-allowed",{sink:t}),r}return n("script-sink-refused",{sink:t,sample:r(o)}),null}}const h=Object.freeze({init:function(r={}){if(s)return f;s=!0;const h=n(r,"ON_VIOLATION"),O="function"==typeof h?(t,e)=>{try{h(t,e)}catch{}}:()=>{},v={version:"0.5.0",ttSupported:!!l,enforcementActive:!1,defaultPolicyOwned:!1,sanitizerReady:!1,excluded:!1,metaInjected:!1,protected:!1,reason:""},m=(t,e)=>(v.protected=v.defaultPolicyOwned&&v.enforcementActive&&v.sanitizerReady,v.reason=t,f=Object.freeze({...v}),e&&O(e,f),f);try{const s=a&&void 0!==a.href?String(a.href):"";if(o(n(r,"EXCLUDE"),s))return v.excluded=!0,m("URL matched EXCLUDE; DOMFortify is intentionally inactive on this page.","excluded-by-url");const f=n(r,"INCLUDE");if(null!=f&&!o(f,s))return v.excluded=!0,m("URL is outside INCLUDE scope; DOMFortify is intentionally inactive on this page.","outside-include-scope");if(!l||"function"!=typeof l.createPolicy)return m("Trusted Types not supported; library is inert. Sinks are NOT routed.","tt-unsupported");const h=function(t,e){const r=n(t,"URL_CONFIG");if(!Array.isArray(r))return null;for(let t=0;th&&e(h,t)?h[t]:n(r,t);if(!0===n(r,"INJECT_META")){const t=(T=n(r,"META_DIRECTIVE"),w="function"==typeof g("SANITIZER"),"string"==typeof T&&T?T:`require-trusted-types-for 'script'; trusted-types ${w?"default":"default dompurify"};`);v.metaInjected=function(t){if(!u)return!1;const e=u,n='\r\n]/g,"")+'">';if("loading"===e.readyState&&"function"==typeof e.write)try{return e.write(n),!0}catch{}try{const n=e.createElement("meta");n.setAttribute("http-equiv","Content-Security-Policy"),n.setAttribute("content",t),(e.head||e.documentElement).appendChild(n)}catch{}return!1}(t),O("meta-injection-attempted",{directive:t,written:v.metaInjected})}v.enforcementActive=function(){try{return u.createElement("div").innerHTML="x",!1}catch{return!0}}();let b=g("SANITIZER");void 0===b&&(b=c.DOMPurify);const L=d(b),A=g("SANITIZER_CONFIG"),I=A&&"object"==typeof A?function(e){const n={};for(const r in e)t.call(e,r)&&"__proto__"!==r&&"constructor"!==r&&"prototype"!==r&&(n[r]=e[r]);return n}(A):void 0,E=g("ALLOW_SCRIPT"),R=g("ALLOW_SCRIPT_URL"),k="function"==typeof E?E:null,P="function"==typeof R?R:null;let S=!1;if(L){const t=function(t,e){try{return"string"==typeof t.sanitize("x",e)?{ready:!0,error:null}:{ready:!1,error:"sanitize() did not return a string"}}catch(t){return{ready:!1,error:i(t)}}}(L,I);S=t.ready,t.ready||O("sanitizer-smoketest-failed",{error:t.error})}v.sanitizerReady=S;const z={createHTML:y(L,I,S,O),createScript:p("createScript",k,O),createScriptURL:p("createScriptURL",P,O)};if(l.defaultPolicy)return m("A default Trusted Types policy already exists; DOMFortify did NOT install and cannot vouch for it. Load DOMFortify first, inline in .","preexisting-default-policy");let D;try{D=l.createPolicy("default",z)}catch(t){return m(`createPolicy("default") threw (${i(t)}); another default policy won the race.`,"default-policy-lost")}return l.defaultPolicy&&l.defaultPolicy!==D?m('Our policy was created but is not the active default (allow-duplicates race lost). Remove "allow-duplicates" from the trusted-types directive.',"default-policy-not-active"):(v.defaultPolicyOwned=!0,v.enforcementActive?S?m(`Active: HTML sinks sanitized, script sinks ${k||P?"partly allowed by hooks":"refused"}.`):m("Enforcement active and slot locked, but the sanitizer is unavailable - HTML sinks will THROW (failing closed). Bundle DOMPurify and load it before DOMFortify.","failing-closed"):m("Default policy installed and slot locked, but TT enforcement is NOT active - sinks are not routed. Deliver require-trusted-types-for (header preferred).","enforcement-inactive"))}catch(t){return m(`init() hit an unexpected error (${i(t)}); failing closed.`,"failing-closed")}var T,w},status:function(){return f}});"undefined"!=typeof window&&(window.DOMFortify=h,h.init(window.DOMFortifyConfig||{}))}(); //# sourceMappingURL=fortify.min.js.map diff --git a/dist/fortify.min.js.map b/dist/fortify.min.js.map index 8f47d04..1d4e1e7 100644 --- a/dist/fortify.min.js.map +++ b/dist/fortify.min.js.map @@ -1 +1 @@ -{"version":3,"file":"fortify.min.js","sources":["../src/fortify.ts","../src/auto.ts"],"sourcesContent":[null,null],"names":["hasOwn","Object","prototype","hasOwnProperty","root","globalThis","window","doc","document","undefined","loc","location","own","obj","key","call","cfg","clip","s","String","slice","emsg","e","message","TT","trustedTypes","installed","cachedStatus","urlMatches","pattern","url","list","Array","isArray","i","length","p","indexOf","RegExp","test","DOMFortify","freeze","init","options","onv","report","status","version","ttSupported","enforcementActive","defaultPolicyOwned","sanitizerReady","excluded","metaInjected","protected","reason","done","code","href","createPolicy","override","rules","r","match","eff","md","ttNames","directive","content","d","tag","replace","readyState","write","m","createElement","setAttribute","head","documentElement","appendChild","injectMeta","written","innerHTML","rawSan","DOMPurify","DP","sanitize","rawCfg","sanitizeConfig","out","k","shallowCopy","asCand","asuCand","allowScript","allowScriptURL","error","reentry","scriptHook","kind","fn","sink","sample","policyDef","createHTML","createScript","createScriptURL","defaultPolicy","ours","DOMFortifyConfig"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"fortify.min.js","sources":["../src/internal.ts","../src/fortify.ts","../src/auto.ts"],"sourcesContent":[null,null,null],"names":["hasOwn","Object","prototype","hasOwnProperty","own","obj","key","call","cfg","undefined","clip","s","String","slice","emsg","e","message","urlMatches","pattern","url","list","Array","isArray","i","length","p","indexOf","RegExp","test","root","globalThis","window","doc","document","loc","location","TT","trustedTypes","installed","cachedStatus","resolveSanitizer","raw","o","getPrototypeOf","sanitize","looksLikeSanitizer","makeSanitizeHTML","sanitizer","config","ready","report","reentry","sink","error","makeScriptHook","kind","fn","r","sample","DOMFortify","freeze","init","options","onv","code","detail","status","version","ttSupported","enforcementActive","defaultPolicyOwned","sanitizerReady","excluded","metaInjected","protected","reason","done","href","include","createPolicy","override","rules","selectOverride","eff","directive","md","functionSanitizer","content","d","tag","replace","readyState","write","m","createElement","setAttribute","head","documentElement","appendChild","injectMeta","written","innerHTML","rawSan","DOMPurify","rawCfg","sanitizeConfig","out","k","shallowCopy","asCand","asuCand","allowScript","allowScriptURL","result","smokeTest","policyDef","createHTML","createScript","createScriptURL","defaultPolicy","ours","DOMFortifyConfig"],"mappings":"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"} \ No newline at end of file diff --git a/osv-scanner.toml b/osv-scanner.toml index 95fe250..8278c74 100644 --- a/osv-scanner.toml +++ b/osv-scanner.toml @@ -4,12 +4,83 @@ # supply-chain surface of its own. Any advisories OSV-Scanner reports come from # development / test / CI tooling in the lockfile, never from distributed code. # -# There are no suppressions yet. If a dev-only advisory ever needs ignoring, add -# an [[IgnoredVulns]] entry with a VERIFIED GHSA id, a reason, and a one-year -# ignoreUntil horizon so it is re-evaluated rather than suppressed forever. +# Most suppressions below are for deliberately-vulnerable legacy libraries +# pulled in ONLY as e2e test fixtures (test/fixtures/with-angularjs.html and +# test/fixtures/with-jquery.html). They are intentionally old: the tests exist +# to prove DOMFortify backstops their known DOM-XSS sinks, so "upgrade to fix" +# is not an option - a patched version would no longer exercise the footgun. +# devDependencies only; never part of the published runtime artifact. # -# Example (commented out): -# [[IgnoredVulns]] -# id = "GHSA-xxxx-xxxx-xxxx" -# ignoreUntil = 2027-06-15 -# reason = "Dev tooling only; not part of the published runtime artifact." +# Each entry carries a one-year ignoreUntil so it is re-evaluated, not buried. + +# --- AngularJS 1.8.3 (EOL, terminal - no fixed 1.x release exists) ----------- +# Fixture: test/fixtures/with-angularjs.html (ng-bind-html without ngSanitize). +[[IgnoredVulns]] +id = "GHSA-2qqx-w9hr-q5gx" +ignoreUntil = 2027-06-22 +reason = "AngularJS 1.8.3 EOL; dev-only test fixture, not in the published runtime artifact." + +[[IgnoredVulns]] +id = "GHSA-2vrf-hf26-jrp5" +ignoreUntil = 2027-06-22 +reason = "AngularJS 1.8.3 EOL; dev-only test fixture, not in the published runtime artifact." + +[[IgnoredVulns]] +id = "GHSA-4w4v-5hc9-xrr2" +ignoreUntil = 2027-06-22 +reason = "AngularJS 1.8.3 EOL; dev-only test fixture, not in the published runtime artifact." + +[[IgnoredVulns]] +id = "GHSA-j58c-ww9w-pwp5" +ignoreUntil = 2027-06-22 +reason = "AngularJS 1.8.3 EOL; dev-only test fixture, not in the published runtime artifact." + +[[IgnoredVulns]] +id = "GHSA-m2h2-264f-f486" +ignoreUntil = 2027-06-22 +reason = "AngularJS 1.8.3 EOL; dev-only test fixture, not in the published runtime artifact." + +[[IgnoredVulns]] +id = "GHSA-m9gf-397r-hwpg" +ignoreUntil = 2027-06-22 +reason = "AngularJS 1.8.3 EOL; dev-only test fixture, not in the published runtime artifact." + +[[IgnoredVulns]] +id = "GHSA-mqm9-c95h-x2p6" +ignoreUntil = 2027-06-22 +reason = "AngularJS 1.8.3 EOL; dev-only test fixture, not in the published runtime artifact." + +[[IgnoredVulns]] +id = "GHSA-prc3-vjfx-vhm9" +ignoreUntil = 2027-06-22 +reason = "AngularJS 1.8.3 EOL; dev-only test fixture, not in the published runtime artifact." + +[[IgnoredVulns]] +id = "GHSA-qwqh-hm9m-p5hr" +ignoreUntil = 2027-06-22 +reason = "AngularJS 1.8.3 EOL; dev-only test fixture, not in the published runtime artifact." + +# --- jQuery 3.4.1 (deliberately pre-3.5: CVE-2020-11022 / -11023 mXSS) ------- +# Fixture: test/fixtures/with-jquery.html ($(t).html() reaching innerHTML). +# Bumping to >= 3.5.0 would patch the very sink the test relies on. +[[IgnoredVulns]] +id = "GHSA-gxr4-xjj5-5px2" +ignoreUntil = 2027-06-22 +reason = "jQuery 3.4.1 pinned pre-3.5 on purpose for the mXSS backstop test; dev-only, not shipped." + +[[IgnoredVulns]] +id = "GHSA-jpcq-cgw6-v4j6" +ignoreUntil = 2027-06-22 +reason = "jQuery 3.4.1 pinned pre-3.5 on purpose for the mXSS backstop test; dev-only, not shipped." + +# --- js-yaml 3.14.2 (transitive, via nyc coverage tooling) ------------------ +# Path: nyc -> @istanbuljs/load-nyc-config -> js-yaml@3.14.2. Not a fixture and +# not deliberately old - just what nyc pins. dev-only (npm audit --omit=dev is +# clean); never in the published zero-dependency runtime artifact. Cannot be +# forward-fixed here: the patched js-yaml is >= 4.2.0, but load-nyc-config calls +# the 3.x safeLoad API that 4.x removed, so an override breaks coverage. The DoS +# also requires parsing attacker-controlled YAML; nyc only reads our own .nycrc. +[[IgnoredVulns]] +id = "GHSA-h67p-54hq-rp68" +ignoreUntil = 2027-06-22 +reason = "js-yaml 3.14.2 via nyc coverage tooling; dev-only, not shipped, no forward fix (4.x drops the safeLoad API nyc uses)." diff --git a/package-lock.json b/package-lock.json index 71b41ac..0e7c32e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,29 @@ { "name": "domfortify", - "version": "0.1.0", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "domfortify", - "version": "0.1.0", + "version": "0.5.0", "license": "(MPL-2.0 OR Apache-2.0)", "devDependencies": { "@playwright/test": "^1.49.0", "@rollup/plugin-replace": "^6.0.1", "@rollup/plugin-terser": "^1.0.0", "@rollup/plugin-typescript": "^12.1.1", - "dompurify": "^3.2.0", + "angular": "1.8.3", + "dompurify": "^3.4.11", "fast-check": "^4.8.0", + "jquery": "3.4.1", + "nyc": "^18.0.0", "prettier": "^3.4.2", "qunit": "^2.23.1", "rimraf": "^6.0.1", "rollup": "^4.28.1", "rollup-plugin-dts": "^6.1.1", + "rollup-plugin-istanbul": "^5.0.0", "tslib": "^2.8.1", "typescript": "^5.7.2" }, @@ -33,7 +37,6 @@ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", @@ -43,17 +46,288 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -604,104 +878,512 @@ "node": ">=0.4.0" } }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, "engines": { - "node": "18 || 20 || >=22" + "node": ">=8" } }, - "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "node_modules/angular": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz", + "integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw==", + "deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward.", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, "engines": { - "node": "18 || 20 || >=22" + "node": ">=8" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, - "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, "license": "MIT", + "dependencies": { + "default-require-extensions": "^3.0.0" + }, "engines": { - "node": ">= 10" + "node": ">=8" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true, "license": "MIT" }, - "node_modules/dompurify": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.10.tgz", - "integrity": "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==", + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": "18 || 20 || >=22" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "node_modules/baseline-browser-mapping": { + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } }, - "node_modules/fast-check": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.8.0.tgz", - "integrity": "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==", + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", "dev": true, "funding": [ { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" + "type": "opencollective", + "url": "https://opencollective.com/browserslist" }, { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "dependencies": { - "pure-rand": "^8.0.0" + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">=12.17.0" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/fsevents": { + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dompurify": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", + "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", + "dev": true, + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.377", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.377.tgz", + "integrity": "sha512-cH1jZgJHoezfTnKfKwnScpHywTFVnJUNITDPREFdhNjiuD502+QFpG0Qk7G8jhsV/f+CEAFlIrzP1fT+IMb92g==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.8.0.tgz", + "integrity": "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^8.0.0" + }, + "engines": { + "node": ">=12.17.0" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", @@ -726,6 +1408,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/glob": { "version": "13.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", @@ -758,6 +1470,40 @@ "dev": true, "license": "MIT" }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -771,6 +1517,33 @@ "node": ">= 0.4" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-core-module": { "version": "2.16.2", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", @@ -787,68 +1560,472 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-3.0.1.tgz", + "integrity": "sha512-s3mX05h5wGZeScG6XnOanygPh4SJu5ujMc9YbvpnLGXWy1cRiGbp0NdVcjHxgoZt3WfQppfBsa0y+gWdYJ2pGQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^6.1.3" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jquery": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", + "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==", + "deprecated": "This version is deprecated. Please upgrade to the latest version or find support at https://www.herodevs.com/support/jquery-nes.", + "dev": true, + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, "license": "MIT", - "optional": true + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true, + "license": "MIT" }, "node_modules/lru-cache": { "version": "11.5.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.48", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz", + "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/node-watch": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz", + "integrity": "sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/nyc": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-18.0.0.tgz", + "integrity": "sha512-G5UyHinFkB1BxqGTrmZdB6uIYH0+v7ZnVssuflUDi+J+RhKWyAhRT1RCehBSI6jLFLuUUgFDyLt49mUtdO1XeQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^3.3.0", + "get-package-type": "^0.1.0", + "glob": "^13.0.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^6.0.2", + "istanbul-lib-processinfo": "^3.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^6.1.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^3.0.0", + "test-exclude": "^8.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/nyc/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, "engines": { - "node": "20 || >=22" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "brace-expansion": "^5.0.5" + "aggregate-error": "^3.0.0" }, "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=6" } }, - "node_modules/node-watch": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz", - "integrity": "sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ==", + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/package-json-from-dist": { @@ -858,6 +2035,26 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -887,8 +2084,7 @@ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "license": "ISC", - "optional": true + "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.4", @@ -903,6 +2099,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/playwright": { "version": "1.61.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.0.tgz", @@ -951,6 +2160,19 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/process-on-spawn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", + "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/pure-rand": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz", @@ -986,6 +2208,36 @@ "node": ">=10" } }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "license": "ISC", + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true, + "license": "ISC" + }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", @@ -1008,6 +2260,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/rimraf": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", @@ -1099,6 +2361,38 @@ "typescript": "^4.5 || ^5.0 || ^6.0" } }, + "node_modules/rollup-plugin-istanbul": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-istanbul/-/rollup-plugin-istanbul-5.0.0.tgz", + "integrity": "sha512-5FMw55B/05AVfEM75yqlzcIBFCMzS4bKDF8mA1pq2XNzYcGUd6BElZM6wvc9sn2uAclTYn6pK+kt4R4JoHmNHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.5", + "istanbul-lib-instrument": "^6.0.1" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/serialize-javascript": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", @@ -1109,6 +2403,43 @@ "node": ">=20.0.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/smob": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.2.tgz", @@ -1140,6 +2471,97 @@ "source-map": "^0.6.0" } }, + "node_modules/spawn-wrap": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-3.0.0.tgz", + "integrity": "sha512-z+s5vv4KzFPJVddGab0xX2n7kQPGMdNUX5l9T8EJqsXdKTWpcxmAqWHpsgHEXoC1taGBCc7b79bi62M5kdbrxQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "cross-spawn": "^7.0.6", + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^6.1.3", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-wrap/node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -1179,6 +2601,21 @@ "dev": true, "license": "MIT" }, + "node_modules/test-exclude": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", + "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^13.0.6", + "minimatch": "^10.2.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/tiny-glob": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", @@ -1197,6 +2634,26 @@ "dev": true, "license": "0BSD" }, + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -1210,6 +2667,139 @@ "engines": { "node": ">=14.17" } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } } } } diff --git a/package.json b/package.json index 0ed6d9e..0374122 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "domfortify", - "version": "0.1.0", + "version": "0.5.0", "description": "Retrofit Trusted Types onto a legacy page: claim the realm's default policy so old DOM-XSS sinks get sanitized without touching the code.", "license": "(MPL-2.0 OR Apache-2.0)", "homepage": "https://github.com/cure53/DOMFortify", @@ -65,20 +65,26 @@ "test:browser": "playwright test --config config/playwright.config.ts", "test": "npm run typecheck && npm run build && npm run test:node && npm run test:fuzz", "prepublishOnly": "npm run build", - "test:fuzz": "node test/fuzz/policy.fuzz.js" + "test:fuzz": "node test/fuzz/policy.fuzz.js", + "build:cov": "rollup -c config/rollup.coverage.config.mjs", + "coverage": "rimraf .nyc_output coverage dist/fortify.cov.es.mjs && npm run build:cov && node scripts/coverage.mjs && nyc report --reporter=html --reporter=text --report-dir=coverage" }, "devDependencies": { "@playwright/test": "^1.49.0", "@rollup/plugin-replace": "^6.0.1", "@rollup/plugin-terser": "^1.0.0", "@rollup/plugin-typescript": "^12.1.1", - "dompurify": "^3.2.0", + "angular": "1.8.3", + "dompurify": "^3.4.11", "fast-check": "^4.8.0", + "jquery": "3.4.1", + "nyc": "^18.0.0", "prettier": "^3.4.2", "qunit": "^2.23.1", "rimraf": "^6.0.1", "rollup": "^4.28.1", "rollup-plugin-dts": "^6.1.1", + "rollup-plugin-istanbul": "^5.0.0", "tslib": "^2.8.1", "typescript": "^5.7.2" }, diff --git a/scripts/coverage.mjs b/scripts/coverage.mjs new file mode 100644 index 0000000..f32d04b --- /dev/null +++ b/scripts/coverage.mjs @@ -0,0 +1,20 @@ +/** + * Runs the node QUnit suite against the Istanbul-instrumented build and writes the collected counters + * to .nyc_output so `nyc report` can render them. DOMFORTIFY_COV is set before the suite is imported + * (dynamic import, so it is read in time) to select the instrumented module. + */ +process.env.DOMFORTIFY_COV = '1'; + +const QUnit = (await import('qunit')).default; +const { mkdirSync, writeFileSync } = await import('node:fs'); +await import('../test/test-suite.mjs'); + +QUnit.on('runEnd', (data) => { + if (globalThis.__coverage__) { + mkdirSync('.nyc_output', { recursive: true }); + writeFileSync('.nyc_output/out.json', JSON.stringify(globalThis.__coverage__)); + } + if (data.testCounts.failed > 0) process.exitCode = 1; +}); + +QUnit.start(); diff --git a/src/fortify.ts b/src/fortify.ts index b8e414b..dd5c564 100644 --- a/src/fortify.ts +++ b/src/fortify.ts @@ -9,6 +9,7 @@ * - Fails closed: no sanitizer means sinks throw, never leak. * - Only covers Trusted Types sinks; inline handlers / style / URL props stay open. */ +import { cfg, clip, emsg, own, shallowCopy, urlMatches } from './internal'; import type { DOMFortifyApi, DOMFortifyConfig, @@ -16,7 +17,6 @@ import type { Sanitizer, SanitizeFn, ScriptHook, - UrlConfigRule, UrlPattern, ViolationCode, } from './types'; @@ -28,25 +28,22 @@ interface TtFactory { defaultPolicy?: unknown; } -// Grab natives up front so later prototype-pollution or clobbering can't swap them out. -const hasOwn = Object.prototype.hasOwnProperty; +type Report = (code: ViolationCode, detail?: unknown) => void; + +// Natives captured up front, so later prototype pollution or clobbering can't swap them out. const root: typeof globalThis = typeof globalThis !== 'undefined' ? globalThis : (window as unknown as typeof globalThis); const doc: Document | undefined = typeof document !== 'undefined' ? document : undefined; const loc: { href?: unknown } | undefined = (root as unknown as { location?: { href?: unknown } }).location; - -const own = (obj: unknown, key: string): boolean => obj != null && hasOwn.call(obj, key); -const cfg = (obj: unknown, key: string): unknown => (own(obj, key) ? (obj as Record)[key] : undefined); -const clip = (s: unknown): string => String(s).slice(0, 80); -const emsg = (e: unknown): string => String((e as { message?: unknown } | undefined)?.message); - const TT = (root as unknown as { trustedTypes?: TtFactory }).trustedTypes; let installed = false; let cachedStatus: Readonly | null = null; -// Are we actually enforced? Under enforcement with no default policy yet, a sink write throws. -// Run this BEFORE we install our policy, or it would always read as "off". +// --- environment probes -------------------------------------------------------------------------- + +// Are we actually enforced? Under enforcement with no default policy yet, a sink write throws. Must +// run BEFORE we install our policy, or it would always read as "off". function enforcementActive(): boolean { try { (doc as Document).createElement('div').innerHTML = 'x'; @@ -56,48 +53,15 @@ function enforcementActive(): boolean { } } -// Copy config off the caller's object, skipping keys that could pollute. Don't JSON-clone - that -// would corrupt RegExp and functions. -function shallowCopy(obj: Record): Record { - const out: Record = {}; - for (const k in obj) { - if (hasOwn.call(obj, k) && k !== '__proto__' && k !== 'constructor' && k !== 'prototype') out[k] = obj[k]; - } - return out; -} - -// Test a URL against one or more patterns. String = substring match; RegExp = test. Used for both -// EXCLUDE and URL_CONFIG, always against the realm's own location.href. -function urlMatches(pattern: UrlPattern | UrlPattern[] | undefined, url: string): boolean { - if (pattern == null) return false; - const list = Array.isArray(pattern) ? pattern : [pattern]; - for (let i = 0; i < list.length; i++) { - const p = list[i]; - if (typeof p === 'string') { - if (p !== '' && url.indexOf(p) !== -1) return true; - } else if (p instanceof RegExp) { - try { - if (p.test(url)) return true; - } catch { - /* ignore a pattern that throws */ - } - } - } - return false; -} - -// Best-effort CSP injection (opt-in). IMPORTANT: a CSP is honored only when the PARSER -// inserts it, so document.write during the initial parse is the only path that can actually switch -// enforcement on - and only for content parsed afterwards. A node appended after parsing is ignored by -// the CSP engine; we still add it (harmless) but report that injection did NOT take. Returns true only -// when written during parse. +// Best-effort CSP injection (opt-in). A CSP is honored only when the PARSER inserts it, +// so document.write during the initial parse is the one path that can switch enforcement on - and only +// for content parsed afterwards. We return true only on that path. After parse we still append the node +// (harmless) but report that it did NOT take. // -// `content` is the trusted CSP directive built from config (the derived default, or META_DIRECTIVE). -// META_DIRECTIVE is developer-controlled and is expected to be trusted, but since this path reaches -// document.write we still strip the characters that could break out of the content="..." attribute or -// the tag. A real CSP directive never contains ", <, >, or newlines (single quotes, e.g. -// 'script', are kept - they are harmless inside the double-quoted attribute), so this is lossless for -// valid input and neutralizes a hostile or malformed directive. Defense in depth. +// `content` is the trusted directive built from config. META_DIRECTIVE is developer-controlled, but +// because this path reaches document.write we still strip the characters that could break out of the +// content="..." attribute. A valid directive never contains ", <, >, or newlines, so the strip is +// lossless for good input and neutralizes a hostile or malformed one. Defense in depth. function injectMeta(content: string): boolean { if (!doc) return false; const d = doc as Document & { write?: (s: string) => void; readyState?: string }; @@ -122,12 +86,147 @@ function injectMeta(content: string): boolean { return false; } +// --- config resolution (all own-key only, so a polluted prototype can't loosen anything) --------- + +// First URL_CONFIG rule whose `match` hits, else null. Own-key reads only, so a polluted prototype +// can neither inject a rule nor reach one. +function selectOverride(options: DOMFortifyConfig, url: string): Record | null { + const rules = cfg(options, 'URL_CONFIG'); + if (!Array.isArray(rules)) return null; + for (let i = 0; i < rules.length; i++) { + const r = rules[i]; + // Read `match` own-key only, so a polluted Object.prototype.match can't make a rule that lacks + // its own match apply to every URL. + if (r && typeof r === 'object' && urlMatches(cfg(r, 'match') as UrlPattern | UrlPattern[] | undefined, url)) { + return r as Record; + } + } + return null; +} + +// Does `raw` carry a `.sanitize` method of its own (or on its own class prototype), as opposed to one +// merely inherited from Object.prototype? We walk the chain but STOP before Object.prototype, so a +// polluted Object.prototype.sanitize is never mistaken for a real sanitizer. Class-based sanitizers, +// whose method lives on their own prototype below Object.prototype, still qualify. Tolerant of a +// hostile getter on the lookup path, which is treated as "not a sanitizer". +function looksLikeSanitizer(raw: unknown): boolean { + try { + for (let o: unknown = raw; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) { + if (own(o, 'sanitize')) return typeof (o as { sanitize?: unknown }).sanitize === 'function'; + } + } catch { + /* a throwing getter on the chain means we cannot trust it as a sanitizer */ + } + return false; +} + +// Normalize whatever the caller handed us into a sanitizer with a `.sanitize` method, or null. +// DOMPurify's export is itself a callable factory that ALSO carries `.sanitize`, so we must check for +// `.sanitize` FIRST - otherwise we'd wrap the factory and call the wrong thing. A bare function (e.g. a +// Sanitizer-API adapter) has no `.sanitize` and falls through to the function case. +function resolveSanitizer(raw: unknown): Sanitizer | null { + if (raw && looksLikeSanitizer(raw)) return raw as Sanitizer; + if (typeof raw === 'function') return { sanitize: raw as SanitizeFn }; + return null; +} + +// The trusted-types directive for INJECT_META. META_DIRECTIVE wins; otherwise we list the policies +// that will exist: our own `default`, plus `dompurify` unless a bare-function sanitizer is in use. +function metaDirective(md: unknown, functionSanitizer: boolean): string { + if (typeof md === 'string' && md) return md; + const ttNames = functionSanitizer ? 'default' : 'default dompurify'; + return `require-trusted-types-for 'script'; trusted-types ${ttNames};`; +} + +// Exercise the sanitizer once so a broken one fails loudly here, not silently on the first real write. +// It must return a string; anything else would inject junk into every sink. +function smokeTest(sanitizer: Sanitizer, config: unknown): { ready: boolean; error: string | null } { + try { + const out = sanitizer.sanitize('x', config); + return typeof out === 'string' + ? { ready: true, error: null } + : { ready: false, error: 'sanitize() did not return a string' }; + } catch (e) { + return { ready: false, error: emsg(e) }; + } +} + +// --- the default policy -------------------------------------------------------------------------- + +// createHTML: route through the sanitizer, fail closed on any problem. `reentry` is true only while +// the sanitizer parses our input internally (inert and synchronous), so handing the raw string back +// is safe and keeps us alive if the sanitizer's own sink re-enters us. +function makeSanitizeHTML( + sanitizer: Sanitizer | null, + config: unknown, + ready: boolean, + report: Report, +): (s: string) => string | null { + let reentry = false; + return (s: string): string | null => { + if (!ready) { + report('sanitizer-unavailable', { sink: 'createHTML' }); + return null; // fail closed + } + if (reentry) return s; + try { + reentry = true; + return (sanitizer as Sanitizer).sanitize(s, config) as string; + } catch (e) { + report('sanitize-threw', { error: emsg(e) }); + return null; // fail closed - never hand back raw markup on error + } finally { + reentry = false; + } + }; +} + +// createScript / createScriptURL: code has no safe subset, so refuse by default. A caller hook may +// allow specific values; if it throws or returns a non-string, we refuse. +function makeScriptHook( + kind: 'createScript' | 'createScriptURL', + fn: ScriptHook | null, + report: Report, +): (s: string) => string | null { + return (s: string): string | null => { + if (fn) { + let r: unknown; + try { + r = fn(s); + } catch (e) { + report('script-hook-threw', { sink: kind, error: emsg(e) }); + return null; // fail closed + } + if (typeof r === 'string') { + report('script-sink-allowed', { sink: kind }); + return r; + } + } + report('script-sink-refused', { sink: kind, sample: clip(s) }); + return null; + }; +} + +// --- public entry point -------------------------------------------------------------------------- + export function init(options: DOMFortifyConfig = {}): Readonly { if (installed) return cachedStatus as Readonly; installed = true; + // The violation reporter is observability, never control flow. Wrap it so a throwing ON_VIOLATION + // can neither abort init() (which would leave us installed with a null status) nor turn a + // fail-closed sink - one that should quietly return null - into a thrown exception. const onv = cfg(options, 'ON_VIOLATION'); - const report = (typeof onv === 'function' ? onv : () => {}) as (code: ViolationCode, detail?: unknown) => void; + const report: Report = + typeof onv === 'function' + ? (code, detail) => { + try { + (onv as Report)(code, detail); + } catch { + /* a misbehaving reporter must never break the policy */ + } + } + : () => {}; const status: DOMFortifyStatus = { version: VERSION, @@ -143,188 +242,139 @@ export function init(options: DOMFortifyConfig = {}): Readonly const done = (reason: string, code?: ViolationCode): Readonly => { status.protected = status.defaultPolicyOwned && status.enforcementActive && status.sanitizerReady; status.reason = reason; - if (code) report(code, status); + // Freeze the snapshot first, then report it: the reporter sees exactly the authoritative status + // that gets cached and returned, and has no window to mutate the cached copy. cachedStatus = Object.freeze({ ...status }); + if (code) report(code, cachedStatus); return cachedStatus; }; - const url = loc && typeof loc.href !== 'undefined' ? String(loc.href) : ''; + try { + const url = loc && typeof loc.href !== 'undefined' ? String(loc.href) : ''; + + // EXCLUDE: on a match, stay completely out of the way - no policy, no meta. We do NOT install a + // passthrough (that would be a silent XSS hole); under globally delivered enforcement, excluded + // pages are the developer's responsibility. Reported via status.excluded. + if (urlMatches(cfg(options, 'EXCLUDE') as UrlPattern | UrlPattern[] | undefined, url)) { + status.excluded = true; + return done('URL matched EXCLUDE; DOMFortify is intentionally inactive on this page.', 'excluded-by-url'); + } - // EXCLUDE: on a matching URL, DOMFortify stays completely out of the way - no policy, no meta. It - // does NOT install a passthrough (that would be a silent XSS hole); under globally delivered - // enforcement, excluded pages are the developer's responsibility. Reported via status.excluded. - if (urlMatches(cfg(options, 'EXCLUDE') as UrlPattern | UrlPattern[] | undefined, url)) { - status.excluded = true; - return done('URL matched EXCLUDE; DOMFortify is intentionally inactive on this page.', 'excluded-by-url'); - } + // INCLUDE: the allow-list complement of EXCLUDE. When set, activate ONLY on matching URLs and stay + // inactive (no policy, no meta) elsewhere. EXCLUDE is checked first, so it wins for URLs matching + // both. Like EXCLUDE, this only scopes activation safely when enforcement is page-scoped too. + const include = cfg(options, 'INCLUDE') as UrlPattern | UrlPattern[] | undefined; + if (include != null && !urlMatches(include, url)) { + status.excluded = true; + return done( + 'URL is outside INCLUDE scope; DOMFortify is intentionally inactive on this page.', + 'outside-include-scope', + ); + } - if (!TT || typeof TT.createPolicy !== 'function') { - return done('Trusted Types not supported; library is inert. Sinks are NOT routed.', 'tt-unsupported'); - } + if (!TT || typeof TT.createPolicy !== 'function') { + return done('Trusted Types not supported; library is inert. Sinks are NOT routed.', 'tt-unsupported'); + } - // URL_CONFIG: the first rule whose `match` hits supplies per-URL overrides. `eff(key)` reads that - // rule's own key when present, else falls back to the base config - both own-key only, so a polluted - // prototype can neither inject a rule nor loosen a refusal. - let override: Record | null = null; - const rules = cfg(options, 'URL_CONFIG'); - if (Array.isArray(rules)) { - for (let i = 0; i < rules.length; i++) { - const r = rules[i] as UrlConfigRule | undefined; - if (r && urlMatches(r.match, url)) { - override = r as unknown as Record; - break; - } + // Resolve config once. `eff(key)` reads the matching URL_CONFIG rule's own key when present, else the + // base config - both own-key only. Nothing is re-read later, so runtime clobbering can't retarget + // the policy after this point either. + const override = selectOverride(options, url); + const eff = (key: string): unknown => (override && own(override, key) ? override[key] : cfg(options, key)); + + // INJECT_META (opt-in, best-effort - see injectMeta and the README). + if (cfg(options, 'INJECT_META') === true) { + const directive = metaDirective(cfg(options, 'META_DIRECTIVE'), typeof eff('SANITIZER') === 'function'); + status.metaInjected = injectMeta(directive); + report('meta-injection-attempted', { directive, written: status.metaInjected }); } - } - const eff = (key: string): unknown => (override && own(override, key) ? override[key] : cfg(options, key)); - - // INJECT_META (opt-in, best-effort - see injectMeta and the README). We only attempt it when TT is - // supported; the directive lists the policies that will exist: our own `default`, plus `dompurify` - // unless a bare-function sanitizer (e.g. the native Sanitizer API) is in use. META_DIRECTIVE overrides. - if (cfg(options, 'INJECT_META') === true) { - const md = cfg(options, 'META_DIRECTIVE'); - const ttNames = typeof eff('SANITIZER') === 'function' ? 'default' : 'default dompurify'; - const directive = - typeof md === 'string' && md ? md : `require-trusted-types-for 'script'; trusted-types ${ttNames};`; - status.metaInjected = injectMeta(directive); - report('meta-injection-attempted', { directive, written: status.metaInjected }); - } - status.enforcementActive = enforcementActive(); - - // Resolve config once, reading own keys only so a polluted prototype can't supply a value - and, - // most importantly, can't loosen a refusal. Nothing is re-read later, so runtime clobbering can't - // retarget the policy either. URL_CONFIG overrides are applied here via `eff`. - let rawSan: unknown = eff('SANITIZER'); - if (rawSan === undefined) rawSan = (root as unknown as { DOMPurify?: unknown }).DOMPurify; - // DOMPurify's export is itself a callable function (the factory) that also exposes `.sanitize`, so - // check for a `.sanitize` method FIRST - otherwise we'd wrap the factory and call the wrong thing. A - // bare function (e.g. a Sanitizer-API adapter) has no `.sanitize` and falls through to the function case. - const DP: Sanitizer | null = - rawSan && typeof (rawSan as Sanitizer).sanitize === 'function' - ? (rawSan as Sanitizer) - : typeof rawSan === 'function' - ? { sanitize: rawSan as SanitizeFn } - : null; - const rawCfg = eff('SANITIZER_CONFIG'); - const sanitizeConfig = - rawCfg && typeof rawCfg === 'object' ? shallowCopy(rawCfg as Record) : undefined; - - // Sink openers count only if they're own functions, so prototype pollution can never open a sink. - const asCand = eff('ALLOW_SCRIPT'); - const asuCand = eff('ALLOW_SCRIPT_URL'); - const allowScript = typeof asCand === 'function' ? (asCand as ScriptHook) : null; - const allowScriptURL = typeof asuCand === 'function' ? (asuCand as ScriptHook) : null; - - // Smoke-test once so a broken sanitizer fails loudly here, not silently on the first real write. It - // must return a string - a sanitizer that returns anything else would otherwise inject junk. - let sanitizerReady = false; - if (DP && typeof DP.sanitize === 'function') { - try { - sanitizerReady = typeof DP.sanitize('x', sanitizeConfig) === 'string'; - if (!sanitizerReady) report('sanitizer-smoketest-failed', { error: 'sanitize() did not return a string' }); - } catch (e) { - report('sanitizer-smoketest-failed', { error: emsg(e) }); + status.enforcementActive = enforcementActive(); + + // Sanitizer: explicit SANITIZER (possibly per-URL), else window.DOMPurify. Config is forwarded + // verbatim as the second argument, copied to drop pollution-prone keys. + let rawSan: unknown = eff('SANITIZER'); + if (rawSan === undefined) rawSan = (root as unknown as { DOMPurify?: unknown }).DOMPurify; + const sanitizer = resolveSanitizer(rawSan); + const rawCfg = eff('SANITIZER_CONFIG'); + const sanitizeConfig = + rawCfg && typeof rawCfg === 'object' ? shallowCopy(rawCfg as Record) : undefined; + + // Sink openers count only if they're own functions, so prototype pollution can never open a sink. + const asCand = eff('ALLOW_SCRIPT'); + const asuCand = eff('ALLOW_SCRIPT_URL'); + const allowScript = typeof asCand === 'function' ? (asCand as ScriptHook) : null; + const allowScriptURL = typeof asuCand === 'function' ? (asuCand as ScriptHook) : null; + + let sanitizerReady = false; + if (sanitizer) { + const result = smokeTest(sanitizer, sanitizeConfig); + sanitizerReady = result.ready; + if (!result.ready) report('sanitizer-smoketest-failed', { error: result.error }); } - } - status.sanitizerReady = sanitizerReady; + status.sanitizerReady = sanitizerReady; - // `reentry` is true only while the sanitizer parses our input internally - inert and synchronous - so - // handing the raw string straight back is safe, and keeps us alive if its own sink re-enters us. - let reentry = false; - const sanitizeHTML = (s: string): string | null => { - if (!sanitizerReady) { - report('sanitizer-unavailable', { sink: 'createHTML' }); - return null; // fail closed + // createHTML closes over sanitizeConfig; the script hooks refuse unless an own-function hook allows. + const policyDef = { + createHTML: makeSanitizeHTML(sanitizer, sanitizeConfig, sanitizerReady, report), + createScript: makeScriptHook('createScript', allowScript, report), + createScriptURL: makeScriptHook('createScriptURL', allowScriptURL, report), + }; + + // Did someone grab the default slot first? We can't evict them and won't vouch for them. + if (TT.defaultPolicy) { + return done( + 'A default Trusted Types policy already exists; DOMFortify did NOT install and cannot vouch for it. ' + + 'Load DOMFortify first, inline in .', + 'preexisting-default-policy', + ); } - if (reentry) return s; + + let ours: unknown; try { - reentry = true; - return (DP as Sanitizer).sanitize(s, sanitizeConfig) as string; + ours = TT.createPolicy('default', policyDef); } catch (e) { - report('sanitize-threw', { error: emsg(e) }); - return null; // fail closed - never hand back raw markup on error - } finally { - reentry = false; + // Throws when a default policy exists and 'allow-duplicates' is off - someone won the race. + return done( + `createPolicy("default") threw (${emsg(e)}); another default policy won the race.`, + 'default-policy-lost', + ); } - }; - // Code has no safe subset, so refuse by default. A caller hook may allow specific values; if it throws - // or returns a non-string, we refuse. - const scriptHook = - (kind: 'createScript' | 'createScriptURL', fn: ScriptHook | null) => - (s: string): string | null => { - if (fn) { - let r: unknown; - try { - r = fn(s); - } catch (e) { - report('script-hook-threw', { sink: kind, error: emsg(e) }); - return null; // fail closed - } - if (typeof r === 'string') { - report('script-sink-allowed', { sink: kind }); - return r; - } - } - report('script-sink-refused', { sink: kind, sample: clip(s) }); - return null; - }; + // With 'allow-duplicates' the create can succeed yet not be the active default. + if (TT.defaultPolicy && TT.defaultPolicy !== ours) { + return done( + 'Our policy was created but is not the active default (allow-duplicates race lost). ' + + 'Remove "allow-duplicates" from the trusted-types directive.', + 'default-policy-not-active', + ); + } - const policyDef = { - createHTML: sanitizeHTML, - createScript: scriptHook('createScript', allowScript), - createScriptURL: scriptHook('createScriptURL', allowScriptURL), - }; + status.defaultPolicyOwned = true; - // Did someone grab the default slot first? We can't evict them and won't vouch for them. - if (TT.defaultPolicy) { + if (!status.enforcementActive) { + return done( + 'Default policy installed and slot locked, but TT enforcement is NOT active - sinks are not routed. ' + + 'Deliver require-trusted-types-for (header preferred).', + 'enforcement-inactive', + ); + } + if (!sanitizerReady) { + return done( + 'Enforcement active and slot locked, but the sanitizer is unavailable - HTML sinks will THROW ' + + '(failing closed). Bundle DOMPurify and load it before DOMFortify.', + 'failing-closed', + ); + } return done( - 'A default Trusted Types policy already exists; DOMFortify did NOT install and cannot vouch for it. ' + - 'Load DOMFortify first, inline in .', - 'preexisting-default-policy', + `Active: HTML sinks sanitized, script sinks ${allowScript || allowScriptURL ? 'partly allowed by hooks' : 'refused'}.`, ); - } - - let ours: unknown; - try { - ours = TT.createPolicy('default', policyDef); } catch (e) { - // Throws when a default policy exists and 'allow-duplicates' is off - someone won the race. - return done( - `createPolicy("default") threw (${emsg(e)}); another default policy won the race.`, - 'default-policy-lost', - ); - } - - // With 'allow-duplicates' the create can succeed yet not be the active default. - if (TT.defaultPolicy && TT.defaultPolicy !== ours) { - return done( - 'Our policy was created but is not the active default (allow-duplicates race lost). ' + - 'Remove "allow-duplicates" from the trusted-types directive.', - 'default-policy-not-active', - ); - } - - status.defaultPolicyOwned = true; - - if (!status.enforcementActive) { - return done( - 'Default policy installed and slot locked, but TT enforcement is NOT active - sinks are not routed. ' + - 'Deliver require-trusted-types-for (header preferred).', - 'enforcement-inactive', - ); - } - if (!sanitizerReady) { - return done( - 'Enforcement active and slot locked, but the sanitizer is unavailable - HTML sinks will THROW ' + - '(failing closed). Bundle DOMPurify and load it before DOMFortify.', - 'failing-closed', - ); + // Defense in depth: init() must never throw or leave the library bricked with a null status. A + // hostile getter or exotic environment that slips past the guards above fails closed here, with a + // real status object still cached and returned. + return done(`init() hit an unexpected error (${emsg(e)}); failing closed.`, 'failing-closed'); } - return done( - `Active: HTML sinks sanitized, script sinks ${allowScript || allowScriptURL ? 'partly allowed by hooks' : 'refused'}.`, - ); } export function status(): Readonly | null { diff --git a/src/internal.ts b/src/internal.ts new file mode 100644 index 0000000..a1bff1b --- /dev/null +++ b/src/internal.ts @@ -0,0 +1,74 @@ +/** + * Internal helpers shared by DOMFortify. Everything here is pure and free of side effects: no DOM, + * no Trusted Types, no module state. The environment captures and the policy logic live in + * fortify.ts; these are the small building blocks it leans on. + */ +import type { UrlPattern } from './types'; + +// Cached up front so later prototype pollution or clobbering can't swap hasOwnProperty out. +const hasOwn = Object.prototype.hasOwnProperty; + +/** True only for an own (non-inherited) property, so a polluted prototype is never consulted. */ +export function own(obj: unknown, key: string): boolean { + return obj != null && hasOwn.call(obj, key); +} + +/** Read an own key off a config-like object, else undefined. Never walks the prototype chain. */ +export function cfg(obj: unknown, key: string): unknown { + return own(obj, key) ? (obj as Record)[key] : undefined; +} + +/** A short, safe preview of an arbitrary value, for violation reports. */ +export function clip(s: unknown): string { + return String(s).slice(0, 80); +} + +/** + * Best-effort error message, tolerant of non-Error throws. Must never throw itself: it runs inside + * init()'s catch and several sink catches, so a hostile error whose `message` is a throwing getter + * must not be able to re-throw from here and brick init(). Falls back to a constant. + */ +export function emsg(e: unknown): string { + try { + return String((e as { message?: unknown } | undefined)?.message); + } catch { + return 'unknown error'; + } +} + +/** + * Copy an object's own keys, dropping the three that could pollute a prototype. Deliberately not a + * JSON clone: that would corrupt the RegExps and functions a sanitizer config may carry. + */ +export function shallowCopy(obj: Record): Record { + const out: Record = {}; + for (const k in obj) { + if (hasOwn.call(obj, k) && k !== '__proto__' && k !== 'constructor' && k !== 'prototype') { + out[k] = obj[k]; + } + } + return out; +} + +/** + * Test a URL against one or more patterns. A string matches as a substring (the empty string never + * matches); a RegExp is test()ed, and a pattern that throws is treated as no match. Used for both + * EXCLUDE and URL_CONFIG, always against the realm's own location.href. + */ +export function urlMatches(pattern: UrlPattern | UrlPattern[] | undefined, url: string): boolean { + if (pattern == null) return false; + const list = Array.isArray(pattern) ? pattern : [pattern]; + for (let i = 0; i < list.length; i++) { + const p = list[i]; + if (typeof p === 'string') { + if (p !== '' && url.indexOf(p) !== -1) return true; + } else if (p instanceof RegExp) { + try { + if (p.test(url)) return true; + } catch { + /* a pattern that throws is treated as no match */ + } + } + } + return false; +} diff --git a/src/types.ts b/src/types.ts index 812e080..93fa984 100644 --- a/src/types.ts +++ b/src/types.ts @@ -44,6 +44,7 @@ export type ViolationCode = | 'default-policy-not-active' | 'enforcement-inactive' | 'excluded-by-url' + | 'outside-include-scope' | 'meta-injection-attempted' | 'failing-closed'; @@ -63,6 +64,16 @@ export interface DOMFortifyConfig { * meta. Matched against `location.href` (string = substring, RegExp = test). */ EXCLUDE?: UrlPattern | UrlPattern[]; + + /** + * Allow-list complement of `EXCLUDE`. When set, DOMFortify activates ONLY on URLs that match and + * stays completely inactive (no policy, no meta) everywhere else - useful for scoping a rollout to + * specific routes. `EXCLUDE` still wins for a URL that matches both. Matched against `location.href` + * (string = substring, RegExp = test). Best paired with page-scoped enforcement (e.g. INJECT_META): + * under a globally delivered enforcement header, non-included pages have enforcement on but no + * default policy, so their sinks fail closed. + */ + INCLUDE?: UrlPattern | UrlPattern[]; /** Per-URL configuration overrides; the first matching rule's keys override the base config. */ URL_CONFIG?: UrlConfigRule[]; /** @@ -86,7 +97,7 @@ export interface DOMFortifyStatus { defaultPolicyOwned: boolean; /** Whether the sanitizer passed its smoke test. */ sanitizerReady: boolean; - /** Whether the current URL matched `EXCLUDE` (DOMFortify intentionally inactive). */ + /** Whether the URL is out of scope (matched `EXCLUDE`, or fell outside `INCLUDE`); inactive here. */ excluded: boolean; /** Whether a CSP `` injection was attempted via document.write this load. */ metaInjected: boolean; diff --git a/test/e2e/deployment.spec.ts b/test/e2e/deployment.spec.ts index 80d0307..0d6c058 100644 --- a/test/e2e/deployment.spec.ts +++ b/test/e2e/deployment.spec.ts @@ -112,6 +112,11 @@ for (const file of readdirSync(FIXTURE_DIR).filter((f) => f.endsWith('.html'))) expect(fired, 'with nothing enforcing Trusted Types the DOM-XSS should fire').toBe(true); expect(status?.protected, 'DOMFortify must not claim protection it does not have').toBeFalsy(); } else if (expectKind === 'protected') { + // Native Trusted Types enforcement is now Baseline, but a given Playwright engine build may + // predate it. The protection guarantee only exists where enforcement is actually on, so gate + // the assertion on it: on an enforcing engine we prove neutralization, elsewhere we skip rather + // than assert a guarantee the platform isn't providing (DOMFortify reports this honestly). + test.skip(!status?.enforcementActive, 'engine build does not enforce Trusted Types natively'); expect(fired, 'the payload must be neutralized under enforcement').toBe(false); expect(status?.protected, 'DOMFortify should report the page as protected').toBe(true); } else if (expectKind === 'best-effort') { @@ -130,14 +135,41 @@ for (const file of readdirSync(FIXTURE_DIR).filter((f) => f.endsWith('.html'))) // level, so a non-fire there is a browser win, not a DOMFortify bug. for (const v of VECTORS) { if (v.firesUnprotected) { - test(`vector ${v.kind}/${v.name}: fires on the unprotected page`, async ({ page }: { page: Dialoged }) => { + test(`vector ${v.kind}/${v.name}: fires on the unprotected page`, async ({ + page, + }: { page: Dialoged }, testInfo) => { + // The firesUnprotected corpus is calibrated on the reference engine; Firefox/WebKit parse some + // vectors (e.g. svg/onload) differently, so a non-fire there is a browser win, not a real miss. + test.skip(testInfo.project.name !== 'chromium', 'firesUnprotected canary runs on the reference engine'); const { fired } = await visit(page, 'unprotected.html', v.payload); expect(fired, `${v.name} should execute when nothing is enforcing`).toBe(true); }); } test(`vector ${v.kind}/${v.name}: neutralized under DOMFortify`, async ({ page }: { page: Dialoged }) => { const { fired, status } = await visit(page, 'meta.html', v.payload); + // Only assert the protection guarantee where the engine actually enforces Trusted Types (see the + // note in the deployment matrix above). Non-enforcing engine builds skip rather than fail. + test.skip(!status?.enforcementActive, 'engine build does not enforce Trusted Types natively'); expect(status?.protected, 'page should be protected').toBe(true); expect(fired, `${v.name} must not execute under enforcement`).toBe(false); }); } + +// --- Legacy-library regression: 0.4.0 must not break when heavy libraries are on the page --------- +// jQuery and AngularJS run internal innerHTML (and AngularJS Function/eval) as they load under +// enforcement, AFTER DOMFortify has claimed the default policy. The libraries themselves may not fully +// initialise under Trusted Types - AngularJS needs Function/eval, which DOMFortify refuses by design, +// and old jQuery's feature-detection can break on sanitized markup. That is inherent to TT enforcement, +// not a DOMFortify regression, so we do not assert the library's own global. What DOMFortify must +// guarantee, and what this asserts, is that its HTML-sink protection stays intact while the library's +// script runs on the page: it stays ready, reports protected, and the payload is neutralized. This +// reproduces the hosted demo's environment so a real regression is caught in CI before release. +for (const file of ['with-jquery.html', 'with-angularjs.html']) { + test(`legacy library present: ${file} keeps DOMFortify ready and protected`, async ({ page }: { page: Dialoged }) => { + const { fired, status } = await visit(page, file, REFERENCE); + test.skip(!status?.enforcementActive, 'engine build does not enforce Trusted Types natively'); + expect(status?.sanitizerReady, 'sanitizer must stay ready with the library present').toBe(true); + expect(status?.protected, 'DOMFortify must stay protected with the library present').toBe(true); + expect(fired, 'the payload must be neutralized with the library present').toBe(false); + }); +} diff --git a/test/e2e/sink-boundary.spec.ts b/test/e2e/sink-boundary.spec.ts new file mode 100644 index 0000000..dc28e8e --- /dev/null +++ b/test/e2e/sink-boundary.spec.ts @@ -0,0 +1,83 @@ +/** + * Sink-boundary coverage matrix. + * + * Proves, vector by vector, exactly where DOMFortify's protection begins and ends, so a future change + * cannot silently reopen a sink. Every vector runs in genuine page context inside the fixture (see the + * note in sink-boundary.runner.js for why this must not move into page.evaluate); the spec only reads + * the recorded result after a short settle that lets async sinks (string setTimeout, script.src) land. + * + * COVERED: must be neutralized under DOMFortify, and must execute on the unprotected canary (which + * proves each vector is a real, working sink and that the detector sees it). + * BOUNDARY: outside the Trusted Types contract by design (function-handler assignment); documented, + * not guarded as a vulnerability. + */ +import { test, expect, type Page } from '@playwright/test'; + +const COVERED = [ + 'innerHTML', + 'outerHTML', + 'insertAdjacentHTML', + 'createContextualFragment', + 'template.innerHTML', + 'eval', + 'Function', + 'setTimeout(string)', + 'script.text', + 'script.src', + 'setAttribute-onclick', +]; +const BOUNDARY = ['el.onclick = fn']; + +interface Probe { + status: Record | null; + fired: Record; + matrix: { label: string; category: string; threw: boolean; msg: string }[]; +} + +async function probe(page: Page, fixture: string): Promise { + await page.goto(`/test/fixtures/${fixture}`); + await page.waitForFunction(() => (window as unknown as { __matrixReady?: boolean }).__matrixReady === true, null, { + timeout: 5_000, + }); + await page.waitForTimeout(300); // let async sinks (string setTimeout, script.src) settle + return page.evaluate(() => ({ + status: + ( + window as unknown as { DOMFortify?: { status?: () => Record | null } } + ).DOMFortify?.status?.() ?? null, + fired: (window as unknown as { __fired: Record }).__fired, + matrix: (window as unknown as { __matrix: Probe['matrix'] }).__matrix, + })); +} + +// --- Canary: on an unprotected page every covered vector must execute ----------------------------- +test('canary: every covered sink executes on the unprotected page', async ({ page }) => { + const { fired } = await probe(page, 'sink-boundary-unprotected.html'); + for (const label of COVERED) { + expect(fired[label], `${label} must execute when nothing is enforcing, or the test is meaningless`).toBe(true); + } +}); + +// --- Protected: every covered vector is neutralized ----------------------------------------------- +test('every covered sink is neutralized under DOMFortify', async ({ page }) => { + const { status, fired } = await probe(page, 'sink-boundary.html'); + // Only assert the guarantee where the engine actually enforces Trusted Types (matches the rest of + // the e2e suite); non-enforcing engine builds skip rather than fail. + test.skip(!status?.enforcementActive, 'engine build does not enforce Trusted Types natively'); + expect(status?.protected, 'page should report protected').toBe(true); + for (const label of COVERED) { + expect(fired[label] ?? false, `${label} must be neutralized under enforcement`).toBe(false); + } +}); + +// --- Boundary: function-handler assignment is outside the contract and stays so -------------------- +test('boundary sinks remain outside the Trusted Types contract (documented, not guarded)', async ({ page }) => { + const { status, fired } = await probe(page, 'sink-boundary.html'); + test.skip(!status?.enforcementActive, 'engine build does not enforce Trusted Types natively'); + for (const label of BOUNDARY) { + expect( + fired[label] ?? false, + `${label} sits outside Trusted Types; if this changes, re-evaluate the threat model and docs`, + ).toBe(true); + } +}); diff --git a/test/fixtures/sink-boundary-unprotected.html b/test/fixtures/sink-boundary-unprotected.html new file mode 100644 index 0000000..0be9e72 --- /dev/null +++ b/test/fixtures/sink-boundary-unprotected.html @@ -0,0 +1,12 @@ + + + + + + +
+ + + diff --git a/test/fixtures/sink-boundary.html b/test/fixtures/sink-boundary.html new file mode 100644 index 0000000..7d12fc6 --- /dev/null +++ b/test/fixtures/sink-boundary.html @@ -0,0 +1,17 @@ + + + + + + + + + + +
+ + + diff --git a/test/fixtures/sink-boundary.runner.js b/test/fixtures/sink-boundary.runner.js new file mode 100644 index 0000000..9e1793d --- /dev/null +++ b/test/fixtures/sink-boundary.runner.js @@ -0,0 +1,123 @@ +/** + * Sink-boundary matrix runner (shared by the protected fixture and the unprotected canary). + * + * It runs every vector in GENUINE page context - this file executes as the page loads, NOT through + * Playwright's page.evaluate. That is deliberate and load-bearing: page.evaluate runs in a CDP context + * that bypasses Trusted Types enforcement for eval()/Function() specifically (innerHTML and setTimeout + * stay gated), so measuring those from page.evaluate falsely reports them as executed. Driving the + * sinks from the page itself is the only honest way to test eval/Function. Do not "simplify" this into + * page.evaluate. + * + * Each payload calls window.__H('