Skip to content

Feat/xss sanitizer - #4

Merged
f1anderz merged 15 commits into
mainfrom
feat/xss-sanitizer
Jul 30, 2026
Merged

Feat/xss sanitizer#4
f1anderz merged 15 commits into
mainfrom
feat/xss-sanitizer

Conversation

@f1anderz

Copy link
Copy Markdown
Owner

add sanitizer using xss

StasStepaniuk and others added 13 commits July 30, 2026 13:11
DOMPurify is rejected on capability grounds: it needs a live DOM, which
React Native cannot provide. js-xss is pure JS, sync, and works in
browser, Node, and Metro.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Prototyping against xss 1.0.15 surfaced two constraints now recorded in
the spec: cid: URIs are dropped by the default src scheme allowlist and
must be preserved explicitly, and xss's dynamic CJS exports break ESM
named imports so it must be consumed via its default export.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The mXSS test asserted a substring was absent when xss correctly escapes
it inside an allowlisted title attribute, and the blocking filter instance
was declared a task before its first consumer, failing no-unused-vars.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
As written the pattern replaced only the scheme prefix, leaving the remote
host visible in the output.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The snapshot baseline is 498 MB because inline images are embedded as
base64 data URIs, so a snapshot diff is unreviewable. Compare the base and
new renderers structurally instead, aggregating lost tags, lost attributes
and visible-text loss across the corpus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Corrected fidelity sweep (attributing drops to a surviving vs. removed
tag, and excluding HTML comments from the visible-text metric) over the
1000-fixture corpus found five attributes genuinely dropped from tags
that survived sanitization, each changing visible rendering:

- body link/vlink (default/visited link color) - 681 fixtures
- ol start/type (list numbering) - 102/94 fixtures
- ul type (bullet style) - 91 fixtures
- hr width/size (rule length/thickness) - 160/159 fixtures
- br clear (text wrap around floats) - 43 fixtures

All other candidates in the corrected report (Outlook Safe Links
metadata, mso-list CSS fragments, inline style value tokens split by
the diff script, tabindex, rel, name, div link/vlink which browsers
only honor on body) were verified to have no visible rendering effect
and were left alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
<style> was allowlisted as an ordinary tag, so xss routed its contents
through the normal element-content pipeline: HTML comments got stripped
(destroying Outlook's canonical <style><!-- ... --></style> export) and
`>` got escaped to `&gt;` (breaking child-combinator selectors, which a
browser never decodes back since <style> text is never HTML-parsed). A
comment that swallowed the real closing </style> also produced
unbalanced output capable of suppressing the rest of the document,
including the attachment list.

Fix: extract every <style>...</style> block before process() runs into
a placeholder token (collision-checked against the input, extended
deterministically rather than via Math.random()), and splice the raw
contents back in verbatim afterward. A block whose contents contain a
literal </style or <script is dropped rather than reinserted. Only a
safe `media` attribute is carried over; nothing else is. Re-measured
across the 1000-fixture corpus (861 fixtures with a genuine, non-IE-
conditional-comment-gated <style> block): fixtures losing their entire
stylesheet dropped from 774 to 0, CSS bytes lost from ~1.6 MB to 0,
unbalanced <style>/</style> outputs from 1 to 0.

Also closes two blockRemoteImages bypasses found in the same pass:
- REMOTE_CSS_URL didn't match double-quoted CSS url("...") because
  defaultSafeAttrValue escapes " to &quot; before the pattern runs, so
  a tracking-pixel URL in that form survived untouched. Now matches
  the escaped-quote forms too, and consumes the trailing quote so the
  single-quoted case no longer leaves a stray quote in the output.
- srcset was only checked against its first comma-separated candidate
  (REMOTE_URL is ^-anchored over the whole value). Now every candidate
  is checked; the attribute is blanked if any one is remote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread src/html/sanitize.ts Fixed
Comment thread src/html/sanitize.ts Fixed
f1anderz and others added 2 commits July 30, 2026 18:44
…expression'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
@f1anderz
f1anderz merged commit 95d9059 into main Jul 30, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants