Hint 1: An allowlist, not a blocklist
Name the tags and the one attribute you want with ALLOWED_TAGS and ALLOWED_ATTR. Do not start from the defaults and add things back with ADD_TAGS or ADD_ATTR.
Hint 1 unlocks after your first failed run.
Hint 2: A scheme allowlist for links
ALLOWED_URI_REGEXP decides which URL schemes survive in href. Anchor it to ^https: so javascript:, data: and others cannot match, and drop ALLOW_UNKNOWN_PROTOCOLS.
Hint 2 unlocks after 2 failed runs.
Reveal the reference patch and explanation
export const commentPolicy = {
ALLOWED_TAGS: ['p', 'strong', 'em', 'ul', 'ol', 'li', 'a'],
ALLOWED_ATTR: ['href'],
ALLOWED_URI_REGEXP: /^https:\/\//i,
};With ALLOWED_TAGS limited to the seven formatting tags, a comment’s script, img, style, iframe and svg are dropped while its p, strong, em, lists and links stay. With ALLOWED_ATTR limited to href, onerror, onclick, style and src attributes are removed, so an image error handler cannot run even if an img slipped through. ALLOWED_URI_REGEXP of ^https: keeps an https link and removes a javascript:, data: or relative href. The ^ matters: the pattern is tested against the whole href, so an unanchored /https:/ keeps javascript:steal()//https://x and http://evil.test/?https: because https: appears somewhere in them. The text of every comment is preserved, because sanitizing strips disallowed markup, not content.
Compare it with your own fix, then rerun the tests. The editor is not changed when you open this explanation.
The reference patch unlocks after you run code you have edited.
Inspect the actual test harness
The browser and the lab download run the same cases, including cases that catch common wrong fixes, such as comparing with one fixture value instead of the real rule. The download includes the complete test adapter and cryptographic fixtures.
Download cases.mjsHow the browser sandbox runs your code
Your code runs in QuickJS 2025-09-13 (quickjs-emscripten 0.32.0) inside a Web Worker, isolated from the page, with a 32 MiB memory limit and a 2.5-second time limit. The test cases and the pass or fail verdict run outside that sandbox, so changing built-ins such as Array.prototype.push cannot change the result. The lab download runs the same cases under Node.js, where your code runs directly.
The same as in current Node.js:
- ES2025 syntax and built-ins, including Set methods such as intersection, iterator helpers, Promise.try, RegExp.escape and the RegExp v flag
Added to the sandbox so that it behaves like Node.js:
- structuredClone, Array.fromAsync, queueMicrotask, atob and btoa
- URL and URLSearchParams, parsed by your browser’s own URL implementation
- TextEncoder and TextDecoder for UTF-8
- Intl.Collator and a locale-aware String.prototype.localeCompare
- console.log and related methods, whose output appears with your results
Not available in the browser sandbox:
- Timers (setTimeout, setInterval), fetch and other network or storage APIs; the exercises are deterministic and offline
- Node.js modules and globals: import, require, process and Buffer
- Web Crypto (crypto.getRandomValues, crypto.subtle); each workshop supplies the cryptographic operations it needs
- Intl other than Collator: toLocaleString and toLocaleDateString ignore the locale, so numbers have no digit grouping and dates use one fixed format in the device’s time zone
- using declarations, Symbol.dispose and DisposableStack (explicit resource management)
- Uint8Array.fromBase64 and toBase64, JSON.rawJSON, Atomics and Error.captureStackTrace
This configures DOMPurify 3; it is not the whole rich-text pipeline. Sanitize on the server after converting Markdown to HTML and before the HTML reaches a sink, keep the library current, and set a Content-Security-Policy as a second layer. A stricter link rule (no credentials, a host allowlist) needs an afterSanitizeAttributes hook, which this lab does not cover. DOMPurify returns a string; build the DOM from it with a safe sink, never with the raw comment.
Your next task: Does your sanitizer run from an allowlist you control, and is it applied after every transformation that could reintroduce markup?
DOMPurify configuration (README)
Related guide: Render rich text without granting it application privileges