Skip to content
Closed

0.x #33

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
10 changes: 5 additions & 5 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
6 changes: 3 additions & 3 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sign-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/slsa-provenance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
217 changes: 168 additions & 49 deletions README.md

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion config/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,9 @@ export default defineConfig({
reuseExistingServer: !process.env.CI,
timeout: 30_000,
},
projects: [{ name: 'chromium', use: { browserName: 'chromium' } }],
projects: [
{ name: 'chromium', use: { browserName: 'chromium' } },
{ name: 'firefox', use: { browserName: 'firefox' } },
{ name: 'webkit', use: { browserName: 'webkit' } },
],
});
13 changes: 13 additions & 0 deletions demos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,19 @@ window.DOMFortifyConfig = {
};
```

### Scoping with INCLUDE [Link](include-demo.html)

The allow-list complement of `EXCLUDE`: activate ONLY on matching URLs and stay inactive elsewhere.
Paired with `INJECT_META` so enforcement is scoped to the same pages, this is the gradual-rollout
pattern - protect a few routes first, leave the rest untouched. Add `?admin` and reload.

```js
window.DOMFortifyConfig = {
INCLUDE: [/[?&]admin\b/], // active only here
INJECT_META: true, // and enforcement scoped to the same pages
};
```

### Meta injection (best-effort) [Link](meta-inject-demo.html)

`INJECT_META` is an opt-in attempt to add the enabling CSP `<meta>` for pages that can set neither a
Expand Down
83 changes: 83 additions & 0 deletions demos/include-demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DOMFortify demo: Scoping with INCLUDE</title>
<link rel="stylesheet" href="lib/demo.css" />
<script src="https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.min.js"></script>
<!--
No static enforcement <meta> here on purpose. INCLUDE scopes WHERE DOMFortify activates, and it
only composes safely when enforcement is scoped the same way - so we let DOMFortify turn
enforcement on itself, per page, with INJECT_META. On an out-of-scope URL it stays inactive AND
injects nothing, so that page is simply left untouched (unprotected, not broken). This is the
gradual-rollout pattern: protect /admin first, leave everything else alone.
-->
<script>
window.DOMFortifyConfig = {
INCLUDE: [/[?&]admin\b/], // activate ONLY where the URL matches
INJECT_META: true, // and let DOMFortify scope enforcement to those same pages
SANITIZER_CONFIG: { ALLOWED_TAGS: ['b', 'i', 'p', 'a', '#text'], ALLOWED_ATTR: ['href'] },
};
</script>
<script src="https://cdn.jsdelivr.net/gh/cure53/DOMFortify@main/dist/fortify.js"></script>
</head>
<body>
<main>
<nav class="crumb"><a href="README.md">DOMFortify demos</a> / Scoping with INCLUDE</nav>
<h1>Scoping with INCLUDE</h1>
<p class="lead">
<code>INCLUDE</code> is the allow-list complement of <code>EXCLUDE</code>: DOMFortify activates
<em>only</em> on matching URLs and stays inactive everywhere else. This page keys off the query
string. <code>?admin</code> 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.
</p>

<div class="card">
<h2>Pick a URL</h2>
<p>
<a href="?">baseline (out of scope)</a> &nbsp;|&nbsp;
<a href="?admin">?admin (in scope)</a>
</p>
<p class="muted">Current: <code id="href"></code></p>
<p class="muted">excluded = <span id="excluded"></span> &nbsp; metaInjected =
<span id="meta"></span> &nbsp; protected = <span id="protected"></span></p>
</div>

<div class="card">
<h2>Same payload on both URLs</h2>
<textarea id="dirty">&lt;p&gt;&lt;b&gt;bold&lt;/b&gt; &lt;a href="https://example.com"&gt;link&lt;/a&gt;&lt;/p&gt;&lt;img src=x onerror="alert(1)"&gt;</textarea>
<button id="run">Run the sink</button>
<div class="muted">Resulting HTML:</div>
<pre id="html"></pre>
<p id="note"></p>
</div>
</main>
<script>
window.alert = () => {}; // so an out-of-scope, unprotected page cannot actually pop a dialog
const s = (window.DOMFortify && DOMFortify.status()) || {};
document.getElementById('href').textContent = location.href;
document.getElementById('excluded').textContent = String(!!s.excluded);
document.getElementById('meta').textContent = String(!!s.metaInjected);
document.getElementById('protected').textContent = String(!!s.protected);

const out = document.createElement('div');
const run = () => {
const note = document.getElementById('note');
try {
out.innerHTML = document.getElementById('dirty').value;
document.getElementById('html').textContent = out.innerHTML;
note.innerHTML = s.protected
? '<span class="status ok">In INCLUDE scope: enforcement injected, sink sanitized.</span>'
: '<span class="status bad">Out of INCLUDE scope: DOMFortify stood down, so this page is unprotected (the raw markup went straight in). On a real site, /admin would be protected and this route left as-is.</span>';
} catch (e) {
document.getElementById('html').textContent = '';
note.innerHTML = '<span class="status bad">Sink threw: ' + e.message + '</span>';
}
};
document.getElementById('run').addEventListener('click', run);
run();
</script>
</body>
</html>
Loading