diff --git a/README.md b/README.md index 312a061..67b4c2b 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,47 @@ # DOMFortify -[](https://www.npmjs.com/package/domfortify) [](https://github.com/cure53/DOMFortify/blob/main/LICENSE)  [](https://github.com/cure53/DOMFortify/actions/workflows/build-and-test.yml) [](https://scorecard.dev/viewer/?uri=github.com/cure53/DOMFortify) [](https://badge.socket.dev/npm/package/domfortify/latest) +[](https://www.npmjs.com/package/domfortify) [](https://github.com/cure53/DOMFortify/blob/main/LICENSE)  [](https://github.com/cure53/DOMFortify/actions/workflows/build-and-test.yml) [](https://github.com/cure53/DOMFortify/actions/workflows/codeql-analysis.yml) -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. +[](https://www.bestpractices.dev/projects/13287) [](https://scorecard.dev/viewer/?uri=github.com/cure53/DOMFortify) [](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.** ## 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. ## 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. + +It does two jobs and no more: own the `default` policy, and route sinks. Whether enforcement is even on +is a CSP's job, not the library's - so DOMFortify reports honestly, through `status()`, whether the page +is actually protected. -## Usage +## Quick start (CDN) -Two parts. First, turn enforcement on with a CSP - a response header if you can set one: +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 `