Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules/
dist/fortify.cov.*
coverage/
.nyc_output/
*.log
Expand Down
26 changes: 17 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# 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](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) [![CodeQL](https://github.com/cure53/DOMFortify/actions/workflows/codeql-analysis.yml/badge.svg?branch=main)](https://github.com/cure53/DOMFortify/actions/workflows/codeql-analysis.yml)
[![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)

[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/13287/badge)](https://www.bestpractices.dev/projects/13287) [![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)
[![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)

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
Expand Down Expand Up @@ -82,8 +82,8 @@ could reach. Pin both with SRI so a bad CDN day fails closed instead of open:
crossorigin="anonymous"
></script>
<script
src="https://cdn.jsdelivr.net/npm/domfortify@0.2.0/dist/fortify.min.js"
integrity="sha384-JXVhAk88k789tRT7GwtEyU9dJuJlu/Esv4Beq6FOrAXZYN59ykiQExs+vCBNNeYs"
src="https://cdn.jsdelivr.net/npm/domfortify@0.4.0/dist/fortify.min.js"
integrity="sha384-oaTzl0Zl3KdISWVHzJErLGpAIL2MYX+raPA5b9uhn/6Ljz117SCLRPKx8p8jhsyD"
crossorigin="anonymous"
></script>
```
Expand Down Expand Up @@ -293,11 +293,19 @@ It's a retrofit, not magic. Know the edges (the
- **Load it first.** Whoever registers the `default` policy first wins. If attacker code beats you to it,
you're worse off than before. Don't add `'allow-duplicates'`.
- **One realm at a time.** Each iframe is its own world and needs its own DOMFortify.
- **Trusted Types sinks only.** DOMFortify sanitizes the Trusted Types HTML sinks. Other sinks - `style`
and CSS injection, `javascript:` URLs, and inline handlers - sit outside that contract, and their
behavior under enforcement varies by browser. Close them definitively with a real CSP alongside the
Trusted Types one, for example `script-src 'self'; object-src 'none'; base-uri 'none'` (no
`'unsafe-inline'`).
- **Trusted Types sinks only.** DOMFortify covers exactly the Trusted Types sinks, and inside that
contract it is thorough. Every HTML sink (`innerHTML`, `outerHTML`, `insertAdjacentHTML`,
`document.write`, `Range.createContextualFragment`, `iframe.srcdoc`) is sanitized, so inline event
handlers and `javascript:` URLs that arrive as markup are stripped; every string-to-code sink
(`eval`, `Function`, string `setTimeout`/`setInterval`, `script.text`, `script.src`, Worker URLs) is
refused; and `setAttribute('onclick', ...)` is refused too, since the browser treats event-handler
content attributes as a TrustedScript sink. What sits outside the contract is only the residue no
Trusted Types policy can see: assigning a function to a handler property (`el.onclick = fn`, which
already presupposes script execution and is not reachable by markup injection), assigning a
`javascript:` URL straight to a property (`a.href = '...'`), and `style`/CSS injection. Close those
definitively with a real CSP alongside the Trusted Types one, for example
`script-src 'self'; object-src 'none'; base-uri 'none'` (no `'unsafe-inline'`). This boundary is
pinned by the `sink-boundary` e2e matrix.
- **One sanitizer.** A bypass in the sanitizer is a bypass in everything it guards.
- **It sanitizes a string, then the sink re-parses it.** The `default` policy returns sanitized HTML as a
string that the browser parses again in context - the serialize/re-parse step that can re-open
Expand Down
23 changes: 23 additions & 0 deletions config/rollup.coverage.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Coverage build: the ESM module instrumented with Istanbul, written to a separate file the node
* test suite imports when DOMFORTIFY_COV is set. Mirrors DOMPurify's instrument-then-nyc approach;
* Istanbul's per-file counters accumulate correctly across the suite's fresh-module-per-test imports,
* where V8 coverage would fragment on the cache-busting query.
*/
import { createRequire } from 'node:module';
import typescript from '@rollup/plugin-typescript';
import replace from '@rollup/plugin-replace';
import istanbul from 'rollup-plugin-istanbul';

const require = createRequire(import.meta.url);
const pkg = require('../package.json');

export default {
input: 'src/index.ts',
output: { file: 'dist/fortify.cov.es.mjs', format: 'es', sourcemap: true },
plugins: [
replace({ preventAssignment: true, values: { __VERSION__: pkg.version } }),
typescript({ tsconfig: './config/tsconfig.build.json' }),
istanbul({ include: ['src/**/*.ts'] }),
],
};
4 changes: 2 additions & 2 deletions dist/fortify.cjs.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions dist/fortify.es.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*! DOMFortify 0.4.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) */
// 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. */
Expand Down Expand Up @@ -78,7 +78,7 @@ function urlMatches(pattern, url) {
* - Fails closed: no sanitizer means sinks throw, never leak.
* - Only covers Trusted Types sinks; inline handlers / style / URL props stay open.
*/
const VERSION = '0.4.0';
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;
Expand Down
4 changes: 2 additions & 2 deletions dist/fortify.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading