-
Notifications
You must be signed in to change notification settings - Fork 1
Installation and Usage
From a CDN, loaded first thing in <head>, with both the sanitizer and DOMFortify pinned by SRI:
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.4.11/dist/purify.min.js"
integrity="sha384-..." crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/domfortify/dist/fortify.min.js"
integrity="sha384-..." crossorigin="anonymous"></script>Pin a version you have vetted and regenerate the SRI when you change it. The README carries the current hashes for the published versions.
From npm:
npm install domfortify
import DOMFortify from 'domfortify';
import DOMPurify from 'dompurify';
DOMFortify.init({ SANITIZER: DOMPurify }); // run this as early as possibleThe npm package ships three builds: dist/fortify.es.mjs (ESM), dist/fortify.cjs.js (CJS), and dist/fortify.min.js (a self-installing IIFE for a plain <script>). In a bundle there is no window.DOMPurify, so pass SANITIZER explicitly, and make sure init() runs before any code that writes to the DOM.
Trusted Types only routes sinks through a policy when enforcement is on. You do not strictly have to set an external CSP yourself, but you do need enforcement from somewhere. In order of robustness:
-
A response header (sturdiest):
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types default dompurify; -
A
<meta>tag placed in the HTML at parse time:<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'; trusted-types default dompurify">
-
Let DOMFortify inject that
<meta>for you, with one flag:window.DOMFortifyConfig = { INJECT_META: true };
This is best-effort: it only takes when DOMFortify runs during the initial parse (inline, first thing in
<head>), because a<meta>CSP is honored only when the parser inserts it. A header or a hand-placed<meta>is still preferable. Confirm it took withstatus().enforcementActive.
Set window.DOMFortifyConfig before the script runs, or pass the same object to init().
| Option | What it does |
|---|---|
SANITIZER |
The sanitizer: an object with .sanitize(str, cfg), or a plain function(str, cfg). Defaults to window.DOMPurify. This is how you swap in the native Sanitizer API. |
SANITIZER_CONFIG |
Passed to the sanitizer as its second argument. |
ALLOW_SCRIPT |
function(code) => string | null. Return a string to allow that exact code at a script sink, else it is refused. Refuses everything by default. |
ALLOW_SCRIPT_URL |
function(url) => string | null. Same idea for script URLs. |
ON_VIOLATION |
function(code, detail). Fires on every notable event. Your report-only on-ramp. |
EXCLUDE |
URL pattern(s) (substring or RegExp) where DOMFortify stays completely inactive. No policy, no meta. |
URL_CONFIG |
Per-URL overrides; the first matching rule's keys override the base config. |
INJECT_META |
Opt-in, default off. Best-effort injection of the enabling CSP <meta>. See above. |
META_DIRECTIVE |
Override the full trusted-types directive used when INJECT_META is on (advanced). |
Config is read own-key only, so a polluted prototype cannot quietly loosen anything, and script openers only count if they are own functions.
init() returns a frozen status object, and status() returns it again later. It is the source of truth for whether you are actually protected.
const s = DOMFortify.status();
// { version, ttSupported, enforcementActive, defaultPolicyOwned,
// sanitizerReady, excluded, metaInjected, protected, reason }protected is true only when enforcement is on, DOMFortify owns the default policy, and the sanitizer passed its smoke test. reason is a human-readable explanation of the current state, which is especially useful when protected is false.
For monitoring, wire ON_VIOLATION to your telemetry and run report-only for a while before you depend on enforcement:
DOMFortify.init({
SANITIZER: DOMPurify,
ON_VIOLATION: (code, detail) => sendToTelemetry(code, detail),
});See How It Works for what happens to a write once enforcement is on, and Risks and Footguns before you ship.