Skip to content

feat: optional Exoplanet.Sanitizer adapter (#24) - #26

Merged
milmazz merged 11 commits into
mainfrom
feat/sanitizer-adapter
Jun 13, 2026
Merged

feat: optional Exoplanet.Sanitizer adapter (#24)#26
milmazz merged 11 commits into
mainfrom
feat/sanitizer-adapter

Conversation

@milmazz

@milmazz milmazz commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Summary

Addresses the sanitizer-hardening item in #24: let consumers delegate HTML sanitization to a comprehensive library (e.g. html_sanitize_ex) for security-sensitive rendering — without making it a dependency.

  • New public behaviour Exoplanet.Sanitizer — one callback sanitize(html) :: html. Activated globally via app env (mirrors Exoplanet.Cache):
    config :exoplanet, sanitizer_adapter: MyApp.FeedSanitizer
  • Replace semantics — when an adapter is set and sanitize_html: true, it replaces the built-in sanitize walk (single source of truth). strip_images/excerpt_length still run after; sanitize_html: false disables sanitization entirely (adapter not called); nil/empty fields aren't passed to the adapter.
  • No filter-map schema change — the adapter is app-env only, so @type t, @defaults, merge/2, and Config.from_file/1 are untouched.
  • No new dependencyhtml_sanitize_ex is the documented example adapter, not added to mix.exs.
  • Security fix found in reviewstrip_images rewrites <img> to <a href=src>. image_src/2 previously skipped the scheme allowlist when the pass ran with sanitize?: false (the strip-only and post-adapter paths), so an <img src="javascript:..."> could become a clickable javascript: link. The generated <a href> is Exoplanet's own construct, so safe_url?/1 is now enforced unconditionally. Regression tests added for both paths.

Docs: Exoplanet.Sanitizer + Exoplanet.Filters moduledocs, README "stronger sanitization" example, CHANGELOG, CLAUDE.md.

Test Plan

  • mix precommit (compile, format, deps.unlock, docs, test) — 137 tests, 0 failures
  • New test/exoplanet/filters_sanitizer_test.exs: replace, skip-when-false, per-field/empty-skip, strip-after-adapter ordering, no-adapter-unchanged, and the javascript:-href regression
  • No-adapter path unchanged (test/exoplanet/filters_test.exs green)
  • No html_sanitize_ex dependency added (mix.exs/mix.lock unchanged)

Refs #24

🤖 Generated with Claude Code

milmazz and others added 11 commits June 13, 2026 15:35
Behaviour + app-env activation that replaces the built-in sanitizer when
set; html_sanitize_ex documented as the example adapter (no new dep).

Refs #24

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refs #24

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Optional adapter for delegating HTML sanitization to a comprehensive
library (e.g. html_sanitize_ex). Activated via the :sanitizer_adapter
application env key.

Refs #24

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When sanitize_html is true and an Exoplanet.Sanitizer adapter is set,
it replaces the built-in tree-walk. strip_images/excerpt still apply.

Refs #24

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review follow-up: extract strip_only_opts/0 used by both the
strip-images-only branch and strip_images_only/2; correct the comment
that overclaimed a single fused walk now that the adapter path exists.

Refs #24

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refs #24

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… test idiom)

Refs #24

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refs #24

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refs #24

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refs #24

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
strip_images rewrites <img> to <a href=src>. image_src/2 skipped the
scheme allowlist when the pass ran with sanitize?: false (strip-only and
post-adapter paths), so an <img src="javascript:..."> could become a
clickable javascript: link. The generated <a href> is Exoplanet's own
construct, so enforce safe_url?/1 unconditionally.

Refs #24

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@milmazz
milmazz merged commit dff2314 into main Jun 13, 2026
5 checks passed
@milmazz
milmazz deleted the feat/sanitizer-adapter branch June 13, 2026 21:30
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.

1 participant