Skip to content

VOL 08 / CH 04 / LESSON 01

4.1 Browser Security Boundaries: Same-Origin Policy, XSS, and CSP ​

When a browser opens pages from a bank, an email service, and an unfamiliar site, it must prevent the unfamiliar site from reading data from other domains. Much of web security rests on a single core boundary: the origin.

Origin and Site Are Not the Same Concept ​

For ordinary HTTP(S) pages, an origin consists of scheme, host, and port; an omitted port uses the protocol default. URLs such as data: can have opaque origins and do not follow this tuple:

text
https://app.example.com:443

Any difference in these components results in a different origin. The same-origin policy restricts scripts from reading responses or DOM elements from cross-origin sources, but allows certain cross-origin requests to be sent, as well as permits a page to load images, styles, and scripts from external sources.

Cookie SameSite uses a site boundary, typically determined by the registrable domain and scheme. a.example.com and b.example.com can be cross-origin while remaining same-site. Therefore, the claims that "the same-origin policy prevents CSRF" and "SameSite can isolate untrusted subdomains" are both invalid.

XSS Runs Attacker Code in a Trusted Origin ​

Once attacker JavaScript runs within the application's origin, it can read the DOM, Web Storage, and non-HttpOnly cookies, invoke same-origin APIs, modify the interface, and act on behalf of the user. HttpOnly prevents direct script access to that cookie; it neither protects page data nor stops the browser from sending authenticated requests. Common entry points:

  • Stored XSS: malicious content is persisted and then displayed to other users;
  • Reflected XSS: request parameters enter the response without proper sanitization;
  • DOM-based XSS: untrusted data is injected into dangerous DOM sinks on the client side.

The root cause isn't the absence of filtering <script>, but rather treating untrusted data as part of an executable syntax.

Output encoding must match context ​

The same string must be processed differently depending on its context:

html
<div>HTML text context</div>
<a href="URL attribute context">link</a>
<script>JavaScript context</script>
<style>CSS context</style>

HTML text encoding cannot safely replace JavaScript string, URL, or CSS encoding. URLs also require scheme and destination validation; percent-encoding alone does not make javascript: safe. Prefer letting the template engine automatically escape based on context, and avoid inserting user-provided data directly into inline scripts or styles.

On the browser side, prefer sinks that do not parse HTML:

js
element.textContent = untrustedText

Rather than:

js
element.innerHTML = untrustedText

If the product must accept rich text, use a well-maintained HTML sanitizer configured with a clear allowlist, and continuously test for differences between the sanitizer and browser parsing. Regular expressions are not reliable for parsing HTML.

URLs Can Also Serve as Execution Entry Points ​

Before setting href, src, or redirect targets, URLs must be parsed and constrained by scheme, host, and intended use. javascript:, data:, protocol-relative URLs, user information segments, and URL encoding can all bypass simple prefix checks.

For external links opening in new windows, explicitly use rel="noopener" to prevent the new page from controlling the opener; modern browsers usually provide this implicitly for target="_blank". Redirect targets should use server-side mappings or strict allowlists, never pass arbitrary next parameters directly into Location.

CSP Adds a Layer of Defense ​

Start by collecting violations with report-only mode, then progressively tighten the policies:

http
Content-Security-Policy:
  default-src 'none';
  script-src 'nonce-<per-response-random-value>' 'strict-dynamic';
  style-src 'self';
  img-src 'self' https: data:;
  connect-src 'self' https://api.example.com;
  base-uri 'none';
  object-src 'none';
  frame-ancestors 'none'

The header is wrapped for readability; send it as one complete header value. Observation mode uses Content-Security-Policy-Report-Only and does not block violations. Generate at least 128 random bits with a CSPRNG for each response, Base64-encode them, and put the nonce in both the policy and trusted script attributes. Replace the placeholder and keep user input out of nonce-bearing scripts.

In CSP3 implementations, strict-dynamic extends trust to scripts dynamically created by these trusted scripts and ignores host/scheme allowlists, self, and unsafe-inline in the same script-src. A trusted loader must therefore reject arbitrary user-supplied URLs. Policies without this trust mechanism remain vulnerable when broad sources or unsafe-inline allow execution through JSONP, upload directories, or compromised subdomains.

Trusted Types can use require-trusted-types-for 'script' to restrict designated DOM XSS sinks to values created by controlled policies in supporting browsers. It does not sanitize HTML automatically: a policy that passes through arbitrary strings still sends dangerous content to the sink.

Third-Party Scripts Have Page Permissions ​

Analytics, customer support components, and tag managers typically run within the page's origin and can access the same data as first-party scripts. Subresource Integrity (SRI) can verify the bytes of a fixed cross-origin resource:

html
<script
  src="https://cdn.example/widget.js"
  integrity="sha384-..."
  crossorigin="anonymous"></script>

Replace the digest placeholder with a hash of the actual resource bytes; cross-origin SRI also requires the resource server to allow the read through CORS. Under the earlier CSP, this script additionally needs a nonce or other applicable CSP authorization. SRI does not grant that permission. Frequently updated scripts are difficult to pin, and SRI does not restrict execution permissions. Third-party code requires supply chain reviews, minimal data exposure, and isolation strategies. High-risk components may benefit from sandboxed iframes and narrow messaging protocols.

Verification Checklist ​

  • Where does each untrusted value ultimately end up in the parsing context?
  • Does the template default to escaping, and where is escaping explicitly disabled?
  • Does the client contain innerHTML, document.write, or the string eval, etc., as sinks?
  • Do regression tests cover rich-text sanitizer bypasses?
  • Does the Content Security Policy (CSP) rely on nonces or hashes rather than broad allowlists?
  • Are third-party scripts truly required to run in the main origin?
  • After a successful XSS, can HttpOnly, re-authentication, and resource-level authorization effectively limit the attack surface?

Next, follow injections through SQL, commands, templates, and paths in server-side input boundaries.

References ​

Built with VitePress | Software Systems Atlas