Same-origin policy
The same-origin policy (SOP) is a critical security mechanism implemented by web browsers that restricts how a document or script loaded from one origin can interact with resources from a different origin, thereby isolating potentially malicious content to prevent unauthorized access to sensitive data.[1] This policy primarily aims to protect users by blocking cross-origin reads of data, such as preventing a script on a malicious site from extracting information from a user's email or banking session through the Document Object Model (DOM).[1]
An origin is defined by the combination of a protocol (e.g., HTTP or HTTPS), host (domain name or IP address), and port number (if specified); two resources share the same origin only if all three components match exactly.[1] For instance, https://[example.com](/page/Example.com):443 and https://[example.com](/page/Example.com) are considered the same origin, but http://[example.com](/page/Example.com) differs due to the protocol mismatch.[1] This strict definition ensures that content from unrelated sites cannot interfere with each other without explicit permission.
Under the SOP, browsers block most cross-origin interactions, including reading properties or methods on DOM objects from another origin, though certain operations like writing data (e.g., via form submissions or hyperlinks) and embedding resources (e.g., images or scripts) are permitted to support basic web functionality.[1] Relaxations to the policy exist to enable legitimate cross-origin communication, such as Cross-Origin Resource Sharing (CORS), which allows servers to specify safe origins via HTTP headers, and the deprecated document.domain property, which once permitted relaxing the policy for subdomains but is now discouraged due to security vulnerabilities.[1] Special cases include about:blank and javascript: URLs inheriting the parent origin in some contexts, while data: URLs receive a unique, empty origin.[1]
Fundamentals
Definition and Core Principles
The same-origin policy (SOP) is a fundamental security mechanism implemented in web browsers to isolate content loaded from different origins, preventing malicious scripts from one site from accessing or manipulating resources from another site without explicit permission.[2] This policy ensures that documents and scripts are confined to their originating context, thereby protecting user data from unauthorized access or interference by potentially hostile web content.[3]
At its core, the SOP defines an origin as a tuple consisting of the scheme (such as HTTP or HTTPS), the host (domain name or IP address), and the port number, requiring exact matches across all three components for two resources to be considered same-origin.[2] It primarily restricts reading from or writing to the Document Object Model (DOM) of another origin, meaning a script cannot inspect or modify elements, properties, or methods in a cross-origin document.[3] However, the policy does not universally block all cross-origin interactions; for instance, loading passive resources like images, stylesheets, or scripts via HTML elements (e.g., <img> or <script> tags) is generally permitted, though these resources cannot directly access the embedding page's DOM.[2]
A practical example illustrates this: a JavaScript script executing on https://example.com (port 443 by default) cannot read the DOM content of an iframe loaded from https://api.example.com (typically port 443 but considered distinct due to the subdomain host difference), enforcing isolation even within the same domain.[3] This principle originated in the early evolution of web browsers to counter risks posed by malicious scripts that could exploit the initially permissive model of web interactions, where untrusted code from one site might steal sensitive information from another.[2]
Security Objectives
The same-origin policy (SOP) primarily aims to safeguard sensitive user data, such as cookies and local storage, by prohibiting cross-site access to these resources, thereby preventing unauthorized retrieval or manipulation by scripts from different origins.[1] This restriction is crucial for thwarting impersonation attacks, where a malicious site could otherwise pose as a legitimate one to steal credentials or session information.[4] By enforcing origin-based isolation, SOP ensures that data associated with one site remains inaccessible to others, mitigating risks of data exfiltration in multi-origin browsing sessions.[1]
A key benefit of SOP lies in its isolation of browsing contexts, which effectively prevents session hijacking by limiting the scope of script interactions and protecting authentication tokens from cross-site interference.[4] Additionally, it facilitates the secure embedding of third-party content, such as images or scripts, without granting full DOM access to the embedded resources, allowing controlled integration while maintaining security boundaries.[1] Without SOP, a hypothetical scenario could unfold where a malicious advertisement script on a benign page accesses and relays sensitive banking data from a shared DOM context, leading to widespread data breaches.[4]
In the broader web security model, SOP serves as a foundational mechanism that complements protocols like HTTPS, which secures data transmission, and Content Security Policy (CSP), which further refines resource loading controls, together forming layered defenses against common web threats.[1] This integration underscores SOP's role in establishing trust zones based on origin matching principles, enhancing overall resilience without relying on client-side permissions alone.[4]
Origin Determination
Components of an Origin
In web security, an origin under the same-origin policy is fundamentally defined as either an opaque origin or a structured tuple consisting of three components: the scheme (also known as the protocol, such as "http" or "https"), the host (the domain name or IP address, including subdomains), and the port number (a 16-bit unsigned integer or null for the default port associated with the scheme).[5] The scheme identifies the protocol used to access the resource, the host specifies the network location, and the port distinguishes between multiple services on the same host, with defaults of 80 for "http" and 443 for "https" when the port is null.[6] These components form the basis for determining whether two resources share the same origin, ensuring isolation based on network addressing elements.[1]
Special cases arise for certain schemes or contexts where origins deviate from the standard tuple structure. For instance, the "localhost" host is treated as a unique tuple origin with the scheme ("http" or "https"), host string "localhost", and null port, making resources like http://localhost and http://localhost:3000 distinct due to the explicit port.[7] In contrast, "file://" URLs, which access local files directly, are assigned an opaque origin that serializes to "null" and cannot be compared meaningfully with other origins, rendering them incomparable for same-origin checks.[8] Similarly, "data:" URIs, which embed data inline without network retrieval, always receive a null opaque origin to prevent cross-origin interactions.[9]
Examples illustrate how these components differentiate origins. The URL https://www.example.com:8080 has a different origin from http://www.example.com because the schemes ("https" vs. "http") mismatch, even though the host and default port (null for both) align otherwise.[1] Subdomains further highlight host sensitivity: https://sub.example.com and https://example.com represent distinct origins due to the differing host strings, preventing direct access between them without relaxation mechanisms.[10]
Opaque origins serve as a security fallback for scenarios beyond standard network schemes, such as when browser privacy features, like those blocking third-party tracking, force an opaque origin to obscure the true tuple and prevent origin-based inferences.[8] This opaqueness ensures that such resources cannot participate in same-origin comparisons, effectively treating each as unique and isolated.[1]
Rules for Comparing Origins
Browsers determine whether two origins are the same using a precise comparison algorithm outlined in RFC 6454, which treats an origin as a tuple consisting of a scheme, host, and port. Two origins are the same if and only if their schemes match exactly (case-sensitive but serialized in lowercase), their hosts match after normalization, and their ports match numerically or both use the scheme's default port (such as 80 for HTTP or 443 for HTTPS).[11]
Host comparison involves normalizing the host component to lowercase ASCII and, for internationalized domain names (IDNs), applying the IDNA2008 ToASCII algorithm with flags for STD3 ASCII rules and DNS length verification to produce Punycode-encoded forms (prefixed with "xn--"). IP address literals are compared directly as strings without further encoding; IPv4 addresses use dotted-decimal notation, while IPv6 addresses are enclosed in brackets and have hexadecimal components lowercased for consistency. This ensures that equivalent representations, like "Example.com" and "example.com", or IDN variants, are treated as identical after normalization, but distinct hosts like "localhost" and "127.0.0.1" remain different origins despite resolving to the same loopback address.[12][13]
Port comparison requires exact numeric equality, but defaults are handled implicitly: if a port is unspecified in the URL, it assumes the scheme's standard value, and two such origins match only if both omit the port or both specify the same non-default value. Opaque origins (such as those from data: schemes) are unique and never match tuple-based origins unless both are the same opaque identifier.[14][15]
In cases involving redirects, the origin for same-origin policy enforcement is derived solely from the final URL after all redirects, regardless of intermediate steps; a redirect chain crossing origins does not alter the policy application to the loaded resource.[16]
Browser implementations introduce minor nuances in edge cases. For instance, Chrome and Firefox assign the about:blank URL the same origin as its embedding document (the parent or opener), enabling seamless script access within the same browsing context.[1]
Historical Development
Early Origins in Browsers
The same-origin policy (SOP) was developed by engineers at Netscape Communications in 1995 as a foundational security mechanism for the newly introduced JavaScript in Netscape Navigator 2.0.[1] This policy emerged in response to the growing capabilities of web browsers, particularly the ability to embed frames and scripts, which introduced vulnerabilities in the mid-1990s web environment. Prior browsers like NCSA Mosaic, released in 1993, lacked any form of origin-based isolation, allowing unrestricted access between documents regardless of source, which highlighted the need for such protections as the web evolved.[17]
The primary motivation for inventing the SOP was to mitigate "framing attacks," where a malicious webpage embedded in a frame could access or manipulate sensitive data from the parent window or other frames, such as user credentials in third-party webmail or intranet resources.[1] For instance, without restrictions, a script from an untrusted site could read properties of a framed document from a different domain, enabling data exfiltration or unauthorized interactions. This concern arose shortly after JavaScript's debut in December 1995 with Navigator 2.0 beta, as client-side scripting amplified risks in cross-frame scenarios. The policy enforced isolation by comparing protocol and host components of origins, aiming to prevent such exploits while preserving legitimate same-site functionality.[17]
Early implementations of the SOP in Netscape Navigator were partial, applying restrictions primarily to JavaScript access to the Document Object Model (DOM) but not fully across all browser features, such as certain image or form submissions.[1] These limitations reflected the rapid development pace of the era, influenced by the absence of security boundaries in predecessors like Mosaic, and set the stage for iterative refinements in subsequent browser releases.[1]
Key Milestones and Standardization
In the early 2000s, browser vendors began refining the same-origin policy (SOP) to address evolving web threats, with Internet Explorer 6 (released in 2001) introducing security zones that allowed administrators to configure trust levels for different site categories, potentially relaxing certain restrictions like script execution but maintaining core SOP boundaries for cross-origin access.[18] Firefox, launched in 2004, enhanced SOP enforcement through its Gecko rendering engine and XPCOM component architecture, enabling stricter isolation of scripts and DOM access across origins to mitigate risks from emerging JavaScript-heavy applications.
The Web Hypertext Application Technology Working Group (WHATWG) significantly influenced SOP standardization between 2007 and 2010 by incorporating it into the HTML Living Standard, which clarified the origin tuple (scheme, host, port) and provided a unified definition for browser interoperability. In 2009, researcher Adam Barth published an IETF draft outlining the principles of SOP, emphasizing its role in isolating untrusted content while allowing controlled interactions, which informed subsequent HTML5 specifications.
During the 2010s, key updates expanded SOP's flexibility without undermining its security foundations. Cross-Origin Resource Sharing (CORS) was introduced in a W3C Working Draft on July 27, 2010, enabling servers to specify safe cross-origin HTTP requests via headers like Access-Control-Allow-Origin, thus standardizing a mechanism to bypass SOP for XMLHttpRequest and Fetch API calls.[19] HTML5's postMessage API, specified in the Web Messaging Candidate Recommendation of May 1, 2012, provided a secure channel for cross-origin messaging between windows and iframes, requiring explicit origin validation to prevent unauthorized data leakage.[20]
Post-2020 developments have integrated SOP with broader privacy initiatives, such as Google's Privacy Sandbox (announced in 2020), which enforces same-origin storage for APIs like Protected Audience and Topics to limit cross-site tracking while respecting SOP boundaries. In July 2024, Google abandoned plans to deprecate third-party cookies in Chrome (previously delayed to early 2025 following regulatory reviews), but continues advancing privacy-enhancing technologies that complement SOP by reducing cross-origin identifier sharing and strengthening isolation against tracking without altering core policy enforcement.[21]
Browser Implementation
Enforcement in DOM and JavaScript
The same-origin policy enforces restrictions on client-side access to the Document Object Model (DOM) and JavaScript APIs to prevent unauthorized interactions between resources from different origins. When a script attempts to access DOM elements or properties across origins, browsers throw a SecurityError exception, such as "Blocked a frame with origin 'https://example.com' from accessing a cross-origin frame" or "Permission denied to access property 'x'".[1][22] These restrictions ensure that a document from one origin cannot read or modify the DOM of another origin, thereby protecting sensitive data like user inputs or session information.
Access to properties like window.frames or window.frames[index] is limited to same-origin frames; attempting to retrieve or manipulate a cross-origin frame's content, such as through frame.contentDocument or frame.contentWindow, results in the aforementioned SecurityError. Similarly, methods like document.getElementById() cannot be used to query elements within a cross-origin document, as the entire document object is inaccessible, preventing scripts from extracting or altering content like form data or dynamically generated elements.[1][23] These DOM barriers are evaluated based on origin comparison rules, where schemes, hosts, and ports must match exactly for access to be granted.[1]
JavaScript APIs are also constrained by the same-origin policy to limit cross-origin data leakage. The XMLHttpRequest object and the Fetch API default to same-origin requests, blocking synchronous or asynchronous access to resources from different origins and throwing errors if credentials are involved without proper authorization; for instance, fetch('https://other-origin.com/api') will fail with a network error visible in the console unless relaxed via mechanisms like CORS. Access to sensitive properties such as location.href on a cross-origin Window object is prohibited, returning an opaque or null value instead to avoid exposing navigation details or enabling phishing attacks.[24][25][26]
In the context of iframes, the same-origin policy applies to embedded documents, treating cross-origin content as isolated by default. The sandbox attribute on the <iframe> element imposes additional restrictions by placing the framed content in a unique, opaque origin, which fails all same-origin checks even if the actual origins match; without the allow-same-origin token, scripts cannot access the iframe's DOM or APIs, and features like forms or plugins are disabled to mitigate risks like clickjacking. Combining allow-scripts with allow-same-origin is discouraged, as it could bypass these protections and allow malicious scripts to interact freely.
Violations of these enforcement rules are surfaced through debugging tools for developer awareness and troubleshooting. Browser consoles log specific errors, such as SecurityError for DOM access attempts or "Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource" for API calls, providing details on the blocked origin and reason. Developer tools, including the Network panel, display blocked requests with status indicators like "(blocked:sop)" or "(failed) net::ERR_BLOCKED_BY_CLIENT", allowing inspection of headers and payloads without exposing sensitive data.[27][22]
Network-Level Controls
The same-origin policy (SOP) is enforced at the network level primarily through restrictions on how web applications can initiate and interact with HTTP requests and resource loads, preventing unauthorized cross-origin data access. Browsers block cross-origin requests made via the XMLHttpRequest (XHR) API or the Fetch API by default, allowing only same-origin requests to proceed without additional server-side permissions. This enforcement ensures that JavaScript from one origin cannot read sensitive data from another origin's server unless explicitly permitted. For non-simple requests—those involving non-standard methods (e.g., PUT, DELETE), custom headers, or specific content types—browsers automatically send a preflight OPTIONS request to the target server to verify if the actual request is allowed, further strengthening network isolation.[28][29]
Certain resource types are exempt from strict SOP blocking during loading to support common web functionality, but access to their contents remains restricted. For instance, HTML elements like <script>, <img>, and <link rel="stylesheet"> can load resources from cross-origin servers, enabling the inclusion of external JavaScript, images, and stylesheets. However, these loaded resources are treated as opaque; JavaScript cannot inspect or manipulate their contents if cross-origin. Specifically, for images loaded via <img>, the browser permits rendering and retrieval of metadata like dimensions, but pixel-level data access is prohibited. This read-only behavior extends to canvas elements: drawing a cross-origin image into a <canvas> taints the canvas, blocking operations such as getImageData() or toDataURL() to prevent extraction of restricted image data.
Network-level SOP also influences caching and client-side storage mechanisms to maintain origin isolation. Service workers, which intercept network requests for offline capabilities, respect SOP by limiting fetches to same-origin resources or cross-origin ones only if server-permitted; cross-origin cache entries cannot be read or modified by scripts from different origins. Similarly, IndexedDB databases are partitioned strictly by origin, meaning each origin maintains isolated storage namespaces, preventing cross-origin access to stored data such as user preferences or application state. This partitioning ensures that even within the same browser, data from one site remains inaccessible to another.[30]
Implementations vary across browsers, particularly in mobile environments where additional platform constraints apply. For example, iOS Safari enforces stricter origin isolation due to its integration with the iOS app ecosystem and privacy features like Intelligent Tracking Prevention, which includes enhanced storage partitioning to limit cross-site data persistence and network interactions. These variations can result in more aggressive blocking of certain cross-origin behaviors compared to desktop counterparts, prioritizing user privacy in constrained device contexts.[31]
Relaxing the Policy
Cross-Origin Resource Sharing (CORS)
Cross-Origin Resource Sharing (CORS) is a standardized mechanism that allows servers to relax the same-origin policy by explicitly permitting cross-origin HTTP requests from specified origins through HTTP response headers. The core of this mechanism involves the server including the Access-Control-Allow-Origin header in its response, which specifies the allowed origin(s) or uses a wildcard (*) to permit any origin. For requests that include credentials such as cookies, HTTP authentication, or client-side SSL certificates, the server must set Access-Control-Allow-Credentials: true and cannot use the wildcard for the origin, ensuring that only trusted origins can access sensitive data. This header-based opt-in approach enables secure data sharing for APIs while maintaining browser-enforced restrictions.[32][24]
For certain "non-simple" requests—such as those using methods like PUT, DELETE, or PATCH, or including custom headers—the browser first issues a preflight request using the HTTP OPTIONS method to check server permissions before sending the actual request. This preflight includes headers like Access-Control-Request-Method to indicate the intended method and Access-Control-Request-Headers to list any non-standard headers. The server responds with Access-Control-Allow-Methods to approve methods and Access-Control-Allow-Headers to permit specific headers, along with a maximum age for caching the preflight result to reduce overhead. In contrast, simple requests (limited to GET, POST, or HEAD methods with standard headers like Accept or Content-Type for certain values) bypass preflight and proceed directly if the origin is allowed.[24][33]
CORS responses are categorized as transparent (readable by JavaScript if allowed) or opaque (in no-cors mode, where content is inaccessible to prevent leaks, though the request still occurs for side effects like loading images). Even with CORS enabled, it does not grant full DOM access to cross-origin resources; instead, it facilitates data retrieval via APIs like fetch() or XMLHttpRequest, without allowing script execution or structural manipulation of the loaded document. Security limitations are critical: misconfigurations, such as using Access-Control-Allow-Origin: * alongside credentials, can expose user data to malicious sites by enabling unauthorized access to authenticated endpoints, potentially leading to information disclosure or session hijacking.[34][35][36]
CORS was standardized as a W3C Recommendation on January 16, 2014, building on earlier browser implementations to provide a uniform cross-origin access model. By 2025, it achieves universal adoption in modern browsers, with full support in Chrome (since version 13), Firefox (since 3.5), Safari (since 6), Edge (since 12), and Opera (since 12.1), covering over 95% of global usage and integrating seamlessly with network-level controls for request validation.[37][38]
document.domain Property
The document.domain property of the Document interface allows JavaScript to get or set the domain portion of the current document's origin, as determined for the same-origin policy. By setting this property to a parent domain, scripts can relax the policy's restrictions, enabling limited cross-origin interactions between documents from the same site but different subdomains, such as allowing access to each other's DOM elements. This adjustment affects the host component of the origin, treating subdomains like sub.example.com and api.example.com as equivalent after setting both to example.com.[39]
Under the security model, the property can only be shortened to a valid parent domain suffix (e.g., from sub.example.com to example.com), and it cannot be lengthened or set to an unrelated domain to prevent arbitrary origin manipulation. For the relaxation to enable bidirectional access, both interacting documents must explicitly set document.domain to the identical parent value; otherwise, the same-origin policy remains enforced. This mutual requirement and suffix limitation were designed to mitigate risks while allowing controlled subdomain communication.[39][1]
Historically, document.domain was used in legacy applications for iframe-based communication, such as embedding third-party widgets from subdomains or sharing state between a main site and its sub-resources without full cross-origin mechanisms. A typical example involves a script in an iframe from sub.example.com:
javascript
if (document.domain !== '[example.com](/page/Example.com)') {
document.domain = '[example.com](/page/Example.com)';
}
if (document.domain !== '[example.com](/page/Example.com)') {
document.domain = '[example.com](/page/Example.com)';
}
After setting, the iframe can then access the parent window's DOM, like window.parent.document.getElementById('element'), provided the parent document at [example.com](/page/Example.com) has similarly set its domain.[1]
The setter for document.domain is deprecated due to its weakening of same-origin policy protections, which can expose sites to attacks like DNS rebinding where malicious scripts exploit domain resolution tricks to gain unauthorized access. It also complicates modern features relying on strict origin isolation, such as SharedWorker origin keys. The setter for document.domain is deprecated across browsers. In Chromium-based browsers like Chrome and Edge, it was made immutable by default starting in version 115 (July 2023), though sites can opt out via the Origin-Agent-Cluster header to restore functionality for compatibility. Firefox has deprecated the setter but continues to support it. Developers are encouraged to migrate to secure alternatives like the postMessage API for cross-document messaging.[40][39][41]
Cross-Document Messaging
Cross-Document Messaging enables secure communication between browsing contexts from different origins, such as a parent window and an embedded iframe or a popup window, without violating the same-origin policy. The primary mechanism is the window.postMessage() method, which allows a window to send data to another window, along with an optional targetOrigin parameter specifying the expected origin of the recipient. The syntax is targetWindow.postMessage(message, targetOrigin, [transfer]), where message can be any serializable object, such as strings, arrays, or JSON-serialized data, and transfer (introduced later) lists transferable objects like ArrayBuffer instances to avoid cloning overhead. To receive messages, the target window adds an event listener for the message event on window, accessing the data via event.data, the sender's origin via event.origin, and the sender window via event.source. Developers must validate event.origin in the listener to ensure the message comes from a trusted source, as the same-origin policy prevents direct access to the sender's DOM.[42][43]
Security is paramount in cross-document messaging, as improper use can lead to data leakage or spoofing attacks. Specifying an exact targetOrigin, such as 'https://example.com', restricts delivery to windows from that origin, preventing interception by malicious frames; using '*' allows delivery to any origin, which is risky if sensitive data is involved, as attackers could embed the content and eavesdrop. On the receiving end, always check event.origin against expected values before processing event.data, and ignore messages from untrusted origins to mitigate origin confusion attacks where a malicious site mimics a legitimate sender. This design relaxes the same-origin policy only for message passing, maintaining isolation for DOM and script execution.[43][44]
Common use cases include embedding third-party widgets, such as payment forms or social media feeds in iframes, where the parent page sends configuration data and receives callbacks without exposing internal APIs. Another frequent application is popup-based authentication flows, where an opener window launches a cross-origin popup for login, and the popup posts success status back upon completion, enabling seamless user experiences. The API supports structured data like JSON objects, facilitating complex interactions such as passing user preferences or event notifications between frames.[43]
The postMessage API was introduced in the HTML5 specification drafts around 2008 as part of the Web Messaging feature to address the need for controlled cross-origin communication. It gained initial browser support in Firefox 3, Internet Explorer 8, and Opera 9.5 by mid-2008, with broader adoption following the W3C Candidate Recommendation in 2012. An enhancement for transferable objects was added in 2011, allowing zero-copy transfer of large binary data like ArrayBuffer or ImageBitmap via the third parameter, improving performance for high-volume scenarios such as media processing between windows.[20][45]
Despite its flexibility, cross-document messaging has limitations: it permits only asynchronous message passing and does not grant direct DOM access or script execution across origins, enforcing the same-origin policy for structural interactions. Messages are cloned by default (except transferables), which can incur memory costs for large payloads, and there is no built-in mechanism for request-response patterns, requiring custom acknowledgments. Additionally, while it supports communication with iframes (subject to their enforcement under the same-origin policy), it cannot bypass sandboxing or other frame restrictions.[42][43]
JSONP and WebSockets
JSONP (JSON with Padding) is a technique that circumvents the same-origin policy by leveraging the browser's exception for loading external scripts via the <script> tag, which permits cross-origin resource inclusion without enforcement of SOP restrictions on script execution.[1] In this method, a client requests data from a remote server by appending a callback parameter to the URL, such as ?callback=func, prompting the server to wrap the JSON response in a function call named after the provided callback; the browser then executes this as JavaScript upon loading the script.[46] For example, to fetch data, a page might include <script src="https://api.example.com/data?callback=parseData"></script>, where parseData is a predefined function that processes the returned object like parseData({ "key": "value" });.[46]
This approach evades blocks on cross-origin XMLHttpRequest (XHR) but is limited to GET requests and cannot include custom headers or support methods like POST, making it unsuitable for complex interactions.[47] Moreover, JSONP is vulnerable to cross-site scripting (XSS) attacks, as the server response is directly executed as script; a malicious or compromised endpoint could inject harmful code instead of valid data, potentially compromising the client.[48] Since the introduction of Cross-Origin Resource Sharing (CORS), JSONP has been discouraged due to its security risks and limitations, with modern browsers and APIs favoring CORS for safer cross-origin data access.[49]
WebSockets provide a protocol-level exception to traditional SOP enforcement by enabling full-duplex, persistent communication over a single TCP connection, distinct from HTTP-based restrictions.[50] Connections use ws:// or secure wss:// URIs, where the browser initiates an HTTP upgrade handshake that includes an Origin header specifying the requesting page's origin for server-side validation.[50] Although browsers permit cross-origin WebSocket connections without strict SOP blocking during the handshake—relying instead on the origin-based security model—the server must verify the Origin header to prevent unauthorized access from differing origins, often by echoing it back or rejecting mismatches.[51] Once established, the connection allows bidirectional data exchange, bypassing many SOP limitations on resource interaction but requiring server-enforced policies to maintain security.
To enhance authentication, WebSockets support subprotocols negotiated via the Sec-WebSocket-Protocol header during the handshake, which can incorporate custom mechanisms like tokens or credentials beyond basic origin checks. For instance, a client might instantiate a connection with const socket = new WebSocket('ws://example.com/socket', ['auth-protocol']);, where the server validates the origin and subprotocol before completing the upgrade.[52] This setup ensures controlled cross-origin real-time communication while mitigating risks through rigorous server-side origin and protocol validation.[50]
Exceptions and Edge Cases
Non-Enforced Resources
The same-origin policy (SOP) does not prevent the loading of certain resources from cross-origin sources, allowing web pages to embed external content for functionality while imposing restrictions on reading or accessing that content programmatically. Specifically, elements such as <img>, <video>, <audio>, <object>, <embed>, <script>, <link>, and <style> can fetch and incorporate cross-origin resources without SOP enforcement blocking the initial load. This exemption facilitates practical web development, such as displaying images from content delivery networks (CDNs) or loading stylesheets from third-party services, without compromising the overall security model by permitting unrestricted data extraction.[1][4]
However, these loads come with strict read limitations to mitigate risks like information disclosure. For cross-origin images loaded via <img>, the browser allows rendering and access to metadata like dimensions through the .src property, but drawing the image onto a <canvas> element taints the canvas, preventing methods such as getImageData() from extracting pixel data and thereby avoiding "pixel stealing" attacks where sensitive information could be inferred from image contents. Similarly, cross-origin scripts loaded with <script> execute in the page's context but prohibit JavaScript access to their raw source code or detailed error information, ensuring the loaded code cannot be inspected or manipulated programmatically. For CSS resources, including those via <link> or inline <style>, and background images specified in stylesheets, the content loads and applies without blockage, but programmatic access to the raw CSS text or embedded image data is denied unless the resource is same-origin. Fonts declared with @font-face also load cross-origin in most browsers, enabling typography from external sources, though advanced uses may require additional permissions.[53][1][4]
HTML forms provide another example of non-enforced cross-origin interaction, where <form> submissions to a different origin are permitted, allowing data to be sent without SOP intervention. The response from such a submission, however, cannot be read by JavaScript in the originating page unless it is same-origin or explicitly allowed via mechanisms like CORS, preventing unauthorized access to potentially sensitive reply data. Fetch requests initiated without CORS configuration similarly allow the network transfer but return an opaque response that blocks reading the body or headers, maintaining the policy's protective boundaries. These controlled exceptions balance usability—such as enabling advertisements, embedded media, and distributed resource hosting—with security by isolating loaded content from script-level inspection.[1][4]
Credentialed Cross-Origin Access
In the context of the same-origin policy (SOP), credentialed cross-origin access refers to mechanisms that allow the inclusion of authentication credentials, such as cookies, HTTP authentication headers, or client-side certificates, in requests to origins different from the requesting site's origin. This is primarily facilitated through APIs like XMLHttpRequest (XHR) or the Fetch API, where the withCredentials property for XHR or the credentials: 'include' option for Fetch instructs the browser to attach credentials to cross-origin requests. However, SOP restricts such requests by default, requiring explicit server-side opt-in via mechanisms like Cross-Origin Resource Sharing (CORS) to prevent unauthorized access.[54][55][24]
For credentialed requests to succeed cross-origin, the server must respond with the Access-Control-Allow-Credentials: true header, alongside an Access-Control-Allow-Origin header specifying the exact origin (wildcards like * are incompatible with credentials). Without this opt-in, browsers block the response from being read by JavaScript, even if the request is sent, to mitigate risks of credential leakage. This enforcement ensures that only trusted servers can receive and process authenticated cross-origin traffic.[24]
Reusable authentication credentials, such as OAuth access tokens stored in client-side storage or cookies, pose risks in cross-origin contexts if not properly isolated by SOP. These credentials can be inadvertently included in unauthorized requests, potentially enabling attacks where a malicious site exploits the user's authenticated session on another domain. For instance, an OAuth token granted for one service might be reused in a cross-origin request to manipulate resources if isolation fails.[56][57]
Under SOP, storage mechanisms like localStorage and cookies are inherently same-origin, meaning they cannot be accessed or shared across different origins. As of mid-2025, Chrome implements storage partitioning to further restrict cross-origin access to such storage in embedded contexts (e.g., iframes), isolating data per top-level site to prevent tracking and unauthorized credential sharing. This ensures that third-party embeds receive partitioned storage, blocking direct access to shared credentials unless explicitly allowed via attributes like CHIPS (Cookies Having Independent Partitioned State).[58]
To mitigate these risks, SameSite cookie attributes provide granular control over credential transmission in cross-origin scenarios. The SameSite=Lax mode allows cookies in top-level navigations and safe HTTP methods (e.g., GET) but blocks them in cross-site POST requests, while SameSite=Strict prevents sending cookies entirely on cross-site requests, offering stronger isolation. These attributes, combined with partitioned storage options, help enforce SOP by limiting credential exposure without fully disabling cross-origin functionality.[59][60]
A practical example involves a cross-origin Fetch request: fetch('https://api.example.com/data', { credentials: 'include' }) from https://client.com. If the server does not include Access-Control-Allow-Origin: https://client.com and Access-Control-Allow-Credentials: true in its response, the browser will send the request with cookies but block JavaScript access to the response body, logging a CORS error in the console. This demonstrates SOP's role in protecting credentialed data unless explicitly permitted.[26][61]
Security Threats
The same-origin policy (SOP) aims to prevent cross-origin access to sensitive data, but it does not block all forms of information leakage, particularly through indirect side-channel attacks that infer data without direct DOM access.[62] These attacks, often categorized as cross-site leaks (XS-Leaks), exploit subtle behavioral differences in browser APIs, network responses, or resource handling to disclose user-specific information such as login status, browsing history, or account details.[63] While SOP enforces strict isolation, vulnerabilities arise from observable side effects like timing variations, error messages, or rendering artifacts that attackers can measure or detect cross-origin.
Cross-site scripting (XSS) represents a foundational vector for SOP-related information disclosure, as the policy does not prevent the injection of malicious scripts into a vulnerable same-origin context.[48] When an attacker injects JavaScript via reflected, stored, or DOM-based XSS on a target site, the script executes with the site's full privileges, bypassing SOP restrictions to read sensitive data like session cookies, local storage, or DOM elements containing personal information.[48] For instance, an injected script can access document.cookie to exfiltrate authentication tokens, enabling session hijacking without needing cross-origin permissions.[48] This leakage occurs because SOP treats the injected code as same-origin, granting it unrestricted access to the victim's data within that origin.[64]
Timing attacks, including cache probing, further undermine SOP by measuring resource load times to infer cross-origin states.[65] In a typical cache-probing XS-Leak, an attacker embeds a cross-origin resource (e.g., an image or script) on their site and times its fetch; if previously cached from a target site visit, the load is faster due to browser cache hits, revealing whether the user accessed that resource.[66] This exploits SOP's allowance for non-credentialed cross-origin fetches while using timing side-channels—often amplified by high-resolution APIs like performance.now()—to detect login status or history without direct access.[65] Early research demonstrated such attacks could achieve high accuracy (over 95%) in distinguishing cached from uncached resources, though modern browsers have implemented mitigations like cache partitioning that reduce their effectiveness.[65][67]
CSS history sniffing, though largely deprecated in modern browsers, historically exploited SOP weaknesses via stylesheet rules to leak browsing history.[68] Attackers applied CSS selectors like :visited to cross-origin links on their page; browsers styled visited links differently (e.g., color changes) based on the user's history, allowing JavaScript to detect style differences through computed properties or pixel measurements, inferring prior visits without violating direct origin checks.[68] This side-channel persisted despite SOP because CSS rendering occurs before full isolation enforcement, enabling leaks of up to thousands of URLs per session in vulnerable implementations.[68] Browsers like Firefox and Chrome mitigated it by randomizing or restricting :visited styles, but remnants inform ongoing XS-Leak research.[69]
Pixel tracking evasions leverage SOP's canvas tainting mechanism, where attempting to read pixel data from a cross-origin image drawn to a <canvas> element throws a security error if tainted, indirectly leaking resource availability.[62] An attacker loads a target site's user-specific image (e.g., a profile avatar) into an <img> tag, draws it to the canvas, and calls getImageData(); a "tainted canvas" exception confirms successful cross-origin load (indicating the user is authenticated, as unauthenticated requests might return 404 errors), while no error suggests failure. This bypasses SOP's read restrictions by using error presence as a binary oracle for sensitive states, such as account existence, without CORS approval.[62] Such techniques, part of broader error-based XS-Leaks, can detect login or personalization with near-perfect reliability in non-CORS scenarios.[70]
Modern vectors, including speculative execution side-channels like Spectre, interact with SOP by exploiting CPU branch prediction to transiently access isolated memory, potentially leaking cross-origin data.[71] In browser contexts, Spectre variants (e.g., Variant 1) mislead speculative execution to load same-origin secrets into shared caches, which attackers probe via JavaScript timing to reconstruct data like passwords from other origins, violating SOP's isolation.[72] This hardware-level attack amplifies SOP weaknesses, as transient executions leave measurable cache artifacts before isolation restores; browsers mitigate via site isolation (process-per-site rendering), reducing shared state exposure.[71] Studies show unmitigated browsers could leak up to 4 KB/s of cross-origin data, underscoring the need for layered defenses beyond SOP.[73]
Cross-Site Request Forgery (CSRF)
Cross-Site Request Forgery (CSRF) is a web security vulnerability that exploits the trust a web application has in an authenticated user's browser, allowing an attacker to induce actions on the target site without the user's consent.[74] In this attack, a malicious website or resource tricks the user's browser into sending unauthorized requests to a different origin where the user is authenticated, often leveraging the browser's automatic inclusion of session cookies or other credentials.[75] The core mechanism relies on the attacker embedding cross-origin requests—such as via HTML forms, images, or scripts—within their own site, which the browser executes in the context of the victim's session.[76]
The Same-Origin Policy (SOP) provides partial protection against CSRF by restricting the attacker's ability to read the response from the forged request, thereby preventing the malicious site from confirming the success of the action.[1] However, SOP does not block the initiation or transmission of the request itself; browsers permit cross-origin writes, such as loading an <img> tag with a source pointing to https://bank.com/transfer?amount=1000&to=attacker or submitting a hidden form to the target origin.[56] This limitation arises because SOP primarily enforces read isolation, allowing state-changing operations like GET or POST requests to proceed if they include credentials from the user's active session.[77]
To mitigate CSRF, web applications commonly implement synchronizer tokens, where a unique, unpredictable value is included in legitimate requests and validated server-side to ensure the request originates from the expected site.[74] Another key defense is the SameSite attribute on cookies, which instructs browsers to withhold cookies from cross-site requests, effectively blocking credentialed cross-origin access in many scenarios.[75] SOP complements these measures by isolating origins, making it harder for attackers to inspect or manipulate responses, though it alone cannot fully prevent the forgery of requests.[76]
A classic example involves a banking application vulnerable to CSRF: if a user authenticated to bank.com visits malicious.com, the latter could embed an auto-submitting form targeting bank.com/transfer, initiating an unauthorized funds transfer using the user's session cookies.[56] Techniques like JSONP, intended for cross-origin data fetching, can exacerbate CSRF risks if misused for state-changing operations, as they bypass some SOP restrictions on script execution.[74]
As of 2025, CSRF vulnerabilities have diminished in modern web applications due to widespread adoption of frameworks with built-in protections and browser defaults like SameSite=Lax, but they persist in legacy systems and custom APIs lacking robust token validation.[78] Reports indicate that while not as prevalent as in earlier decades, CSRF remains a notable risk in environments with credentialed cross-origin access, underscoring the ongoing need for layered defenses alongside SOP.[79]
Cookie and Storage Exploitation
The Same-origin policy (SOP) partitions web storage mechanisms such as cookies, localStorage, and sessionStorage to ensure they are accessible only to resources from the same origin, preventing cross-origin scripts from reading or modifying data stored under a different origin.[80] This partitioning applies strictly in scenarios like iframes, where third-party embedded content cannot access or alter the parent document's storage, thereby blocking unauthorized data extraction or tampering by malicious embeds.[31]
Despite this isolation, the cookie's Domain attribute can be exploited to set cookies broadly across a domain and its subdomains, allowing related-domain attackers—such as those controlling a vulnerable subdomain—to shadow or hijack cookies intended for the primary domain through techniques like cookie overwriting.[81] Subdomain configurations without strict scoping further enable cross-site cookie setting, where an attacker on a compromised subdomain can inject cookies that propagate to sibling subdomains, potentially facilitating session fixation or unauthorized access.[82]
Sensitive cookie data can leak cross-origin via the Referer header in HTTP requests, which browsers include by default and may expose domain-specific identifiers or tokens to untrusted parties. Similarly, mishandling of the postMessage API, intended for controlled cross-origin communication, can result in storage leaks if origin validation is inadequate, allowing attackers to exfiltrate cookie-derived data through unverified message passing.[83] To mitigate third-party cookie tracking while preserving functionality, the Cookies Having Independent Partitioned State (CHIPS) proposal, implemented in 2024, partitions such cookies by top-level site, restricting their access to the embedding context and preventing cross-site linkage.[58]
Attackers can force cookie modifications in cross-origin iframes by leveraging permissive Domain attributes or subdomain control, embedding malicious frames that set or overwrite cookies for the parent domain if third-party restrictions are bypassed.[84] Cross-site scripting (XSS) vulnerabilities enable direct mutation of origin-specific storage, as injected scripts execute in the victim's context and can arbitrarily read, alter, or delete localStorage and sessionStorage entries, often leading to data theft or persistent backdoors.[85]
Browser updates have strengthened these protections: Safari's Intelligent Tracking Prevention (ITP), introduced in 2017 and fully blocking third-party cookies by default since 2020, limits cross-site storage access to prevent tracking without user consent.[86] Google's Chrome planned a complete third-party cookie phase-out by early 2025 to align with privacy goals, but as of November 2025, this deprecation has been paused indefinitely, maintaining partial support while promoting alternatives like CHIPS.[87]
Modern Extensions
Cross-Origin Embedder Policy (COEP)
The Cross-Origin Embedder Policy (COEP) is an HTTP response header that allows web developers to declare a policy for how their document loads and embeds cross-origin resources, requiring explicit permission from those resources to prevent unauthorized access. Introduced to enhance web security in the early 2020s, COEP addresses vulnerabilities in resource embedding by enforcing that cross-origin embeds, such as images, scripts, and fonts, must include appropriate Cross-Origin Resource Policy (CORP) or Cross-Origin Resource Sharing (CORS) headers.[88] Its primary purpose is to mitigate cross-origin data exfiltration attacks, such as those exploiting tainted canvases to extract pixel data from images or using fonts to leak information via timing side-channels, thereby promoting safer resource isolation.[89] By opting into COEP, documents can achieve cross-origin isolation, unlocking access to high-performance features like SharedArrayBuffer that were previously restricted due to security concerns.[90]
COEP supports several directives to balance security and usability. The default value, unsafe-none, permits loading cross-origin resources without restrictions, maintaining backward compatibility but offering no additional protection. The require-corp directive enforces strict controls, blocking any cross-origin resource that lacks a CORP header (e.g., Cross-Origin-Resource-Policy: cross-origin) or CORS configuration, ensuring only trusted embeds are allowed.[91] In contrast, the credentialless mode provides a more flexible opt-in for third-party resources; it loads them without user credentials (e.g., cookies), stripping sensitive data while still enabling cross-origin isolation without requiring the remote server to add CORP headers.[90] This mode is particularly useful for embedding unconfigurable third-party content, such as analytics scripts, while mitigating risks associated with credentialed access.[92]
Browser support for COEP began with Chrome and Edge version 83 in April 2020, followed by Firefox version 79 later that year, and Safari version 15.2 in December 2021.[93] The credentialless directive has narrower adoption, supported in Chrome and Edge from version 96 (October 2021) and Firefox from version 119 (November 2023).[94] For example, a website setting Cross-Origin-Embedder-Policy: require-corp will fail to load an image from https://example.com/image.jpg unless that resource includes a CORP header like Cross-Origin-Resource-Policy: cross-origin; otherwise, the load is blocked, and any attempt to draw the image onto a canvas would result in a tainted canvas error, preventing pixel data extraction. This enforcement extends to other embeds like scripts and fonts, ensuring comprehensive protection against speculative execution attacks.[88]
Cross-Origin Opener Policy (COOP)
The Cross-Origin Opener Policy (COOP) is an HTTP response header that governs how a top-level document interacts with other documents opened via mechanisms like window.open(), by controlling whether they share the same browsing context group. When set to same-origin, the policy instructs the browser to place cross-origin documents in a separate browsing context group, effectively severing the opener relationship and preventing access to the original document's global object or DOM. This isolation occurs upon navigation, ensuring that potential cross-origin interactions are blocked at the browsing context level.[95]
The primary purpose of COOP is to mitigate advanced security threats, including side-channel attacks like Spectre, by enabling origin isolation that prevents timing-based data leaks across different origins sharing a process. It also defends against reverse tabnabbing, a phishing technique where a malicious cross-origin page opened in a new tab can navigate or manipulate the original tab's location or content via the window.opener reference. By enforcing separate browsing context groups, COOP ensures that such manipulations are impossible without explicit policy alignment.[96][97][98] As of October 2025, major services like Gmail have enabled COOP to protect against XS-Search attacks.[99]
COOP interacts with the Cross-Origin Embedder Policy (COEP) to achieve cross-origin isolation, a state where a document and its embedder cannot access each other's resources unless explicitly allowed, providing comprehensive mitigations for shared-memory vulnerabilities in modern web APIs. Together, these policies enable secure use of features requiring isolation, such as high-resolution timers or shared array buffers. The header supports three main values: unsafe-none (the default, permitting shared browsing contexts with no restrictions), same-origin (enforcing strict isolation for cross-origin navigations), and same-origin-allow-popups (allowing popups to share contexts if they declare a compatible policy).[89][95]
COOP was standardized in 2021 as part of the HTML Living Standard by the WHATWG, with broad browser support emerging around that time (Chrome 83+, Firefox 79+, Safari 15.2+).[100][101]
Integration with Fetch and Service Workers
The Fetch API, introduced as a modern replacement for XMLHttpRequest, integrates with the same-origin policy (SOP) through configurable request modes that govern cross-origin access. The API supports four primary modes: "same-origin," which restricts requests exclusively to the document's origin, preventing any cross-origin fetches; "cors," which permits cross-origin requests only if the target server explicitly allows them via Cross-Origin Resource Sharing (CORS) headers; "no-cors," which enables simple cross-origin requests (such as for images or scripts) but returns an opaque Response object, limiting access to headers, body, or status details; and "navigate," used specifically for top-level navigation requests.[102][26] These modes ensure that SOP enforcement remains consistent while providing developers with flexible control over network interactions, such as including credentials like cookies via the credentials option (e.g., fetch(url, {mode: 'cors', credentials: 'include'})), which requires CORS preflight checks for non-simple requests.[26]
Service Workers extend SOP compliance by acting as client-side proxies that intercept network requests within their registered scope, which is inherently tied to a specific origin. Registration occurs via navigator.serviceWorker.register(scope), where the scope path must be same-origin relative to the registration origin, locking the worker to that origin and preventing cross-origin interference.[103][104] Upon activation, a Service Worker listens for fetch events and can modify requests or responses, but it must respect SOP: for instance, the onfetch handler receives a FetchEvent with the original request's origin, and any synthetic responses or redirects are subject to the same CORS and opacity rules as direct fetches (e.g., cross-origin responses remain opaque unless CORS-enabled).[102][103] This origin-bound interception enhances caching and offline capabilities for progressive web apps while upholding security boundaries, as demonstrated in code like self.addEventListener('fetch', event => { if (new URL(event.request.url).[origin](/page/Origin) === self.location.[origin](/page/Origin)) { event.respondWith(cachedResponse); } });.[104]
Recent enhancements to Service Workers and related APIs address privacy concerns in cross-origin contexts through partitioning mechanisms. Specifications from 2023 onward introduced partitioned Service Workers, where workers in third-party iframes are isolated per top-level site origin to prevent cross-site tracking, affecting registration and fetch interception by scoping storage and events to the embedding origin rather than the worker's own.[105] Complementing this, Private State Tokens provide a mechanism for anonymous cross-origin authentication, allowing a site to request tokens from a trusted issuer (same or different origin) to prove user legitimacy without revealing identifiers, integrated with Fetch via token redemption headers while adhering to SOP via opt-in issuer lists.[106][107] These features enable safer cross-origin interactions in scenarios like fraud detection.[106]
Looking ahead, the Fetch API and Service Workers are aligning with emerging protocols like WebTransport, which extends low-latency bidirectional communication over HTTP/3 while enforcing SOP through origin checks on session establishment and data streams, ensuring cross-origin usage requires explicit permissions similar to CORS.[108] The WebTransport specification was updated to Working Draft on October 22, 2025.[108] For progressive web apps (PWAs), ongoing standardization efforts explore targeted SOP relaxations, such as multi-origin scopes for federated PWAs, to improve installation and offline functionality without compromising core security principles, though implementations remain proposal-stage as of 2025.[109]