DSK-35Recommended
Insert text into web pages as text from content scripts
Content scripts put strings into a page with textContent, createElement and setAttribute, never with innerHTML, outerHTML, insertAdjacentHTML or document.write. HTML that must go in passes a sanitiser such as DOMPurify first. Controls the user clicks live in the extension's own popup or side panel.
How to do it
A content script that writes page text, API answers or stored notes as HTML opens a hole on every site the user visits. Stop cross-site scripting describes the attack. Mozilla tells extension authors to insert strings with safe DOM methods and to sanitise any HTML with an up-to-date DOMPurify. It also warns that page scripts can reach buttons or toolbars added to the page. Put such controls in the extension's own UI or in an iframe the page cannot touch. Never call window.eval() or read page objects through wrappedJSObject and then act on them. Messages the content script sends on follow treat messages from web pages and content scripts as untrusted.
Examples
JavaScript example
// content.js: the note comes from storage, so it goes in as text
export function showNote(note) {
const badge = document.createElement('span');
badge.className = 'ledgerly-note';
badge.textContent = note.text;
badge.setAttribute('title', note.author);
document.body.append(badge);
}
Python example
# test_content_script.py: a hostile page title must not run as script
from playwright.sync_api import sync_playwright
def test_title_is_inserted_as_text(extension_context_dir, extension_path):
with sync_playwright() as p:
browser = p.chromium.launch_persistent_context(extension_context_dir, args=[f"--load-extension={extension_path}"])
page = browser.new_page()
dialogs = []
page.on("dialog", lambda dialog: (dialogs.append(dialog.message), dialog.dismiss()))
page.set_content("<title><img src=x onerror=alert(1)></title><p>Invoice 42</p>")
page.wait_for_selector(".ledgerly-note")
assert dialogs == []
How to check
A lint rule such as eslint-plugin-no-unsanitized runs on every content script in CI and fails on an unsanitised HTML sink. A test page with the title <img src=x onerror=alert(1)> runs the extension, and no alert fires.
Sources
- Firefox Extension Workshop, Build a secure extension
- OWASP Cross Site Scripting Prevention Cheat Sheet
Related rules
- APS-3 Escape everything you show in Application security
- DSK-16 Treat messages from web pages and content scripts as untrusted in Desktop apps and extensions
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/desktop-apps/browser-extensions/content-script-dom">Agent Wranglers desktop apps and extensions playbook</a>. In code you write, add a comment next to it with the same link.