fix(html): check link schemes the way browsers read them

FastSanitize checks the scheme with URI.parse/1, which finds none in
java<TAB>script:alert(1). Browsers remove tabs and newlines and leading
control characters first and run it as a javascript: link. The scrubbers
now use Pleroma.HTML.URIAttributes, which normalizes the URL the same
way before the scheme check.
This commit is contained in:
Lain Soykaf 2026-10-22 16:30:38 +04:00
commit 1be50ed28b
No known key found for this signature in database
6 changed files with 95 additions and 5 deletions

View file

@ -0,0 +1,40 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTML.URIAttributes do
@moduledoc """
URI attribute rules for the scrubbers.
Works like `FastSanitize.Sanitizer.Meta.allow_tag_with_uri_attributes/3`, but
checks the scheme the way a browser reads the URL. Browsers remove tabs and
newlines anywhere and control characters and spaces at the start, so
`java\\tscript:` is a `javascript:` link to them, while `URI.parse/1` finds no
scheme and FastSanitize lets it through.
"""
defmacro allow_tag_with_uri_attributes(tag, attrs, valid_schemes) do
for attr <- attrs do
quote do
def scrub_attribute(unquote(tag), {unquote(attr), "&" <> _}), do: nil
def scrub_attribute(unquote(tag), {unquote(attr), uri} = attribute) do
if Pleroma.HTML.URIAttributes.valid_scheme?(uri, unquote(valid_schemes)),
do: attribute
end
end
end
end
@spec valid_scheme?(String.t(), [String.t()]) :: boolean()
def valid_scheme?(uri, valid_schemes) do
scheme =
uri
|> String.replace(~r/[\t\n\r]/, "")
|> String.replace(~r/\A[\x00-\x20]+/, "")
|> URI.parse()
|> Map.get(:scheme)
is_nil(scheme) or scheme in valid_schemes
end
end