How Safari's CSP Blocks XSS and Unauthorized Scripts

This article provides a comprehensive overview of how the Safari web browser enforces Content Security Policy (CSP) to neutralize cross-site scripting (XSS) attacks and halt unauthorized script execution. Powered by the WebKit rendering engine, Safari reads CSP directives delivered via HTTP headers or HTML meta tags to establish a strict security boundary for web applications. By disabling dangerous default behaviors like inline script execution, restricting where external code can be loaded from, blocking dynamic code evaluation, and utilizing cryptographic verification, Safari ensures that malicious scripts injected by attackers cannot be executed in the user's browser.

WebKit and CSP Parsing

Safari relies on its underlying engine, WebKit, to parse and enforce Content Security Policy rules. When a user navigates to a webpage, Safari inspects the server's HTTP response for the Content-Security-Policy header or scans the HTML document for a corresponding <meta> tag. Before rendering the page or executing any code, WebKit compiles these directives into an active security policy. Any resource request or script execution attempt that violates this policy is immediately aborted by the browser at the engine level.

Neutralizing Inline Scripts

The primary method attackers use to execute XSS is injecting malicious inline scripts directly into the HTML document. By default, when a script-src or default-src directive is active, Safari completely disables the execution of all inline scripts. This blocks three major attack vectors:

  • Inline <script> blocks: Any JavaScript written directly inside <script> tags without a valid authorization token is ignored by the browser.
  • Inline Event Handlers: HTML attributes that execute code, such as onload, onclick, or onerror, are entirely blocked from running.
  • javascript: URLs: Navigating to or clicking links that use the javascript: pseudo-protocol is prevented.

By stripping away the browser's ability to trust inline code, Safari ensures that even if an attacker successfully injects a script tag into the Document Object Model (DOM), the WebKit engine will refuse to run it.

Restricting External Script Origins

Attackers often attempt to load malicious payloads from external servers they control. Safari prevents this unauthorized script execution by enforcing source allowlists defined in the script-src directive.

When a webpage attempts to load an external JavaScript file via a <script src="..."> tag, Safari checks the domain of the requested file against the CSP allowlist. If the domain is not explicitly listed, Safari blocks the network request altogether. For example, if a policy is set to script-src 'self' https://trustedscripts.com, Safari will only execute scripts originating from the host domain or trustedscripts.com. Any script tag pointing to an unauthorized third-party domain is terminated before the file can even be downloaded.

Blocking Dynamic Code Evaluation

Cross-site scripting can also occur when legitimate scripts safely loaded by the browser are tricked into executing malicious strings of text as code. Safari mitigates this risk by disabling dynamic code evaluation functions whenever a CSP is present.

Unless the policy explicitly includes the 'unsafe-eval' keyword, Safari blocks the following JavaScript features:

  • The eval() function.
  • The Function() constructor.
  • Passing string arguments (instead of functions) to setTimeout() and setInterval().

If a script attempts to convert a string into executable code using these methods, Safari throws a security exception and halts that specific execution thread, preventing DOM-based XSS attacks.

Cryptographic Nonces and Hashes

Modern web applications often require legitimate inline scripts to function properly. Instead of forcing developers to use the insecure 'unsafe-inline' directive, Safari supports CSP Level 2 and Level 3 mechanisms: nonces and hashes.

  • Nonces: A server generates a unique, random base64-encoded string (a "number used once") for every HTTP request. This nonce is placed in the CSP header (e.g., script-src 'nonce-rAnd0m') and added as an attribute to legitimate <script nonce="rAnd0m"> tags. Safari will only execute inline scripts if their nonce attribute matches the one in the HTTP header. Because an attacker cannot guess the random nonce generated for a specific session, their injected scripts will fail to execute.
  • Hashes: Developers can calculate a cryptographic SHA-256, SHA-384, or SHA-512 hash of a legitimate inline script's contents and include it in the CSP header. Before Safari executes an inline script, WebKit calculates the hash of the script's content. If the calculated hash matches the hash in the CSP header, the script runs. If an attacker alters the script or injects a new one, the hashes will not match, and Safari will block execution.

Enforcing Strict Dynamic Execution

Safari also supports the 'strict-dynamic' directive, which simplifies the loading of complex, modular JavaScript applications. When 'strict-dynamic' is active, Safari grants trust to a root script that has been validated by a nonce or a hash. If that trusted script dynamically creates and appends new <script> elements to the DOM, Safari automatically trusts and executes those new scripts as well. At the same time, Safari ignores any host-based allowlists (like 'self' or https:), ensuring that unauthorized scripts cannot be loaded via standard HTML injection, even from previously allowlisted domains.

Violation Reporting

When Safari blocks an unauthorized script or an XSS attempt, it does so silently to the end-user, simply logging an error in the Web Inspector console. However, to help developers monitor attacks, Safari enforces the report-uri and report-to directives. Whenever a script execution is blocked, WebKit automatically generates a JSON payload detailing the violation—such as the blocked URI, the violated directive, and the line number of the offending code—and sends it via an HTTP POST request to the specified reporting endpoint. This allows security teams to detect and patch XSS vulnerabilities in real time while Safari actively protects the user.