ASVS 5.0 requirement V3.5.4 says separate applications must be hosted on different hostnames, so you get the protection of the same-origin policy: limits on how documents and scripts from one origin interact with another, and hostname based restrictions on cookies. It is a Level 2 requirement.
In plain words: do not put your admin console at example.com/admin and your user content at example.com/uploads. Give them admin.example.com and a separate domain for user content.
Why it matters
Browsers draw the security boundary at the origin: scheme, host and port. Two apps under different paths of one host share everything. A script injected into one can read the other's pages, call its endpoints with the user's session and read localStorage. A cookie set without a Domain attribute is shared across all paths on the host.
The risky pairing is a high value app, such as an admin panel or payment flow, next to a low trust one, such as a blog, a help center or a place where users upload HTML or SVG. A stored XSS in the blog then becomes an admin takeover. Hosting user generated content on the same origin as the main app is the most common version of this mistake.
How to implement it
Plan hostnames by trust level:
www.example.comfor the marketing siteapp.example.comfor the main productadmin.example.comfor internal tools, ideally behind a VPN or identity aware proxy- a separate registrable domain such as
example-usercontent.netfor uploads and untrusted rendering
A separate registrable domain matters for user content because sibling subdomains can still affect each other's cookies: a page on blog.example.com can set a cookie for .example.com that the app will receive.
Scope cookies tightly. Omit the Domain attribute so the cookie is host only, and use the __Host- prefix for session cookies:
Set-Cookie: __Host-sid=abc123; Path=/; Secure; HttpOnly; SameSite=Lax
__Host- cookies require Secure, Path=/ and no Domain, so a sibling subdomain cannot overwrite them.
nginx server blocks, one per app:
server { server_name app.example.com; include app.conf; }
server { server_name admin.example.com; include admin.conf; allow 10.0.0.0/8; deny all; }
How to test it
List every hostname and path prefix that serves an application, then check which apps share an origin:
for u in https://example.com/admin https://example.com/blog https://example.com/uploads; do
curl -sI "$u" | head -1
done
If distinct apps answer on the same host, they fail the requirement. Next, inspect cookies in developer tools: session cookies should show no Domain value, or a host only domain, and the __Host- prefix where possible. Finally, from a page on the low trust host, try document.cookie = "sid=evil; domain=.example.com" and see whether the main app accepts it.
Common mistakes
Path based multi-tenancy for apps with different trust. Paths are not a security boundary.
User uploads served from the app origin. Serve from a separate domain with Content-Disposition and nosniff.
Wide Domain cookies. Domain=.example.com hands the cookie to every subdomain.
Forgotten staging hosts. They often share parent domain cookies with production.
Quick check
Are subdomains enough, or do I need a separate domain?
Subdomains give separate origins, which is the main benefit. For untrusted user content a separate registrable domain also isolates cookies completely.
Sources
- OWASP ASVS 5.0, requirement V3.5.4
- MDN: Same-origin policy