1. Introduction
This section is not normative.
Currently, web applications are almost always compartmentalized by using separate host names to establish separate web origins. This is useful for helping to prevent XSS and other cross-origin attacks, but has many unintended consequences. For example, it causes latency due to additional DNS lookups, removes the ability to use single-origin features (such as the history.pushState API), and creates cryptic host name changes in the user experience. Perhaps most importantly, it results in an extremely inflexible architecture that, once rolled out, cannot be easily and transparently changed later on.
There are several mechanisms for reducing the attack surface for XSS without creating separate host-name based origins, but each pose their own problems. Per-page Suborigins is an attempt to fill some of those gaps. Two of the most notable mechanisms are Sandboxed Frames and Content Security Policy (CSP). Both are powerful but have shortcomings and there are many external developers building legacy applications that find they cannot use those tools.
Sandboxed frames can be used to completely separate untrusted content, but they
pose a large problem for containing trusted but potentially buggy code because
it is very difficult, by design, for them to communicate with other frames. The
synthetic origins assigned in a sandboxed frame are random and unpredictable,
making the use of postMessage
and CORS difficult. Moreover, because
they are by definition unique origins, with no relationship to the original
origin, designing permissions for them to access resources of the original
origin would be difficult.
Content Security Policy is also promising but is generally incompatible with current website design. Many notable companies found it impractical to retrofit most of their applications with it. On top of this, until all applications hosted within a single origin are simultaneously put behind CSP, the mechanism offers limited incremental benefits, which is especially problematic for companies with large portfolios of disparate products all under the same domain.
1.1. Goals
-
Provide a way for different applications hosted at the same real origin to
separate their content into separate logical origins. For example,
https://foobar.com/application
andhttps://foobar.com/widget
, today, are, by definition, in the same origin, even if they’re different applications. Thus an XSS athttps://foobar.com/application
means an XSS athttps://foobar.com/widget
, even ifhttps://foobar.com/widget
is "protected" by a strong Content Security Policy. - Similarly, provide a way for content authors to split their applications into logical modules with origin level separation without using different real origins. Content authors should not have to choose between putting all of their content in the same origin, on different real origins, or putting content in anonymous unique origins (sandboxes).
- Provide a way for content authors to attribute different permissions such as cookie access, storage access, etc. to different suborigins.
Not sure how to actually refer to 'real origins'. This is a terrible name, and we need a better way to talk about them. Maybe physical origin? Traditional origin? (jww)
1.2. Use Cases/Examples
We see effectively three different use cases for Per-page Suborigins:
-
Separating distinct applications that happen to be served from the same
domain, but do not need to extensively interact with other content. Examples
include marketing campaigns, simple search UIs, and so on. This use requires
very little engineering effort and faces very few constraints; the
applications may use
XMLHttpRequest
andpostMessage
to communicate with their host domain as required. -
Allowing for modularity within a larger web application by splitting the
functional components into different suborigins. For example, Gmail might
put the contacts widget, settings tab, and HTML message views in separate
Per-page Suborigins. Such deployments may require relatively modest
refactorings to switch to
postMessage
and CORS where direct DOM access and same-originXMLHttpRequest
are currently used, but we believe doing so is considerably easier than retrofitting CSP onto arbitrary code bases and can be done very incrementally. - Similar to (2), applications with many users can split information relating to different users into their own suborigin. For example, Twitter might put each user profile into a unique suborigin so that an XSS within one profile cannot be used to immediately infect other users or read their personal messages stored within the account.
These examples are expounded upon in §7.1 Case Studies.
2. Key Concepts and Terminology
TODO(jww) This needs to be filled in once we have a pretty good handle on the basic structure of this document. At that point, we should extract the terms defined throughout the spec and place them here.
This section defines several terms used throughout the document.
2.1. Terms defined by this specification
2.2. Terms defined by reference
- origin
- An origin defines the scope of authority or privilege under which a resource operates. It is defined in detail in the Origin specification [RFC6454].
3. Defining a Suborigin
Origins are a mechanism for user agents to group URIs into protection domains. Two URIs are in the same origin if they share the same scheme, host, and port. If URIs are in the same origin, then they share the same authority and can access all of each others resources.
This has been a successful mechanism for privilege separation on the Web. However, it does limit the ability of a URI to separate itself into a new protection domain as it automatically shares authority with all other identical origins, which are defined by physical, rather than programatic, properties. While it is possible to setup unique domains and ports different parts of the same application (scheme is more difficult to separate out), there are a diverse set of practical problems in doing so.
Suborigins provide a mechanism for creating this type of separation programatically. Any resources may provide, in a manner detailed below, a string value [suborigin namespace][]. If either of two URIs provide a suborigin namespace, then the two URIs are in the same origin if and only if they share the same scheme, host, port, and suborigin namespace.
Q. In today’s Web, can’t a site get the effective same protection domain simply by hosting their content at different subdomains?
A. Yes, but there are many practical reasons why this is difficult:
3.1. Difficulties using subdomains
3.1.1. Separate applications, same origin
Google runs Search and Maps on the same domain, respectivelyhttps://www.google.com
and
https://www.google.com/maps
. While these two applications are
fundamentally separate, there are many reasons for hosting them on the same
origin, including historical links, branding, and performance. However, from
security perspective, this means that a compromise of one application is a
compromise of the other since the only security boundary in the browser is the
origin, and both applications are hosted on the same origin. Thus, even if
Google Search were to successful implement a strong Content Security Policy
[CSP2], if Google Maps were to have an XSS vulnerability, it would be
equivalent to having an XSS on Google Search as well, negating Google Search’s
security measures.
3.1.2. Separation within a single application
Separation is sometimes desirable within a single application because of the presence of untrusted data. Take, for example, a social networking site with many different user profiles. Each profile contains lots of untrusted content created by a single user but it’s all hosted on a single origin. In order to separate untrusted content, the application might want a way to put all profile information into separate logical origins while all being hosted at the same physical origin. Furthermore, all content within a profile should be able to access all other content within the same origin, even if displayed in unique frames.This type of privilege separation within an application has been shown to be
valuable and reasonable for applications to do by work such as
Privilege Separation in HTML5 Applications by Akhawe et al
[PRIVILEGESEPARATION]. However, these systems rely on cross frame messaging
using postMessage
even for content in the same trust boundary since
they utilize sandbox
. This provides much of the motivation for the
named container nature of suborigins.
3.2. Threat Model
Origins and the Same Origin Policy have provided a strong defence against malicious applications. Instead of giving the application the power of the user, applications on the Web are limited to a unique space that is defined by their host. However, by tying the origin to the physical host, this has limited the power of developers.
Suborigins attempt to provide developers with tool to contain two different principles that are on the same host. Suborigins allow two or more applications or modules to be hosted at the same origin but use the same origin policy to separate them from each other.
3.2.1. Cross-Document Attacker
An attacker that is able to compromise one document should not be able to
control another document that is on the same host but delivered in a different
suborigin namespace. If an attacker is able to XSS, for example, a document on
example.com
delivered in the suborigin namespace foo
,
the attacker should not be able to control any document on
example.com
not in the foo
namespace.
3.2.2. Out of Scope Attacker
This tool is purely for modularity and meant to be an application security tool. It is not meant to help users differentiate between two different applications at the same host, as reflected by the fact that user agents may not put the suborigin in user-visible UI. Additionally, suborigins cannot protect against colluding malicious or compromised applications.
3.3. Relationship of Suborigins to Origins
Suborigins, in fact, do not provide any new authority to resources. Suborigins simply provide an additional way to construct Origins. That is, Suborigins do not supercede Origins or provide any additional authority above Origins. From the user agent’s perspective, two resources in different Suborigins are simply in different Origins, and the relationship between the two resources should be the same as any other two differing origins as described in [RFC6454]. Thus, this specification is intended to provide the following two important properties:
-
The rules on how Suborigins are defined.
-
The rules on how Suborigins are tracked.
3.4. Representation of Suborigins
At an abstract level, a suborigin consists of the scheme, host, and port of a
traditional origin, plus a suborigin namespace. However, as mentioned
above, suborigins are intended to fit within the framework of [RFC6454].
Therefore, this specification provides a way of serializing a Suborigin bound
resource into a traditional Origin. This is done by inserting the suborigin
namespace into the scheme space of the Origin, thus creating a new scheme but
maintaining all of the information about both the original scheme and the
suborigin namespace. This is done by inserting a +
into the URI
after the scheme, followed by the suborigin namespace, then followed by the rest
of the URI starting with :
.
For example, if the resource is hosted at https://example.com/
in
the suborigin namespace profile
, this would be serialized as
https+profile://example.com/
.
Similarly, if a resource is hosted at https://example.com:8080/
in
the suborigin namespace separate
, this would be serialized as
https+separate://example.com:8080/
.
Internally, the suborigin namespace must be tracked by the user agent. When the origin needs to be serialized for a resource, the user agent should follow the algorithm in §6.3 Serializing Suborigins.
3.5. Opting into a Suborigin
Unlike the sandbox
attribute, suborigin namespaces are predictable
and controllable. Because of this, potentially untrusted content cannot opt into
suborigins, unlike iframe sandboxes. If they could, then an XSS on a site could
enter a specific suborigin and access all of its resources, thus violating the
entire privilege separation suborigins are intended to protect. To prevent this,
the server (rather than a resource itself) is treated as the only authoritative
source of the suborigin namespace of a resource. This is implemented through an
additional header-only Content Security Policy directive suborigin
,
which takes a string value that is the namespace. For example, to put a resource
in the testing
suborigin namespace, the server would specify the
following directive in the CSP header:
suborigin: testing
3.6. The suborigin
Directive
Suborigins are defined by a suborigin directive in the Content Security Policy [CSP2] of the resource. The syntax for the name and value of the directive are described by the following ABNF grammar:
directive-name = "suborigin" directive-value = 1*( ALPHA / DIGIT / "-" )
A resource’s suborigin namespace is the value of the
suborigin
directive.
3.7. Accessing the Suborigin in JavaScript
I don’t have a great idea for how to do this yet. Should it be as simple as document.location.suborigin? Or should it be serialized into document.origin, plus a deserialization mechanism? (jww)
4. Access Control
Cross-origin (including cross-suborigin) communication is tricky when suborigins are involved because they need to be backwards compatible with user agents that do not support suborigins while providing origin-separation for user agents that do support suborigins. The following discussions discuss the three major cross-origin mechanisms that are relevant.
4.1. CORS
For pages in a suborigin namespace, all XMLHttpRequest
s to any URL
should be treated as cross-origin, thus triggering CORS [CORS] logic with
special Finer-Origin:
and Suborigin:
headers added.
Additionally, the Origin:
header that is normally applied to
cross-origin requests should not be added. These header changes are
needed so that a server that recognizes suborigins can see the suborigin
namespace the request is coming from and apply the appropriate CORS headers as
is appropriate, while legacy servers will not "accidentally" approve
cross-origin requests because of an Origin
header that provides an
incomplete picture of the origin (that is, an origin without the suborigin).
The Finer-Origin:
header takes a value identical to the Origin
Header, as defined in [RFC6454]. The Suborigin:
header takes a
string value that is the suborigin namespace. The former servers identically as
the Origin:
header, but in a purposefully backwards incompatible
way, while the Suborigin:
header allows a server to make a more
nuanced access control choice. A user agent must not include more than one
Finer-Origin:
header and must not include more than one
Suborigin:
field.
Similar changes are needed for responses from the server with the addition of
Access-Control-Allow-Finer-Origin
and
Access-Control-Allow-Suborigin
response headers. The former takes
the same values as Access-Control-Allow-Origin
as defined in
[CORS], while the later takes a string value that matches allowed suborigin
namespaces, or *
to allow all suborigin namespaces.
I expect that this will be a relatively controversial part of the proposal, but I think the concern is pretty important. In particular, a lot of the potential benefits of the proposal are eliminated if the Origin header is set with the broad, traditional origin as an isolated but compromised suborigin could just request private information from the other origin. That having been said, we might be able to bypass a lot of these concerns by using the Origin header but putting the serialized suborigin as described above it its place. This would require monkey patching the Origin spec’s syntax of the Origin header.
4.2. postMessage
Cross-origin messaging via postMessage
[WebMessaging] provides
many of the same concerns as CORS. Namely, it is necessary for the recipient to
see the suborigin namespace of the message sender so an appropriate access
control decision can be made, and similarly, legacy applications should by
default treat these messages as not coming from the traditional origin of the
sender.
To enforce this, when a message is sent from a suborigin namespace, the receiver
has the event.origin
value set to null
so if it is
read, it is not treated as coming from any particular origin. Instead, new
properties of event.finerorigin
and event.suborigin
should be set the scheme/host/port and suborigin namespace, respectively.
Similar to the CORS case, another option is to set
event.origin
to the serialized namespace and then provide a
deserialization tool.
4.3. Workers
We need a story here. I basically think that workers should be treated as if they’re in the same suborigin as whatever created them, but I’m also open to other suggestions. Particularly tricky are service workers, which for simplicity sake I suggest we treat as applying universally to all suborigins at a single physical origin since it works in terms of network requests, and suborigins are not relevant to network requests. Pull requests welcome.
5. Impact on Web Platform
Content inside a suborigin namespace is severely restricted in what the hosted
content can do. The restrictions match the behavior of an iframe with the
sandbox attribute set to the value of allow-scripts
[HTML].
While more specifics are described below, the general idea here is to put
suborigin namespaces in a "default secure" context. However, restrictions may be
lifted going forward at a time when a way to whitelist particular Web platform
permissions is well-defined.
5.1. Relationship with Sensitive Permissions
We need to discuss DOM storage, cookies, document.domain, etc. In particular, we should relate this to http://www.w3.org/TR/html5/browsers.html#sandboxing.
6. Framework
Note: These sections are tricky because, unlike traditional origins, we can’t define suborigins in terms of URIs. Since the suborigin namespace is defined in a header, not in the URI, we need to define them in terms of resources.
6.1. Suborigin of a Resource
The suborigin of a resource is the value computed by the following algorithm:
- Let origin be the triple result from starting with step 1 of Section 4 of the Section 4 of of the Origin specification. [RFC6454]
-
If the Content Security Policy of the resource contains a valid
suborigin directive in the directive list
[CSP2], then let
suborigin-namespace
be thedirective-value
. -
Otherwise, let
suborigin-namespace
benull
. -
Return the pair
(origin, suborigin-namespace)
.
6.2. Comparing Suborigins
Since we’d like to make the claim that suborigins do not supersede the same-origin policy, would it be worth defining suborigins purely in terms of being a unique scheme? That would make this section somewhat unnecessary since origin comparisons would be the same.
Two suborigins are "the same" if, and only if, they are identical. In particular:
-
If the origin portions of the suborigin pairs are scheme/host/port triples, the two suborigins are the same if, and only if, they have identical schemes, hosts, and ports and the
suborigin-namespace
portions of the suborigin pairs are identical. -
If both
suborigin-namespace
portions of the suborigin pairs are null, this is considered identical. -
An origin that is a globally unique identifier cannot be the same as an origin that is a scheme/host/port triple, with or without a
suborigin-namespace
.
Two resources are the same-origin if their suborigins are the same.
6.3. Serializing Suborigins
This section defines how to serialize an origin to a unicode [Unicode6] string and to an ASCII [RFC0020] string.
6.3.1. Unicode Serialization of a Suborigin
The Unicode serialization of a suborigin is the value returned by the following algorithm:
-
If the origin portion of the suborigin pair is not a scheme/host/port
triple, then return the string
null
(i.e., the code point sequence U+006E, U+0075, U+006C, U+006C) and abort these steps.
-
Otherwise, if the suborigin-namespace portion of the suborigin pair is not
null:
- Let suffix be the string "+".
- Append the suborigin-namespace portion of the suborigin pair to suffix.
- Append suffix to the scheme part of the origin triple.
- Proceed with step 1 of Section 6.1 in the Origin specification [RFC6454].
6.3.2. ASCII Serialization of a Suborigin
The ASCII serialization of a suborigin is the value returned by the following algorithm:
-
If the origin portion of the suborigin pair is not a scheme/host/port
triple, then return the string
null
(i.e., the code point sequence U+006E, U+0075, U+006C, U+006C) and abort these steps.
-
Otherwise, if the suborigin-namespace portion of the suborigin pair is not
null:
- Let suffix be the string "+".
- Append the suborigin-namespace portion of the suborigin pair to suffix.
- Append suffix to the scheme part of the origin triple.
- Proceed with step 1 of Section 6.2 in the Origin specification [RFC6454].
7. Examples
7.1. Case Studies
7.1.1. Separation of Two Applications
Take a site,https://example.com/
, that runs two applications, Chat
and Shopping, used, respectively, for instant messaging and Internet shopping.
The former is hosted at https://example.com/chat/
, and the latter
is hosted at https://example.com/shopping/
.
The Shopping application has been very well tested and generally does not
contain much untrusted content. In fact, it only takes simple text from
advertisers, and that text only ever appears in HTML contexts, so the
application is able to entity encode the text and stop nearly all cross-site
scripting attacks on the application. Just in case, though, the developers have
implemented a CSP that is served with pages at subpaths of
https://example.com/shopping/
that only allows scripts loaded from
scripts.example.com
.
Historically, https://example.com/chat/
has been riddled with
cross-site scripting attacks. The application takes untrusted content from a
wider variety of sources and for added complexity, that content ends up in many
more contexts, such as HTML tag attributes. On top of that, the developers never
bothered creating a CSP for the application.
This is bad enough, but, unfortunately, it has led to the extremely bad consequence of attackers using the low hanging fruit of Chat to attack Shopping, the more desirable target. Cross-site scripting Shopping allows an attacker to buy goods with the user’s account, so this is really the juicy target.
Since the applications are hosted on the same origin, these attacks have not
traditionally been that difficult. Once an attacker has executed code on Chat
with an XSS, they open a new window or iframe at
example.com/shopping/
. Since this is at the same origin as Chat,
this allows the attacker to inject code through the document
object
of the window or iframe into the Shopping context, allowing the attacker to buy
whatever they’d like.
For historical and branding reasons, both must be hosted on the
example.com
origin. Thus, while these two applications are
completely separate, the company cannot split the products into two different
origins (e.g. examplechat.com
and exampleshopping.com
)
or different suborigins (e.g. chat.example.com
and
shopping.example.com
).
To address this, the developers decide to serve both applications on two
separate suborigins. For all HTTP requests to any subpath of /chat
or /shopping
, example.com includes a Content Security Policy
directive of suborigin: chat
or suborigin: shopping
,
respectively.
This does not remove any of the XSS attacks on Chat. However, when an attacker
injects code into Chat and opens a window or iframe to
example.com/shopping/
, they can no longer inject content through
the document as it will fail the same origin check. Of course, the application
can still use XMLHttpRequest
and postMessage
to
communicate with the document, but that will only be through well defined APIs.
In short, the CSP of the Shopping application is now actually effective as the
permissive Chat application is no longer a bypass of it.
7.1.2. Other Case Study/Studies
TODO: Find out if anyone (perhaps lcamtuf or aaj?) would like to write in another, real life case study.
7.2. Practical Considerations in Using Suborigins
Using suborigins with a Web application should be relatively simple. At the most
basic level, if you have an application hosted on
https://example.com/app/
, and all of its resources are hosted at
subpaths of /app
, it requires that the server set a Content
Security Policy on all HTTP requests to subpaths of /app
that
contain the directive suborigin: namespace
, where
namespace
is of the application’s choosing. This will ensure that
the user agent loads all of these resources into the suborigin
namespace
and will enforce this boundary accordingly.
Additionally, if your application allows cross-origin requests, instead of
adding the usual Access-Control-Allow-Origin
header for
cross-origin requests, the server must add the
Access-Control-Allow-Finer-Origin
and
Access-Control-Allow-Suborigin
headers, as defined in §4.1 CORS.
In the client-side portion of the application, if postMessage
is
used, the application must be modified so it does not check the
event.origin
field. Instead, it should check
event.finerorigin
and additionally the event.suborigin
fields, as they are defined in §4.2 postMessage.
TODO Write down any other practical ramifications of using suborigins here. For example, after we decide what permissions will be allowed by default in suborigins, point out that the application will be limited accordingly.
8. Security Considerations
8.1. Presentation of Suborigins to Users
A complication of suborigins is that while they provide a meaningful security for an application, that boundary makes much less sense to a user. That is, traditional origins provide a security boundary at a physical level: separate scheme, hosts, and ports map to real boundaries external of a given application. However, suborigins as a boundary only makes sense within the context of the program logic itself, and there is no meaningful way for users to make decisions based on suborigins a priori.
Therefore, suborigins should be used only internally in a user agent and should not be presented to users at all. For example, suborigins should never be presented in link text or a URL bar.
8.2. Not Overthrowing Same-origin Policy
Suborigins do not fundamentally change how the same-origin policy works. An application without suborigins should work identically to how it always has, and even in an application with suborigins, the same-origin policy still applies as always. In fact, suborigins have been defined within the context of the same-origin policy so that, in theory, serialized suborigins can be thought of as a just a special case of the traditional same-origin policy.