LibWeb: Add require-trusted-types-for Directive

This is meant to configure the behaviour of an injection sinks when a
string is passed.
This commit is contained in:
Tete17 2025-08-05 14:36:11 +02:00 committed by Luke Wilde
commit 8df173e1bd
Notes: github-actions[bot] 2025-09-01 15:21:05 +00:00
11 changed files with 218 additions and 31 deletions

View file

@ -922,6 +922,7 @@ set(SOURCES
SVG/SVGViewElement.cpp
SVG/TagNames.cpp
TrustedTypes/InjectionSink.cpp
TrustedTypes/RequireTrustedTypesForDirective.cpp
TrustedTypes/TrustedHTML.cpp
TrustedTypes/TrustedScript.cpp
TrustedTypes/TrustedScriptURL.cpp

View file

@ -77,7 +77,7 @@ public:
// 5. A pre-navigation check, which takes a request, a navigation type string ("form-submission" or "other")
// and a policy as arguments, and is executed during § 4.2.4 Should navigation request of type be blocked by
// Content Security Policy?. It returns "Allowed" unless otherwise specified.
virtual Result pre_navigation_check(GC::Ref<Fetch::Infrastructure::Request const>, NavigationType, GC::Ref<Policy const>) const { return Result::Allowed; }
virtual Result pre_navigation_check(GC::Ref<Fetch::Infrastructure::Request>, NavigationType, GC::Ref<Policy const>) const { return Result::Allowed; }
// https://w3c.github.io/webappsec-csp/#directive-navigation-response-check
// 6. A navigation response check, which takes a request, a navigation type string ("form-submission" or "other"),

View file

@ -31,6 +31,7 @@
#include <LibWeb/ContentSecurityPolicy/Directives/StyleSourceElementDirective.h>
#include <LibWeb/ContentSecurityPolicy/Directives/WebRTCDirective.h>
#include <LibWeb/ContentSecurityPolicy/Directives/WorkerSourceDirective.h>
#include <LibWeb/TrustedTypes/RequireTrustedTypesForDirective.h>
namespace Web::ContentSecurityPolicy::Directives {
@ -78,6 +79,9 @@ GC::Ref<Directive> create_directive(GC::Heap& heap, String name, Vector<String>
if (name == Names::ReportUri)
return heap.allocate<ReportUriDirective>(move(name), move(value));
if (name == Names::RequireTrustedTypesFor)
return heap.allocate<TrustedTypes::RequireTrustedTypesForDirective>(move(name), move(value));
if (name == Names::Sandbox)
return heap.allocate<SandboxDirective>(move(name), move(value));

View file

@ -18,7 +18,7 @@ FormActionDirective::FormActionDirective(String name, Vector<String> value)
{
}
Directive::Result FormActionDirective::pre_navigation_check(GC::Ref<Fetch::Infrastructure::Request const> request, NavigationType navigation_type, GC::Ref<Policy const> policy) const
Directive::Result FormActionDirective::pre_navigation_check(GC::Ref<Fetch::Infrastructure::Request> request, NavigationType navigation_type, GC::Ref<Policy const> policy) const
{
// 1. Assert: policy is unused in this algorithm.
// FIXME: File spec issue, because this is not the case. The policy is required to resolve 'self'.

View file

@ -18,7 +18,7 @@ class FormActionDirective final : public Directive {
public:
virtual ~FormActionDirective() = default;
virtual Result pre_navigation_check(GC::Ref<Fetch::Infrastructure::Request const>, NavigationType, GC::Ref<Policy const>) const override;
virtual Result pre_navigation_check(GC::Ref<Fetch::Infrastructure::Request>, NavigationType, GC::Ref<Policy const>) const override;
private:
FormActionDirective(String name, Vector<String> value);

View file

@ -10,30 +10,31 @@
namespace Web::ContentSecurityPolicy::Directives::Names {
#define ENUMERATE_DIRECTIVE_NAMES \
__ENUMERATE_DIRECTIVE_NAME(BaseUri, "base-uri") \
__ENUMERATE_DIRECTIVE_NAME(ChildSrc, "child-src") \
__ENUMERATE_DIRECTIVE_NAME(ConnectSrc, "connect-src") \
__ENUMERATE_DIRECTIVE_NAME(DefaultSrc, "default-src") \
__ENUMERATE_DIRECTIVE_NAME(FontSrc, "font-src") \
__ENUMERATE_DIRECTIVE_NAME(FormAction, "form-action") \
__ENUMERATE_DIRECTIVE_NAME(FrameAncestors, "frame-ancestors") \
__ENUMERATE_DIRECTIVE_NAME(FrameSrc, "frame-src") \
__ENUMERATE_DIRECTIVE_NAME(ImgSrc, "img-src") \
__ENUMERATE_DIRECTIVE_NAME(ManifestSrc, "manifest-src") \
__ENUMERATE_DIRECTIVE_NAME(MediaSrc, "media-src") \
__ENUMERATE_DIRECTIVE_NAME(ObjectSrc, "object-src") \
__ENUMERATE_DIRECTIVE_NAME(ReportTo, "report-to") \
__ENUMERATE_DIRECTIVE_NAME(ReportUri, "report-uri") \
__ENUMERATE_DIRECTIVE_NAME(Sandbox, "sandbox") \
__ENUMERATE_DIRECTIVE_NAME(ScriptSrc, "script-src") \
__ENUMERATE_DIRECTIVE_NAME(ScriptSrcElem, "script-src-elem") \
__ENUMERATE_DIRECTIVE_NAME(ScriptSrcAttr, "script-src-attr") \
__ENUMERATE_DIRECTIVE_NAME(StyleSrc, "style-src") \
__ENUMERATE_DIRECTIVE_NAME(StyleSrcElem, "style-src-elem") \
__ENUMERATE_DIRECTIVE_NAME(StyleSrcAttr, "style-src-attr") \
__ENUMERATE_DIRECTIVE_NAME(TrustedTypes, "trusted-types") \
__ENUMERATE_DIRECTIVE_NAME(WebRTC, "webrtc") \
#define ENUMERATE_DIRECTIVE_NAMES \
__ENUMERATE_DIRECTIVE_NAME(BaseUri, "base-uri") \
__ENUMERATE_DIRECTIVE_NAME(ChildSrc, "child-src") \
__ENUMERATE_DIRECTIVE_NAME(ConnectSrc, "connect-src") \
__ENUMERATE_DIRECTIVE_NAME(DefaultSrc, "default-src") \
__ENUMERATE_DIRECTIVE_NAME(FontSrc, "font-src") \
__ENUMERATE_DIRECTIVE_NAME(FormAction, "form-action") \
__ENUMERATE_DIRECTIVE_NAME(FrameAncestors, "frame-ancestors") \
__ENUMERATE_DIRECTIVE_NAME(FrameSrc, "frame-src") \
__ENUMERATE_DIRECTIVE_NAME(ImgSrc, "img-src") \
__ENUMERATE_DIRECTIVE_NAME(ManifestSrc, "manifest-src") \
__ENUMERATE_DIRECTIVE_NAME(MediaSrc, "media-src") \
__ENUMERATE_DIRECTIVE_NAME(ObjectSrc, "object-src") \
__ENUMERATE_DIRECTIVE_NAME(ReportTo, "report-to") \
__ENUMERATE_DIRECTIVE_NAME(ReportUri, "report-uri") \
__ENUMERATE_DIRECTIVE_NAME(RequireTrustedTypesFor, "require-trusted-types-for") \
__ENUMERATE_DIRECTIVE_NAME(Sandbox, "sandbox") \
__ENUMERATE_DIRECTIVE_NAME(ScriptSrc, "script-src") \
__ENUMERATE_DIRECTIVE_NAME(ScriptSrcElem, "script-src-elem") \
__ENUMERATE_DIRECTIVE_NAME(ScriptSrcAttr, "script-src-attr") \
__ENUMERATE_DIRECTIVE_NAME(StyleSrc, "style-src") \
__ENUMERATE_DIRECTIVE_NAME(StyleSrcElem, "style-src-elem") \
__ENUMERATE_DIRECTIVE_NAME(StyleSrcAttr, "style-src-attr") \
__ENUMERATE_DIRECTIVE_NAME(TrustedTypes, "trusted-types") \
__ENUMERATE_DIRECTIVE_NAME(WebRTC, "webrtc") \
__ENUMERATE_DIRECTIVE_NAME(WorkerSrc, "worker-src")
#define __ENUMERATE_DIRECTIVE_NAME(name, value) extern FlyString name;

View file

@ -22,6 +22,7 @@ namespace Web::TrustedTypes {
__ENUMERATE_INJECTION_SINKS(Function, "Function") \
__ENUMERATE_INJECTION_SINKS(HTMLIFrameElementsrcdoc, "HTMLIFrameElement srcdoc") \
__ENUMERATE_INJECTION_SINKS(HTMLScriptElementsrc, "HTMLScriptElement src") \
__ENUMERATE_INJECTION_SINKS(Locationhref, "Location href") \
__ENUMERATE_INJECTION_SINKS(SVGScriptElementhref, "SVGScriptElement href") \
ENUMERATE_GLOBAL_EVENT_HANDLERS(EVENT_HANDLERS_INJECTION_SINKS) \
ENUMERATE_WINDOW_EVENT_HANDLERS(EVENT_HANDLERS_INJECTION_SINKS)

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2025, Miguel Sacristán Izcue <miguel_tete17@hotmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/TrustedTypes/RequireTrustedTypesForDirective.h>
#include <LibWeb/ContentSecurityPolicy/Directives/Names.h>
#include <LibWeb/DOMURL/DOMURL.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
#include <LibWeb/TrustedTypes/TrustedScript.h>
#include <LibWeb/TrustedTypes/TrustedTypePolicy.h>
namespace Web::TrustedTypes {
GC_DEFINE_ALLOCATOR(RequireTrustedTypesForDirective);
RequireTrustedTypesForDirective::RequireTrustedTypesForDirective(String name, Vector<String> value)
: Directive(move(name), move(value))
{
}
// https://www.w3.org/TR/trusted-types/#require-trusted-types-for-pre-navigation-check
ContentSecurityPolicy::Directives::Directive::Result RequireTrustedTypesForDirective::pre_navigation_check(GC::Ref<Fetch::Infrastructure::Request> request, NavigationType, GC::Ref<ContentSecurityPolicy::Policy const>) const
{
// 1. If requests urls scheme is not "javascript", return "Allowed" and abort further steps.
if (request->url().scheme() != "javascript"sv)
return Result::Allowed;
// 2. Let urlString be the result of running the URL serializer on requests url.
auto url_string = request->url().serialize();
// 3. Let encodedScriptSource be the result of removing the leading "javascript:" from urlString.
auto const encoded_script_source = MUST(url_string.substring_from_byte_offset("javascript:"sv.length()));
// 4. Let convertedScriptSource be the result of executing Process value with a default policy algorithm, with the following arguments:
// expectedType:
// TrustedScript
// global:
// requests clientss global object:
// input:
// encodedScriptSource:
// sink:
// "Location href":
auto converted_script_source = process_value_with_a_default_policy(
TrustedTypeName::TrustedScript,
request->client()->global_object(),
Utf16String::from_utf8(encoded_script_source),
InjectionSink::Locationhref);
// If that algorithm threw an error or convertedScriptSource is not a TrustedScript object, return "Blocked" and abort further steps.
if (converted_script_source.is_error() || !converted_script_source.value().has_value())
return Result::Blocked;
auto const* converted_script_source_value = converted_script_source.value().value().get_pointer<GC::Root<TrustedScript>>();
if (!converted_script_source_value)
return Result::Blocked;
// 5. Set urlString to be the result of prepending "javascript:" to stringified convertedScriptSource.
url_string = MUST(String::formatted("javascript:{}", (*converted_script_source_value)->to_string()));
// 6. Let newURL be the result of running the URL parser on urlString. If the parser returns a failure, return "Blocked" and abort further steps.
auto const new_url = DOMURL::parse(url_string);
if (!new_url.has_value())
return Result::Blocked;
// 7. Set requests url to newURL.
request->set_url(new_url.value());
// 8. Return "Allowed".
return Result::Allowed;
}
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2025, Miguel Sacristán Izcue <miguel_tete17@hotmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/ContentSecurityPolicy/Directives/Directive.h>
namespace Web::TrustedTypes {
// https://www.w3.org/TR/trusted-types/#require-trusted-types-for-csp-directive
class RequireTrustedTypesForDirective final : public ContentSecurityPolicy::Directives::Directive {
GC_CELL(RequireTrustedTypesForDirective, ContentSecurityPolicy::Directives::Directive)
GC_DECLARE_ALLOCATOR(RequireTrustedTypesForDirective);
public:
~RequireTrustedTypesForDirective() override = default;
Result pre_navigation_check(GC::Ref<Fetch::Infrastructure::Request>, NavigationType, GC::Ref<ContentSecurityPolicy::Policy const>) const override;
private:
RequireTrustedTypesForDirective(String name, Vector<String> value);
};
}

View file

@ -8,10 +8,13 @@
#include <LibGC/Ptr.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/Value.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/WindowOrWorkerGlobalScope.h>
#include <LibWeb/TrustedTypes/TrustedHTML.h>
#include <LibWeb/TrustedTypes/TrustedScript.h>
#include <LibWeb/TrustedTypes/TrustedScriptURL.h>
#include <LibWeb/TrustedTypes/TrustedTypePolicyFactory.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/CallbackType.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
@ -182,4 +185,72 @@ WebIDL::ExceptionOr<JS::Value> TrustedTypePolicy::get_trusted_type_policy_value(
return policy_value;
}
// https://www.w3.org/TR/trusted-types/#process-value-with-a-default-policy-algorithm
WebIDL::ExceptionOr<Optional<TrustedType>> process_value_with_a_default_policy(TrustedTypeName trusted_type_name, JS::Object& global, Variant<GC::Root<TrustedHTML>, GC::Root<TrustedScript>, GC::Root<TrustedScriptURL>, Utf16String> input, InjectionSink sink)
{
auto& vm = global.vm();
auto& realm = HTML::relevant_realm(global);
// 1. Let defaultPolicy be the value of globals trusted type policy factorys default policy.
auto const& default_policy = as<HTML::WindowOrWorkerGlobalScopeMixin>(global).trusted_types()->default_policy();
// This algorithm routes a value to be assigned to an injection sink through a default policy, should one exist.
// FIXME: Open an issue upstream. It is not immediately clear what to do if the default policy does not exist.
// Ref: https://github.com/w3c/trusted-types/issues/595
if (!default_policy)
return Optional<TrustedType> {};
// 2. Let policyValue be the result of executing Get Trusted Type policy value, with the following arguments:
// policy:
// defaultPolicy
// value:
// stringified input
// trustedTypeName:
// expectedTypes type name
// arguments:
// « trustedTypeName, sink »
// throwIfMissing:
// false
// 3. If the algorithm threw an error, rethrow the error and abort the following steps.
auto arguments = GC::RootVector<JS::Value>(vm.heap());
arguments.append(JS::PrimitiveString::create(vm, to_string(trusted_type_name)));
arguments.append(JS::PrimitiveString::create(vm, to_string(sink)));
auto policy_value = TRY(default_policy->get_trusted_type_policy_value(
trusted_type_name,
input.visit(
[](auto& value) { return value->to_string(); },
[](Utf16String& value) { return value; }),
arguments,
ThrowIfCallbackMissing::No));
// 4. If policyValue is null or undefined, return policyValue.
if (policy_value.is_nullish())
return Optional<TrustedType> {};
// 5. Let dataString be the result of stringifying policyValue.
Utf16String data_string;
switch (trusted_type_name) {
case TrustedTypeName::TrustedHTML:
case TrustedTypeName::TrustedScript:
data_string = TRY(WebIDL::to_utf16_string(vm, policy_value));
break;
case TrustedTypeName::TrustedScriptURL:
data_string = TRY(WebIDL::to_utf16_usv_string(vm, policy_value));
break;
default:
VERIFY_NOT_REACHED();
}
// 6. Return a new instance of an interface with a type name trustedTypeName, with its associated data value set to dataString.
switch (trusted_type_name) {
case TrustedTypeName::TrustedHTML:
return realm.create<TrustedHTML>(realm, move(data_string));
case TrustedTypeName::TrustedScript:
return realm.create<TrustedScript>(realm, move(data_string));
case TrustedTypeName::TrustedScriptURL:
return realm.create<TrustedScriptURL>(realm, move(data_string));
}
VERIFY_NOT_REACHED();
}
}

View file

@ -9,13 +9,17 @@
#include <LibJS/Forward.h>
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Bindings/TrustedTypePolicyPrototype.h>
#include <LibWeb/TrustedTypes/InjectionSink.h>
namespace Web::TrustedTypes {
using TrustedTypesVariants = WebIDL::ExceptionOr<Variant<
// https://www.w3.org/TR/trusted-types/#typedefdef-trustedtype
using TrustedType = Variant<
GC::Root<TrustedHTML>,
GC::Root<TrustedScript>,
GC::Root<TrustedScriptURL>>>;
GC::Root<TrustedScriptURL>>;
using TrustedTypesVariants = WebIDL::ExceptionOr<TrustedType>;
enum class TrustedTypeName {
TrustedHTML,
@ -49,16 +53,18 @@ public:
WebIDL::ExceptionOr<GC::Root<TrustedScript>> create_script(Utf16String const&, GC::RootVector<JS::Value> const&);
WebIDL::ExceptionOr<GC::Root<TrustedScriptURL>> create_script_url(Utf16String const&, GC::RootVector<JS::Value> const&);
WebIDL::ExceptionOr<JS::Value> get_trusted_type_policy_value(TrustedTypeName, Utf16String const& value, GC::RootVector<JS::Value> const& values, ThrowIfCallbackMissing throw_if_missing);
private:
explicit TrustedTypePolicy(JS::Realm&, Utf16String const&, TrustedTypePolicyOptions const&);
virtual void initialize(JS::Realm&) override;
TrustedTypesVariants create_a_trusted_type(TrustedTypeName, Utf16String const&, GC::RootVector<JS::Value> const& values);
WebIDL::ExceptionOr<JS::Value> get_trusted_type_policy_value(TrustedTypeName, Utf16String const& value, GC::RootVector<JS::Value> const& values, ThrowIfCallbackMissing throw_if_missing);
Utf16String const m_name;
TrustedTypePolicyOptions const m_options;
};
WebIDL::ExceptionOr<Optional<TrustedType>> process_value_with_a_default_policy(TrustedTypeName, JS::Object&, Variant<GC::Root<TrustedHTML>, GC::Root<TrustedScript>, GC::Root<TrustedScriptURL>, Utf16String>, InjectionSink);
}