diff --git a/lib/music_library_web/components/notes.ex b/lib/music_library_web/components/notes.ex
index e97bafa1..c8fe0570 100644
--- a/lib/music_library_web/components/notes.ex
+++ b/lib/music_library_web/components/notes.ex
@@ -156,6 +156,8 @@ defmodule MusicLibraryWeb.Components.Notes do
end
end
+ # sobelow_skip ["XSS.Raw"]
+ # Markdown.to_html/1 sanitizes HTML via HtmlSanitizeEx
defp render_content(content) do
content
|> Markdown.to_html()
diff --git a/lib/music_library_web/components/search_components.ex b/lib/music_library_web/components/search_components.ex
index e1b80a53..102b8f41 100644
--- a/lib/music_library_web/components/search_components.ex
+++ b/lib/music_library_web/components/search_components.ex
@@ -346,6 +346,8 @@ defmodule MusicLibraryWeb.SearchComponents do
"""
end
+ # sobelow_skip ["XSS.Raw"]
+ # Markdown.to_html/1 sanitizes HTML via HtmlSanitizeEx
defp render_description(description) do
description
|> String.slice(0, 100)
diff --git a/lib/music_library_web/live/record_set_live/index.ex b/lib/music_library_web/live/record_set_live/index.ex
index d9ddf15e..63a42a53 100644
--- a/lib/music_library_web/live/record_set_live/index.ex
+++ b/lib/music_library_web/live/record_set_live/index.ex
@@ -466,6 +466,8 @@ defmodule MusicLibraryWeb.RecordSetLive.Index do
"""
end
+ # sobelow_skip ["XSS.Raw"]
+ # Markdown.to_html/1 sanitizes HTML via HtmlSanitizeEx
defp render_description(description) do
description
|> Markdown.to_html()
diff --git a/lib/music_library_web/live/record_set_live/show.ex b/lib/music_library_web/live/record_set_live/show.ex
index 28199316..3bf53faa 100644
--- a/lib/music_library_web/live/record_set_live/show.ex
+++ b/lib/music_library_web/live/record_set_live/show.ex
@@ -302,6 +302,8 @@ defmodule MusicLibraryWeb.RecordSetLive.Show do
defp page_title(:edit, record_set), do: gettext("Edit") <> " · " <> record_set.name
defp page_title(:add_record, record_set), do: gettext("Add Record") <> " · " <> record_set.name
+ # sobelow_skip ["XSS.Raw"]
+ # Markdown.to_html/1 sanitizes HTML via HtmlSanitizeEx
defp render_description(description) do
description
|> Markdown.to_html()
diff --git a/lib/music_library_web/markdown.ex b/lib/music_library_web/markdown.ex
index 9b206373..84a46d9e 100644
--- a/lib/music_library_web/markdown.ex
+++ b/lib/music_library_web/markdown.ex
@@ -22,6 +22,7 @@ defmodule MusicLibraryWeb.Markdown do
markdown_text
|> process_double_bracket_links()
|> Earmark.as_html!(%Earmark.Options{gfm: true})
+ |> HtmlSanitizeEx.markdown_html()
{result, %{}}
end
diff --git a/mix.exs b/mix.exs
index c8ddf715..fb9b9469 100644
--- a/mix.exs
+++ b/mix.exs
@@ -105,6 +105,7 @@ defmodule MusicLibrary.MixProject do
# Notes
{:earmark, "~> 1.4"},
+ {:html_sanitize_ex, "~> 1.4"},
# Syntax highlighting
{:lumis, "~> 0.1"},
diff --git a/mix.lock b/mix.lock
index 993a375a..8dbbf363 100644
--- a/mix.lock
+++ b/mix.lock
@@ -31,6 +31,7 @@
"glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"},
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "0435d4ca364a608cc75e2f8683d374e55abbae26", [tag: "v2.2.0", sparse: "optimized", depth: 1]},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
+ "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.4.4", "271455b4d300d5d53a5d92b5bd1c00ad14c5abf1c9ff87be069af5736496515c", [:mix], [{:mochiweb, "~> 2.15 or ~> 3.1", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm", "12e1754204e7db5df1750df0a5dba1bbdf89260800019ab081f2b046596be56b"},
"idna": {:hex, :idna, "7.1.0", "1067a13043538129602d2f2ce6899d8713125c7d19734aa557ce2e3ea55bd4f1", [:rebar3], [], "hexpm", "6ae959a025bf36df61a8cab8508d9654891b5426a84c44d82deaffd6ddf8c71f"},
"igniter": {:hex, :igniter, "0.7.3", "33dccf5f9e3def0358bf01735cfa26c6c25b307078b728e5e514f5df258fbc59", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "9c6144bb50202329cbcbe941c06aed87d7ddc212ce4ecf855f32ab0bbf175258"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
@@ -40,6 +41,7 @@
"lumis": {:hex, :lumis, "0.1.2", "be5f65768c6be567f98d0a056f7055ea93470e0c34524f80e36e3f0b6589073c", [:mix], [{:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:rustler, "~> 0.29", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "729fc865753134999cf335de9e93188f384c62119dfdcc0ab8b0c06a1164434b"},
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
"mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
+ "mochiweb": {:hex, :mochiweb, "3.3.0", "2898ad0bfeee234e4cbae623c7052abc3ff0d73d499ba6e6ffef445b13ffd07a", [:rebar3], [], "hexpm", "aa85b777fb23e9972ebc424e40b5d35106f19bc998873e026dedd876df8ee50c"},
"multipart": {:hex, :multipart, "0.6.0", "73eae40ac88b67a8dd7cf6a2972762c1152470efeb0e18d68c7651f3f70c471b", [:mix], [{:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm", "818366054b1ff7215b7c091b73859968aa16e9620ec4a8a79a3a7362960b72bf"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
diff --git a/test/music_library_web/markdown_test.exs b/test/music_library_web/markdown_test.exs
index c16a921c..dca56aee 100644
--- a/test/music_library_web/markdown_test.exs
+++ b/test/music_library_web/markdown_test.exs
@@ -1,5 +1,37 @@
defmodule MusicLibraryWeb.MarkdownTest do
use ExUnit.Case, async: true
+ alias MusicLibraryWeb.Markdown
+
doctest MusicLibraryWeb.Markdown
+
+ describe "to_html/1" do
+ test "strips script tags" do
+ result = Markdown.to_html("")
+
+ refute result =~ ""
+ end
+
+ test "strips event handlers from tags" do
+ result = Markdown.to_html("
")
+
+ refute result =~ "onerror"
+ refute result =~ "alert"
+ end
+
+ test "preserves normal markdown formatting" do
+ result = Markdown.to_html("**bold**")
+
+ assert result =~ "bold"
+ end
+
+ test "preserves links" do
+ result = Markdown.to_html("[link](http://example.com)")
+
+ assert result =~ ""
+ end
+ end
end