LibWeb: Enforce Integrity Policy on Fetch requests

This commit is contained in:
Kenneth Myhra 2025-08-07 22:41:54 +02:00 committed by Luke Wilde
commit 1228063a85
Notes: github-actions[bot] 2025-08-14 12:38:53 +00:00
5 changed files with 88 additions and 4 deletions

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2025, Luke Wilde <luke@ladybird.org> * Copyright (c) 2025, Luke Wilde <luke@ladybird.org>
* Copyright (c) 2025, Kenneth Myhra <kennethmyhra@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -14,8 +15,11 @@
#include <LibWeb/DOM/Element.h> #include <LibWeb/DOM/Element.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h> #include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h> #include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
#include <LibWeb/Fetch/Infrastructure/URL.h>
#include <LibWeb/HTML/Window.h> #include <LibWeb/HTML/Window.h>
#include <LibWeb/HTML/WorkerGlobalScope.h>
#include <LibWeb/Infra/Strings.h> #include <LibWeb/Infra/Strings.h>
#include <LibWeb/SRI/SRI.h>
#include <LibWeb/WebAssembly/WebAssembly.h> #include <LibWeb/WebAssembly/WebAssembly.h>
namespace Web::ContentSecurityPolicy { namespace Web::ContentSecurityPolicy {
@ -131,6 +135,66 @@ Directives::Directive::Result should_request_be_blocked_by_content_security_poli
return result; return result;
} }
// https://w3c.github.io/webappsec-subresource-integrity/#should-request-be-blocked-by-integrity-policy
Directives::Directive::Result should_request_be_blocked_by_integrity_policy(GC::Ref<Fetch::Infrastructure::Request> request)
{
VERIFY(request->policy_container().has<GC::Ref<HTML::PolicyContainer>>());
// 1. Let policyContainer be requests policy container.
auto const& policy_container = request->policy_container().get<GC::Ref<HTML::PolicyContainer>>();
// 2. Let parsedMetadata be the result of calling parse metadata with requests integrity metadata.
auto parsed_metadata = MUST(SRI::parse_metadata(request->integrity_metadata()));
// 3. If parsedMetadata is not the empty set and requests mode is either "cors" or "same-origin", return "Allowed".
if (!parsed_metadata.is_empty() && (request->mode() == Fetch::Infrastructure::Request::Mode::CORS || request->mode() == Fetch::Infrastructure::Request::Mode::SameOrigin))
return Directives::Directive::Result::Allowed;
// 4. If requests url is local, return "Allowed".
if (Fetch::Infrastructure::is_local_url(request->url()))
return Directives::Directive::Result::Allowed;
// 5. Let policy be policyContainers integrity policy.
auto const& policy = policy_container->integrity_policy;
// 6. Let reportPolicy be policyContainers report only integrity policy.
auto const& report_policy = policy_container->report_only_integrity_policy;
// 7. If both policy and reportPolicy are empty integrity policys, return "Allowed".
if (policy.is_empty() && report_policy.is_empty())
return Directives::Directive::Result::Allowed;
// 8. Let global be requests clients global object.
auto& global = request->client()->global_object();
// 9. If global is not a Window nor a WorkerGlobalScope, return "Allowed".
if (!is<HTML::Window>(global) && !is<HTML::WorkerGlobalScope>(global))
return Directives::Directive::Result::Allowed;
// 10. Let block be a boolean, initially false.
bool block = false;
// FIXME: 11. Let reportBlock be a boolean, initially false.
[[maybe_unused]] auto report_block = false;
// 12. If policys sources contains "inline" and policys blocked destinations contains requests destination, set block to true.
if (policy.sources.contains_slow("inline"sv)
&& request->destination().has_value()
&& policy.blocked_destinations.contains_slow(request->destination().value()))
block = true;
// 13. If reportPolicys sources contains "inline" and reportPolicys blocked destinations contains requests destination, set reportBlock to true.
if (report_policy.sources.contains_slow("inline"sv)
&& request->destination().has_value()
&& report_policy.blocked_destinations.contains_slow(request->destination().value()))
report_block = true;
// FIXME: 14. If block is true or reportBlock is true, then report violation with request, block, reportBlock, policy and reportPolicy.
// 15. If block is true, then return "Blocked"; otherwise "Allowed".
return block ? Directives::Directive::Result::Blocked : Directives::Directive::Result::Allowed;
}
// https://w3c.github.io/webappsec-csp/#should-block-response // https://w3c.github.io/webappsec-csp/#should-block-response
Directives::Directive::Result should_response_to_request_be_blocked_by_content_security_policy(JS::Realm& realm, GC::Ref<Fetch::Infrastructure::Response> response, GC::Ref<Fetch::Infrastructure::Request> request) Directives::Directive::Result should_response_to_request_be_blocked_by_content_security_policy(JS::Realm& realm, GC::Ref<Fetch::Infrastructure::Response> response, GC::Ref<Fetch::Infrastructure::Request> request)
{ {

View file

@ -14,6 +14,7 @@ namespace Web::ContentSecurityPolicy {
void report_content_security_policy_violations_for_request(JS::Realm&, GC::Ref<Fetch::Infrastructure::Request>); void report_content_security_policy_violations_for_request(JS::Realm&, GC::Ref<Fetch::Infrastructure::Request>);
Directives::Directive::Result should_request_be_blocked_by_content_security_policy(JS::Realm&, GC::Ref<Fetch::Infrastructure::Request>); Directives::Directive::Result should_request_be_blocked_by_content_security_policy(JS::Realm&, GC::Ref<Fetch::Infrastructure::Request>);
Directives::Directive::Result should_request_be_blocked_by_integrity_policy(GC::Ref<Fetch::Infrastructure::Request>);
Directives::Directive::Result should_response_to_request_be_blocked_by_content_security_policy(JS::Realm&, GC::Ref<Fetch::Infrastructure::Response>, GC::Ref<Fetch::Infrastructure::Request>); Directives::Directive::Result should_response_to_request_be_blocked_by_content_security_policy(JS::Realm&, GC::Ref<Fetch::Infrastructure::Response>, GC::Ref<Fetch::Infrastructure::Request>);
Directives::Directive::Result should_navigation_request_of_type_be_blocked_by_content_security_policy(GC::Ref<Fetch::Infrastructure::Request> navigation_request, Directives::Directive::NavigationType navigation_type); Directives::Directive::Result should_navigation_request_of_type_be_blocked_by_content_security_policy(GC::Ref<Fetch::Infrastructure::Request> navigation_request, Directives::Directive::NavigationType navigation_type);

View file

@ -319,11 +319,13 @@ WebIDL::ExceptionOr<GC::Ptr<PendingResponse>> main_fetch(JS::Realm& realm, Infra
// 6. Upgrade a mixed content request to a potentially trustworthy URL, if appropriate. // 6. Upgrade a mixed content request to a potentially trustworthy URL, if appropriate.
MixedContent::upgrade_a_mixed_content_request_to_a_potentially_trustworthy_url_if_appropriate(request); MixedContent::upgrade_a_mixed_content_request_to_a_potentially_trustworthy_url_if_appropriate(request);
// 7. If should request be blocked due to a bad port, should fetching request be blocked as mixed content, or // 7. If should request be blocked due to a bad port, should fetching request be blocked as mixed content, should
// should request be blocked by Content Security Policy returns blocked, then set response to a network error. // request be blocked by Content Security Policy, or should request be blocked by Integrity Policy Policy
// returns blocked, then set response to a network error.
if (Infrastructure::block_bad_port(request) == Infrastructure::RequestOrResponseBlocking::Blocked if (Infrastructure::block_bad_port(request) == Infrastructure::RequestOrResponseBlocking::Blocked
|| MixedContent::should_fetching_request_be_blocked_as_mixed_content(request) == Infrastructure::RequestOrResponseBlocking::Blocked || MixedContent::should_fetching_request_be_blocked_as_mixed_content(request) == Infrastructure::RequestOrResponseBlocking::Blocked
|| ContentSecurityPolicy::should_request_be_blocked_by_content_security_policy(realm, request) == ContentSecurityPolicy::Directives::Directive::Result::Blocked) { || ContentSecurityPolicy::should_request_be_blocked_by_content_security_policy(realm, request) == ContentSecurityPolicy::Directives::Directive::Result::Blocked
|| ContentSecurityPolicy::should_request_be_blocked_by_integrity_policy(request) == ContentSecurityPolicy::Directives::Directive::Result::Blocked) {
response = Infrastructure::Response::network_error(vm, "Request was blocked"_string); response = Infrastructure::Response::network_error(vm, "Request was blocked"_string);
} }

View file

@ -22,7 +22,6 @@
#include <LibWeb/Fetch/Infrastructure/HTTP.h> #include <LibWeb/Fetch/Infrastructure/HTTP.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h> #include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Headers.h> #include <LibWeb/Fetch/Infrastructure/HTTP/Headers.h>
#include <LibWeb/HTML/PolicyContainers.h>
namespace Web::Fetch::Infrastructure { namespace Web::Fetch::Infrastructure {

View file

@ -10,12 +10,22 @@
#include <LibGC/CellAllocator.h> #include <LibGC/CellAllocator.h>
#include <LibJS/Heap/Cell.h> #include <LibJS/Heap/Cell.h>
#include <LibURL/Forward.h> #include <LibURL/Forward.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
#include <LibWeb/Forward.h> #include <LibWeb/Forward.h>
#include <LibWeb/HTML/EmbedderPolicy.h> #include <LibWeb/HTML/EmbedderPolicy.h>
#include <LibWeb/ReferrerPolicy/ReferrerPolicy.h> #include <LibWeb/ReferrerPolicy/ReferrerPolicy.h>
namespace Web::HTML { namespace Web::HTML {
// https://w3c.github.io/webappsec-subresource-integrity/#integrity-policy
struct IntegrityPolicy {
Vector<String> sources;
Vector<Fetch::Infrastructure::Request::Destination> blocked_destinations;
Vector<String> endpoints;
bool is_empty() const { return sources.is_empty() && blocked_destinations.is_empty() && endpoints.is_empty(); }
};
// https://html.spec.whatwg.org/multipage/origin.html#policy-container // https://html.spec.whatwg.org/multipage/origin.html#policy-container
// A policy container is a struct containing policies that apply to a Document, a WorkerGlobalScope, or a WorkletGlobalScope. It has the following items: // A policy container is a struct containing policies that apply to a Document, a WorkerGlobalScope, or a WorkletGlobalScope. It has the following items:
struct PolicyContainer : public GC::Cell { struct PolicyContainer : public GC::Cell {
@ -37,6 +47,14 @@ public:
// A referrer policy, which is a referrer policy. It is initially the default referrer policy. // A referrer policy, which is a referrer policy. It is initially the default referrer policy.
ReferrerPolicy::ReferrerPolicy referrer_policy { ReferrerPolicy::DEFAULT_REFERRER_POLICY }; ReferrerPolicy::ReferrerPolicy referrer_policy { ReferrerPolicy::DEFAULT_REFERRER_POLICY };
// https://html.spec.whatwg.org/multipage/browsers.html#policy-container-integrity-policy
// An integrity policy, which is an integrity policy, initially a new integrity policy.
IntegrityPolicy integrity_policy {};
// https://html.spec.whatwg.org/multipage/browsers.html#policy-container-report-only-integrity-policy
// A report only integrity policy, which is an integrity policy, initially a new integrity policy.
IntegrityPolicy report_only_integrity_policy {};
[[nodiscard]] GC::Ref<PolicyContainer> clone(GC::Heap&) const; [[nodiscard]] GC::Ref<PolicyContainer> clone(GC::Heap&) const;
[[nodiscard]] SerializedPolicyContainer serialize() const; [[nodiscard]] SerializedPolicyContainer serialize() const;