8 Commits

Author SHA1 Message Date
Sam Morrow 0bb1e569ce test(sanitize): drop the optimization scaffolding
The benchmarks and the reference-implementation equivalence harness
existed to justify the sanitizer rewrite. They have served that purpose,
so remove them along with the verbatim copy of the old pipeline they
carried.

Five checks move into sanitize_test.go rather than going away, because
none of them reference the old implementation and all of them guard
behaviour the rewrite introduced:

- isHTMLInert must be a fixed point of the live bluemonday policy,
  checked byte by byte and as whole strings, with the accepted byte set
  pinned explicitly. Nothing else fails if that set is widened, and
  widening it changes sanitizer output.
- Both filters are fixed points on their own output, which is what
  licenses Sanitize to skip its second pass.
- Clean ASCII sanitizes with zero allocations.
- Invalid UTF-8 is re-encoded to U+FFFD.
- Known payloads still lose content.

Net -560 lines.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 16:42:46 +02:00
Sam Morrow 02cf0f7d75 perf(sanitize): make clean text allocation-free on the hot path
Sanitizing user-authored response fields ran multiple allocating passes
over every string regardless of content: FilterInvisibleCharacters
converted the whole input to []rune and back, FilterCodeFenceMetadata
split and rejoined every line, and bluemonday ran unconditionally. On
comment- and issue-heavy responses this dominated conversion CPU and
allocation.

Three changes, none of which alter output or widen what the policy
allows:

- FilterInvisibleCharacters scans first and copies only from the first
  filtered rune, skipping ASCII runs without decoding them. Invalid
  UTF-8 is still re-encoded to U+FFFD, matching the []rune round trip it
  replaces.
- FilterCodeFenceMetadata walks lines in place and returns the input
  when no line changes.
- FilterHTMLTags skips bluemonday for input that is provably a fixed
  point of the policy: printable ASCII, TAB and LF, with none of the
  five characters html.EscapeString rewrites. Sanitize also skips the
  second invisible/code-fence pass when HTML normalization returned its
  input unchanged, since both filters are fixed points there.

Equivalence is pinned by a verbatim copy of the previous pipeline: the
new code is diffed against it over a corpus of ~22k deterministic cases
plus two fuzz targets, and the fast path is checked byte by byte against
the live bluemonday policy.

Benchmarks (Intel Ultra 9 185H, n=6):

  Sanitize/TitleASCII      5.35µs ->  114ns   1 -100% allocs
  Sanitize/Comment1KiB     45.3µs -> 1.27µs   1 -100% allocs
  Sanitize/Body64KiB       2.47ms -> 85.9µs   1 -100% allocs
  30 issues x 2KiB body    3.00ms -> 88.6µs   1.55MiB -> 1.9KiB
  100 comments x 1KiB      5.04ms ->  169µs   2.19MiB -> 6.3KiB

Content that genuinely needs rewriting still pays for it, and non-ASCII
text still goes through bluemonday by design.

Fixes #3117

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 16:33:23 +02:00
Sam Morrow 53fc915a10 Re-run fence filter and preserve valid variation sequences
Address review feedback on the post-HTML-entity sanitization pass.

Entity decoding could still smuggle code-fence metadata past the
sanitizer. A first line such as "`&#8203;``steal secrets" is not a fence
in the raw input, so FilterCodeFenceMetadata left it alone; decoding the
entity and stripping the zero width space then produced a real fence with
its info string intact. Sanitize now re-runs the fence filter after the
input is fully normalized.

Filtering every variation selector also corrupted legitimate text: VS15
and VS16 select text or emoji presentation, so "✈️" was reduced to "✈",
and the Variation Selectors Supplement encodes registered CJK ideographic
variation sequences. Selectors are now filtered contextually. A selector
is kept when it can apply to the character it follows, and dropped when it
is orphaned, follows a removed or non-graphic character, or continues a
run of selectors. Supplement selectors additionally require a CJK
ideograph base, matching the Ideographic Variation Database. That keeps
the anti-smuggling property, since hidden payloads rely on selector runs,
without rewriting valid Unicode.

Also corrects a lowercase-hex test case that claimed uppercase digits.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 15:22:09 +02:00
Sam Morrow ea2d979198 Filter invisible Unicode after HTML entity normalization
FilterInvisibleCharacters previously ran only before FilterHTMLTags,
so numeric HTML entities (e.g. &#8203; or &#x200b;) that bluemonday
decodes into invisible or bidirectional control characters could
survive sanitization untouched. Sanitize now applies the
invisible-character filter both before HTML processing (so raw
invisible characters don't interfere with code-fence parsing) and
again after, so entity-decoded characters cannot escape the policy.

Also expands the removal set to include:
- ARABIC LETTER MARK (U+061C), a directional format character in the
  same family as the already-covered LRM/RLM marks.
- Variation selectors (U+FE00-U+FE0F) and the variation selectors
  supplement (U+E0100-U+E01EF), which can be used to hide payloads
  after emoji or other base characters.

Fixes #3101
2026-08-19 15:22:09 +02:00
JoannaaKL bc4555f0f5 Allow http (#1369)
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Build and Test Go Project / build (macos-latest) (push) Has been cancelled
Build and Test Go Project / build (ubuntu-latest) (push) Has been cancelled
Build and Test Go Project / build (windows-latest) (push) Has been cancelled
License Check / license-check (push) Has been cancelled
2025-11-06 14:48:11 +01:00
JoannaaKL 5cef28f28e Filter code fences (#1367)
* Filter code fences

* Add test and actually use new function

* Update pkg/sanitize/sanitize_test.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Reorder

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-06 13:25:16 +01:00
JoannaaKL 6a39a39e6d Add html filtering (#1356)
* Add html content sanitizer

* Add basic html sanitization

* .

* Correct licenses and scripts to ignore vendor directory

* Use singleton

* Simplify
2025-11-05 12:49:49 +01:00
JoannaaKL 304f29a2fd Add basic content sanitizer (#1344) 2025-10-31 12:51:22 +01:00