mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 11:49:44 +00:00
LibWeb/CSP: Implement the script-src directive
This commit is contained in:
parent
3d43462ccd
commit
0cff47828d
Notes:
github-actions[bot]
2025-07-09 21:53:59 +00:00
Author: https://github.com/Lubrsi
Commit: 0cff47828d
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5328
Reviewed-by: https://github.com/ADKaster ✅
17 changed files with 839 additions and 14 deletions
|
@ -6,11 +6,16 @@
|
|||
|
||||
#include <LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h>
|
||||
#include <LibWeb/ContentSecurityPolicy/Directives/DirectiveOperations.h>
|
||||
#include <LibWeb/ContentSecurityPolicy/Directives/KeywordSources.h>
|
||||
#include <LibWeb/ContentSecurityPolicy/Directives/Names.h>
|
||||
#include <LibWeb/ContentSecurityPolicy/PolicyList.h>
|
||||
#include <LibWeb/ContentSecurityPolicy/Violation.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Element.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
|
||||
#include <LibWeb/Infra/Strings.h>
|
||||
#include <LibWeb/WebAssembly/WebAssembly.h>
|
||||
|
||||
namespace Web::ContentSecurityPolicy {
|
||||
|
||||
|
@ -322,4 +327,233 @@ Directives::Directive::Result should_navigation_response_to_navigation_request_o
|
|||
return result;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-csp/#should-block-inline
|
||||
Directives::Directive::Result should_elements_inline_type_behavior_be_blocked_by_content_security_policy(JS::Realm& realm, GC::Ref<DOM::Element> element, Directives::Directive::InlineType type, String const& source)
|
||||
{
|
||||
// Spec Note: The valid values for type are "script", "script attribute", "style", and "style attribute".
|
||||
VERIFY(type == Directives::Directive::InlineType::Script || type == Directives::Directive::InlineType::ScriptAttribute || type == Directives::Directive::InlineType::Style || type == Directives::Directive::InlineType::StyleAttribute);
|
||||
|
||||
// 1. Assert: element is not null.
|
||||
// NOTE: Already done by only accepting a GC::Ref.
|
||||
|
||||
// 2. Let result be "Allowed".
|
||||
auto result = Directives::Directive::Result::Allowed;
|
||||
|
||||
// 3. For each policy of element’s Document's global object’s CSP list:
|
||||
auto& global_object = element->document().realm().global_object();
|
||||
auto csp_list = PolicyList::from_object(global_object);
|
||||
VERIFY(csp_list);
|
||||
|
||||
for (auto const policy : csp_list->policies()) {
|
||||
// 1. For each directive of policy’s directive set:
|
||||
for (auto const directive : policy->directives()) {
|
||||
// 1. If directive’s inline check returns "Allowed" when executed upon element, type, policy and source,
|
||||
// skip to the next directive.
|
||||
if (directive->inline_check(realm.heap(), element, type, policy, source) == Directives::Directive::Result::Allowed)
|
||||
continue;
|
||||
|
||||
// 2. Let directive-name be the result of executing § 6.8.2 Get the effective directive for inline checks
|
||||
// on type.
|
||||
auto directive_name = Directives::get_the_effective_directive_for_inline_checks(type);
|
||||
|
||||
// 3. Otherwise, let violation be the result of executing § 2.4.1 Create a violation object for global,
|
||||
// policy, and directive on the current settings object’s global object, policy, and directive-name.
|
||||
// FIXME: File spec issue about using "current settings object" here, as it can run outside of a script
|
||||
// context (for example, a just parsed inline script being prepared)
|
||||
auto violation = Violation::create_a_violation_object_for_global_policy_and_directive(realm, global_object, policy, directive_name.to_string());
|
||||
|
||||
// 4. Set violation’s resource to "inline".
|
||||
violation->set_resource(Violation::Resource::Inline);
|
||||
|
||||
// 5. Set violation’s element to element.
|
||||
violation->set_element(element);
|
||||
|
||||
// 6. If directive’s value contains the expression "'report-sample'", then set violation’s sample to the
|
||||
// substring of source containing its first 40 characters.
|
||||
// FIXME: Should this be case insensitive?
|
||||
auto maybe_report_sample = directive->value().find_if([](auto const& directive_value) {
|
||||
return directive_value.equals_ignoring_ascii_case(Directives::KeywordSources::ReportSample);
|
||||
});
|
||||
|
||||
if (!maybe_report_sample.is_end()) {
|
||||
Utf8View source_view { source };
|
||||
auto sample = source_view.unicode_substring_view(0, min(source_view.length(), 40));
|
||||
violation->set_sample(String::from_utf8_without_validation(sample.as_string().bytes()));
|
||||
}
|
||||
|
||||
// 7. Execute § 5.5 Report a violation on violation.
|
||||
violation->report_a_violation(realm);
|
||||
|
||||
// 8. If policy’s disposition is "enforce", then set result to "Blocked".
|
||||
if (policy->disposition() == Policy::Disposition::Enforce) {
|
||||
result = Directives::Directive::Result::Blocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-csp/#can-compile-strings
|
||||
JS::ThrowCompletionOr<void> ensure_csp_does_not_block_string_compilation(JS::Realm& realm, ReadonlySpan<String>, StringView, StringView code_string, JS::CompilationType, ReadonlySpan<JS::Value>, JS::Value)
|
||||
{
|
||||
// FIXME: 1. If compilationType is "TIMER", then:
|
||||
// 1. Let sourceString be codeString.
|
||||
StringView source_string = code_string;
|
||||
// FIXME: 2. Else:
|
||||
// FIXME: We don't do these two steps as we don't currently support Trusted Types.
|
||||
|
||||
// 3. Let result be "Allowed".
|
||||
auto result = Directives::Directive::Result::Allowed;
|
||||
|
||||
// 4. Let global be realm’s global object.
|
||||
auto& global = realm.global_object();
|
||||
|
||||
// 5. For each policy of global’s CSP list:
|
||||
auto csp_list = PolicyList::from_object(global);
|
||||
VERIFY(csp_list);
|
||||
for (auto const policy : csp_list->policies()) {
|
||||
// 1. Let source-list be null.
|
||||
Optional<Vector<String>> maybe_source_list;
|
||||
|
||||
// 2. If policy contains a directive whose name is "script-src", then set source-list to that directive's value.
|
||||
auto maybe_script_src = policy->directives().find_if([](auto const& directive) {
|
||||
return directive->name() == Directives::Names::ScriptSrc;
|
||||
});
|
||||
|
||||
if (!maybe_script_src.is_end()) {
|
||||
maybe_source_list = (*maybe_script_src)->value();
|
||||
} else {
|
||||
// Otherwise if policy contains a directive whose name is "default-src", then set source-list to that
|
||||
// directive’s value.
|
||||
auto maybe_default_src = policy->directives().find_if([](auto const& directive) {
|
||||
return directive->name() == Directives::Names::DefaultSrc;
|
||||
});
|
||||
|
||||
if (!maybe_default_src.is_end())
|
||||
maybe_source_list = (*maybe_default_src)->value();
|
||||
}
|
||||
|
||||
// 3. If source-list is not null, and does not contain a source expression which is an ASCII case-insensitive
|
||||
// match for the string "'unsafe-eval'", then:
|
||||
if (maybe_source_list.has_value()) {
|
||||
auto const& source_list = maybe_source_list.value();
|
||||
|
||||
auto maybe_unsafe_eval = source_list.find_if([](auto const& directive_value) {
|
||||
return directive_value.equals_ignoring_ascii_case(Directives::KeywordSources::UnsafeEval);
|
||||
});
|
||||
|
||||
if (maybe_unsafe_eval.is_end()) {
|
||||
// 1. Let violation be the result of executing § 2.4.1 Create a violation object for global, policy,
|
||||
// and directive on global, policy, and "script-src".
|
||||
auto script_src_string = Directives::Names::ScriptSrc.to_string();
|
||||
auto violation = Violation::create_a_violation_object_for_global_policy_and_directive(realm, global, policy, script_src_string);
|
||||
|
||||
// 2. Set violation’s resource to "eval".
|
||||
violation->set_resource(Violation::Resource::Eval);
|
||||
|
||||
// 3. If source-list contains the expression "'report-sample'", then set violation’s sample to the
|
||||
// substring of sourceString containing its first 40 characters.
|
||||
// FIXME: Should this be case insensitive?
|
||||
auto maybe_report_sample = source_list.find_if([](auto const& directive_value) {
|
||||
return directive_value.equals_ignoring_ascii_case(Directives::KeywordSources::ReportSample);
|
||||
});
|
||||
|
||||
if (!maybe_report_sample.is_end()) {
|
||||
Utf8View source_view { source_string };
|
||||
auto sample = source_view.unicode_substring_view(0, min(source_view.length(), 40));
|
||||
violation->set_sample(String::from_utf8_without_validation(sample.as_string().bytes()));
|
||||
}
|
||||
|
||||
// 4. Execute § 5.5 Report a violation on violation.
|
||||
violation->report_a_violation(realm);
|
||||
|
||||
// 5. If policy’s disposition is "enforce", then set result to "Blocked".
|
||||
if (policy->disposition() == Policy::Disposition::Enforce)
|
||||
result = Directives::Directive::Result::Blocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. If result is "Blocked", throw an EvalError exception.
|
||||
if (result == Directives::Directive::Result::Blocked) {
|
||||
return realm.vm().throw_completion<JS::EvalError>("Blocked by Content Security Policy"sv);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-csp/#can-compile-wasm-bytes
|
||||
JS::ThrowCompletionOr<void> ensure_csp_does_not_block_wasm_byte_compilation(JS::Realm& realm)
|
||||
{
|
||||
// 1. Let global be realm’s global object.
|
||||
auto& global = realm.global_object();
|
||||
|
||||
// 2. Let result be "Allowed".
|
||||
auto result = Directives::Directive::Result::Allowed;
|
||||
|
||||
// 3. For each policy of global’s CSP list:
|
||||
auto csp_list = PolicyList::from_object(global);
|
||||
VERIFY(csp_list);
|
||||
for (auto const policy : csp_list->policies()) {
|
||||
// 1. Let source-list be null.
|
||||
Optional<Vector<String>> maybe_source_list;
|
||||
|
||||
// 2. If policy contains a directive whose name is "script-src", then set source-list to that directive's value.
|
||||
auto maybe_script_src = policy->directives().find_if([](auto const& directive) {
|
||||
return directive->name() == Directives::Names::ScriptSrc;
|
||||
});
|
||||
|
||||
if (!maybe_script_src.is_end()) {
|
||||
maybe_source_list = (*maybe_script_src)->value();
|
||||
} else {
|
||||
// Otherwise if policy contains a directive whose name is "default-src", then set source-list to that
|
||||
// directive’s value.
|
||||
auto maybe_default_src = policy->directives().find_if([](auto const& directive) {
|
||||
return directive->name() == Directives::Names::DefaultSrc;
|
||||
});
|
||||
|
||||
if (!maybe_default_src.is_end())
|
||||
maybe_source_list = (*maybe_default_src)->value();
|
||||
}
|
||||
|
||||
// 3. If source-list is non-null, and does not contain a source expression which is an ASCII case-insensitive
|
||||
// match for the string "'unsafe-eval'", and does not contain a source expression which is an ASCII
|
||||
// case-insensitive match for the string "'wasm-unsafe-eval'", then:
|
||||
if (maybe_source_list.has_value()) {
|
||||
auto const& source_list = maybe_source_list.value();
|
||||
|
||||
auto maybe_unsafe_eval = source_list.find_if([](auto const& directive_value) {
|
||||
return directive_value.equals_ignoring_ascii_case(Directives::KeywordSources::UnsafeEval)
|
||||
|| directive_value.equals_ignoring_ascii_case(Directives::KeywordSources::WasmUnsafeEval);
|
||||
});
|
||||
|
||||
if (maybe_unsafe_eval.is_end()) {
|
||||
// 1. Let violation be the result of executing § 2.4.1 Create a violation object for global, policy,
|
||||
// and directive on global, policy, and "script-src".
|
||||
auto script_src_string = Directives::Names::ScriptSrc.to_string();
|
||||
auto violation = Violation::create_a_violation_object_for_global_policy_and_directive(realm, global, policy, script_src_string);
|
||||
|
||||
// 2. Set violation’s resource to "wasm-eval".
|
||||
violation->set_resource(Violation::Resource::WasmEval);
|
||||
|
||||
// 3. Execute § 5.5 Report a violation on violation.
|
||||
violation->report_a_violation(realm);
|
||||
|
||||
// 4. If policy’s disposition is "enforce", then set result to "Blocked".
|
||||
if (policy->disposition() == Policy::Disposition::Enforce)
|
||||
result = Directives::Directive::Result::Blocked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. If result is "Blocked", throw a WebAssembly.CompileError exception.
|
||||
if (result == Directives::Directive::Result::Blocked) {
|
||||
return realm.vm().throw_completion<WebAssembly::CompileError>("Blocked by Content Security Policy"sv);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue