Origin Policy

Draft Community Group Report,

This version:
https://wicg.github.io/origin-policy/
Editor:
Domenic Denicola (Google)
Former Editor:
(Google)
Participate:
GitHub WICG/origin-policy (new issue, open issues)

Abstract

This specification defines a delivery mechanism for a number of policies which are to be applied to an entire origin. It complements header-based delivery mechanisms for existing policies (Content Security Policy, Referrer Policy, etc).

Status of this document

This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

1. Introduction

Developers set a number of properties associated with resources on an origin by delivering resource-specific HTTP response headers and meta elements. This is becoming more common over time, and it’s quite normal these days to see multiple kilobytes at the beginning of every response dedicated to such metadata. Setting this metadata is valuable indeed, as it can have a large impact on performance, security, and privacy.

However, the existing delivery mechanism is ill-suited to the task, suffering from a clear mismatch between the resource-specific nature of the metadata declarations on the one hand, and the origin-wide intent of the metadata on the other. Take `Strict-Transport-Security`, for example, which explicitly alters the state of an entire origin, but is delivered as a resource-specific response header. [RFC6797]

Moreover, many resource-specific headers are deployed in such a way as to be de facto static across all resources that an origin serves. `Content-Security-Policy`, for instance, can be very granular indeed, but is commonly implemented by setting a single policy which is delivered for an entire application. [CSP]

A number of implications follow:

  1. Servers are required to repeat themselves. At length. `Content-Security-Policy` alone can easily eat multiple kilobytes of each navigational response, bandwidth which could instead be dedicated to content a user might care about. This has obvious and direct impact on the delay a user experiences when navigating, but has less obvious knock-on effects that reduce performance further. HTTP/2’s HPACK header compression is limited to ~4k of state for processing, for instance, meaning that these verbose headers can greatly reduce its effectiveness. [RFC7541]

  2. Servers are required to repeat themselves. Unerringly. When policy applies strictly to a resource, and not to the origin, then a server must send that policy down with every response. If the developer forgets a page (especially common on error pages), then the policy’s protections don’t apply to that resource, leaving an exploitable hole.

This document introduces a new delivery mechanism for policies which are meant to apply to an entire origin. The server provides an origin policy manifest at a well-known location. This file contains origin-wide configuration metadata, which can affect all requests to and responses from that origin. Then, individual HTTP responses can instruct the user agent to download and process this manifest. Depending on the preference expressed by the server for a given resource, processing of the response can be blocked until the manifest is fetched and its policy items applied. Alternately, the server can indicate that previously-downloaded origin policy manifests, or indeed no origin policy at all, are acceptable for a given resource.

1.1. Examples

MegaCorp, Inc. wishes to ensure that a baseline content security policy is applied to each of the pages on https://example.com, while avoiding the overhead associated with large response headers, and the uncertainty that they’ve really covered everything that lives on the origin.

The first thing MegaCorp does is ensure that https://example.com/.well-known/origin-policy serves an appropriate policy. They place a UTF-8 encoded JSON file at that location, containing the following:

{
  "ids": ["policy-1"],
  "content_security": {
    "policies": ["script-src 'self' https://cdn.example.com"]
  }
}

They then modify all responses from their origin to contain a `Origin-Policy` header requiring that this policy be fetched and applied. For example, given:

GET / HTTP/1.1
Host: example.com
Connection: keep-alive
...

the server can respond with

HTTP/1.1 200 OK
Content-Type: text/html
...
Origin-Policy: allowed=("policy-1")
...

Upon seeing the `Origin-Policy` header, the browser will check if it has a cached origin policy for https://example.com/, with "policy-1" as one of its IDs. For this first request, no such cache entry is found. Since the null policy is not in the header’s allowed list, this means that although the browser can continue to download the response body, further processing of the response (in this case navigation) is blocked on retrieving and applying the origin policy. So the browser does that:

GET /.well-known/origin-policy HTTP/1.1
Host: example.com
Connection: keep-alive
...

and gets back

HTTP/1.1 200 OK
Content-Type: application/originpolicy+json
Cache-Control: max-age=86400
... response body as above ...

The navigation to https://example.com/ then proceeds as normal, with the content security policy indicated in the origin policy manifest applied.

From the landing page at https://example.com/, the user clicks a link and navigates to https://example.com/about. The request and response headers are the same as in the previous example, but this time the browser handles things a bit differently.

This time, when the browser goes to look for "policy-1", it finds a matching policy in the HTTP cache. (We’re assuming here that less than 86400 seconds have passed.) So no requests to https://example.com/.well-known/origin-policy need to be made, and the response for https://example.com/about can be immediately processed, with the origin policy applied.

GigaCorp, Inc., located at https://example.net, wishes to include a widget from MegaCorp on their page. They embed it using an iframe:
<iframe src="https://example.com/widget"></iframe>

When a user visits https://example.net, the subresource request for https://example.com/widget goes through the same origin policy retrieval process as in the first visit. Note that this is true even if the user has already visited https://example.com, because origin policies are stored in the HTTP cache, which is partitioned by top-level origin. That is, there is no cache entry for https://example.com/.well-known/origin-policy in the HTTP cache partition corresponding to https://example.net. See § 6.1 Tracking for more on why this is the case.

But the HTTP cache still helps: repeat visits to https://example.net will be able to use the https://example.com/.well-known/origin-policy cache entry in the https://example.net cache partition, and avoid fetching the origin policy for the embedded iframe.

MegaCorp., Inc. is interested in removing all uses of synchronous XMLHttpRequest from their site. They think they’ve got them all, but they’d like to roll out a report-only feature policy to be sure.

They update their origin policy manifest to include such reporting:

{
  "ids": ["policy-1"],
  "content_security": {
    "policies": ["script-src 'self' https://cdn.example.com"]
  },
  "features": {
    "policy_report_only": "sync-xhr 'none'"
  }
}

New visitors to their site, who download the origin policy manifest for the first time, will get this updated policy, and thus their browsers will report any synchronous XHRs. The same is true for visitors whose cache entry for the origin policy manifest expires, once they re-fetch the updated one. But can we do better?

Yes, we can! The trick is to leverage the policy IDs mechanism. First, update the manifest with a new ID:

{
  "ids": ["policy-2"],
  "content_security": {
    "policies": ["script-src 'self' https://cdn.example.com"]
  },
  "features": {
    "policy_report_only": "sync-xhr 'none'"
  }
}

Then, change the `Origin-Policy` response header to indicate that this new policy is preferred:

Origin-Policy: preferred="policy-2", allowed=("policy-1")

When the browser sees this header value on any response from https://example.com, one of three things will happen:

In this way, MegaCorp can deploy the new origin policy even to clients within the 24-hour max-age window for the origin policy resource, without incurring any extra fetches if the good-enough previous policy is available for immediate use.

The above example has a flaw when combined with cached resources. Consider the case where the user has previously visited https://example.com/static-page.html, which was delivered with headers such as
HTTP/1.1 200 OK
Content-Type: text/html
...
Origin-Policy: allowed=("policy-1")
Cache-Control: max-age=31536000
...

i.e. the page was cached for a year.

If the user re-visits this URL, then the page will be loaded from the cache. But the cached page does not allow "policy-2", which is the only ID found in the current origin policy at https://example.com/.well-known/origin-policy. It only allows "policy-1".

As with all origin policy mismatches, this will cause a re-fetch of the origin policy to try to find a new policy that matches the one requested by the `Origin-Policy` header. (We saw this previously on the first visit, when the page requested "policy-1" but the current policy for the origin was the null policy.) But in this case, such a request will just return the same origin policy manifest, with "ids": ["policy-2"]. Thus, https://example.com/static-page.html will fail to load.

To fix this, the server operator can instead update their origin policy as follows:

{
  "ids": ["policy-1", "policy-2"],
  "content_security": {
    "policies": ["script-src 'self' https://cdn.example.com"]
  },
  "features": {
    "policy_report_only": "sync-xhr 'none'"
  }
}

This would mean that the new origin policy is able to be applied even when "policy-1" is requested, such as in the case of our cached static page.

The general rule here is that when adding a new value to the "ids" field of their origin policy manifest, server operators will be best served by keeping any previous IDs as well, at least until any cached resources that reference that origin policy could have expired.

MegaCorp has noticed that first-time visitors to their site, as in the example above, are having a suboptimal experience, with their responses being blocked by an additional round-trip to fetch the origin policy. Upon reflection, MegaCorp realizes that first-time visitors probably don’t need origin policies applied. All of their site’s exciting stuff is hidden behind a login wall, which involves at least one navigation. So it would be fine to omit the origin policy for such initial responses, as long as it gets applied on post-login responses.

To express this, MegaCorp modifies their response header to

Origin-Policy: preferred="policy-2", allowed=("policy-1" null)

This indicates that the null origin policy is acceptable, if both "policy-1" and "policy-2" are not cached, and thus allows responses to initial visitors to proceed without blocking on fetching an origin policy. But, because there is a preferred value, in such cases the browser will also do a non-blocking fetch to https://example.com/.well-known/origin-policy, to update the HTTP cache for any future requests to https://example.com/.

MegaCorp could even vary their `Origin-Policy` header dynamically between this fast-but-permissive version and the previous blocking-and-strict version, depending on characteristics of the request such as credentials.

MegaCorp is downsizing, and can no longer spare headcount for a dedicated Origin Policy Specialist to carefully maintain their origin policy manifest’s "ids" field, and their `Origin-Policy` header allowed and preferred values. They just want to perform updates to their origin policy manifest, and have them rolled out to their visitors as soon as is possible.

To make this work, they change all their response headers to

Origin-Policy: preferred=latest-from-network, allowed=(latest null)

which will ensure that browser uses the latest origin policy available from the cache, if any, or the null origin policy, if nothing is cached. Furthermore, the preferred=latest-from-network part of the header ensures that the browser will always perform a non-blocking fetch to https://example.com/.well-known/origin-policy to update the HTTP cache.

This is slightly less efficient than the previous version. And, it doesn’t allow expressing constraints on the contents of policies, by matching preferred and allowed values with the manifest’s "ids" field. But, it is simpler to maintain.

MegaCorp wishes to remove the origin policy, and ensure that any visitors to its site no longer get any of its effects. It can do so by using the following `Origin-Policy` response header:
Origin-Policy: allowed=(null)

When the browser receives this header, it will ensure that no origin policy is applied for the response, since only the null policy is allowed.

2. Framework

2.1. The `Origin-Policy` HTTP header

The `Origin-Policy` response header can be used to indicate the allowed and preferred origin policy used when processing a response. It may be sent on any HTTP response, in order to update the user agent’s record of a given origin's origin policy.

`Origin-Policy` is a dictionary structured header. The dictionary has two keys: [STRUCTURED-HEADERS]

Either a non-empty allowed list, or a preferred value, or both, need to be provided.

If an ID is provided for preferred, it is unnecessary to also include it in the allowed list.

This specification does not currently use parameters in any locations within the structured header it defines. (Any provided will be ignored.)

The full processing model for this header, including the fallback behavior for when it is not provided or does not conform to the above data model, is given in § 4.1 Response processing. Roughly speaking, the processing model for omitting the header is to use any previously-cached origin policy for the origin, whereas a malformed header or one with no valid values will cause the response will be treated as a network error.

§ 1.1 Examples gives several examples of valid values for the `Origin-Policy` header. The following are all invalid and will cause a network error:
Origin-Policy:
Origin-Policy: allowed=()
Origin-Policy: allowed=latest
Origin-Policy: preferred=?0, allowed=(another-token)
Origin-Policy: preferred=latest-from-network, allowed=(1.5 null)

The following examples all contain extraneous features, but those features are ignored, and will not cause a network error:

Origin-Policy: allowed=(latest);param=param-value
Origin-Policy: preferred=another-token, allowed=("my-policy" yet-another-token)
Origin-Policy: preferred="my-policy", allowed=(latest null), another-dictionary-key=some-value

Allowing these currently-extraneous features provides potential future extensibility points.

2.2. The origin policy manifest

The origin policy manifest is a JSON-formatted document consisting of a top-level object with one or more members. Its MIME type is application/originpolicy+json, and it is always treated as UTF-8.

{
  "ids": ["my-policy"],
  "features": {
    "policy": "fullscreen 'none'; geolocation 'none'"
  },
  "content_security": {
    "policies": ["frame-ancestors 'none'", "object-src 'none'"],
    "policies_report_only": ["script-src 'self' https://cdn.example.com/js/"]
  }
}

This section gives an overview of the file format, as well authoring requirements. The processing model for the manifest, including how to deal with violations of these authoring requirements, are handled by the algorithms in § 4.3 Parsing.

2.2.1. The "ids" member

The "ids" member defines the IDs of the origin policy. These are used, in conjunction with the `Origin-Policy` header, to allow origins to express their preferences and requirements among any previously-cached revisions of the origin policy manifest.

It must be present, and must be an array containing only valid origin policy ID strings. At least one such string must be present in the array.

2.2.2. The "features" policy item

The "features" member defines the baseline feature policy to apply to any responses from the origin.

If present, it must be an object, with one possible key: "policy". The "policy" sub-member’s value must be a string that matches the serialized-feature-policy production. [FEATURE-POLICY]

Although the `Feature-Policy` header tolerates using commas as a separator, that is an artifact of the HTTP header serialization, and commas will cause parsing failures inside an origin policy manifest.

As per the processing model in § 5.4 Feature Policy, feature policy directives supplied here serve as a baseline, which the `Feature-Policy` header on individual responses can override on an individual feature basis.

If a response is processed with the origin policy manifest given by
{
  "ids": ["my-policy"],
  "features": {
    "policy": "fullscreen 'self'; geolocation 'none'"
  }
}

and the `Feature-Policy` header

Feature-Policy: fullscreen https://example.com; camera 'self'

then the resulting Document's feature policy will be «[ "fullscreen" → «https://example.com/», "geolocation" → «'none'», "camera" → «'self'» ]». Note how the allowlists are overwritten; they do not combine.

2.2.3. The "content_security" policy item

The "content_security" member defines zero or more content security policies to apply to any responses from the origin.

If present, it must be an object, with two possible keys: "policies" and "policies_report_only". Both sub-members' values must be arrays containing zero or more strings, where the strings match the serialized CSP production. [CSP]

Although the `Content-Security-Policy` and `Content-Security-Policy-Report-Only` headers tolerate using commas as a separator, that is an artifact of the HTTP header serialization, and commas will cause parsing failures inside an origin policy manifest. Instead, CSP lists are represented as arrays, so if you wish to use multiple content security policies, include them as multiple array members.

As per the processing model in § 5.5 Content Security Policy, content security policies supplied here are applied before any from the `Content-Security-Policy`/`Content-Security-Policy-Report-Only` headers on individual responses. When used together, they combine the usual manner for multi-item CSP lists.

If a response is processed with the origin policy manifest given by
{
  "ids": ["my-policy"],
  "content_security": {
    "policies": ["script-src cdn.example.org 'unsafe-inline'; object-src 'none'"]
  }
}

and the `Content-Security-Policy` header

Content-Security-Policy: script-src 'nonce-random123' 'strict-dynamic' 'unsafe-inline' https:

then the resulting Document's CSP list will contain two separate content security policies, with the combined effect of only allowing scripts which match both policies. In this case, it will accept scripts with a nonce of random123 (per the header policy) which are either inline script blocks or come from cdn.example.org (per the origin policy).

2.3. The origin-policy well-known URL

Origin policy manifests for a given origin are located at the well-known URL /.well-known/origin-policy. [RFC8615]

Servers who wish to provide an origin policy will need to respond to `GET` requests to this URL with the contents of their origin policy manifest, including setting the correct value of application/originpolicy+json for the response’s `Content-Type` header.

The usual caching strategies (e.g. long max-age, or using the `Etag` header) are quite useful in conjunction with the /.well-known/origin-policy resource, especially since the `Origin-Policy` header can be used to force a cache refresh.

The processing model for how browsers send requests to this URL is given in § 4.2 Updating the origin policy.

3. Data model

An origin policy is a struct containing the following items:

IDs

A list of strings which are each a valid origin policy ID.

feature policy

A feature policy directive. [FEATURE-POLICY]

content security policies

A list of content security policies. [CSP]

The null policy is an origin policy that has empty lists for its IDs, feature policy, and content security policies. As enforced by the processing model, no other policy can an empty IDs list.

A string is a valid origin policy ID if all of the following are true:

4. Processing model

The below algorithms make use of the special values latest and latest-from-network, which we use as the parsed representation of the corresponding tokens from the `Origin-Policy` structured header.

4.1. Response processing

This section details the entry point algorithms, for determining which origin policy applies to an incoming response.

To get the origin policy for a response, given a response response and an environment settings object client:
  1. If response’s URL is null, then return the null policy.

  2. Let origin be response’s URL's origin.

  3. Let header be the result of getting `Origin-Policy` from response’s header list.

  4. If header is null, then return the result of retrieving the cached origin policy for origin given client.

  5. Let (allowedIds, preferredId) be the result of parsing an `Origin-Policy` header given header. If this instead returns "parse error", then return "failure".

  6. Return the result of fetching an origin’s origin policy for origin given client, allowedIds, and preferredId.

To parse an `Origin-Policy` header given a byte sequence headerBytes:
  1. Let parsedHeader be the result of parsing a structured header given headerBytes and "dictionary". If this fails, then return "parse error".

  2. Let allowedIds be an empty ordered set.

  3. If parsedHeader["allowed"] exists:

    1. If parsedHeader["allowed"][0] is not a list, then return "parse error".

    2. Let rawAllowedList be parsedHeader["allowed"][0].

    3. For each itemTuple in rawAllowedList:

      1. Let allowedId be itemTuple[0].

      2. If allowedId is not a string or token, or if allowedId is the empty string, then return "parse error".

      3. If allowedId is a token but is neither null nor latest, then continue.

      4. If allowedId is the null token, then set allowedId to null.

      5. If allowedId is the latest token, then set allowedId to latest.

      6. Assert: if allowedId is a string, then it is a valid origin policy ID.

      7. Append allowedId to allowedIds.

  4. Let preferredId be null.

  5. If parsedHeader["preferred"] exists:

    1. Set preferredId to parsedHeader["preferred"][0].

    2. If preferredId is not a string or the token, or if preferredId is the empty string, then return "parse error".

    3. If preferredId is a token but is not latest-from-network, then set preferredId to null.

    4. If preferredId is the latest-from-network token, then set preferredId to latest-from-network.

    5. Assert: if preferredId is a string, then it is a valid origin policy ID.

  6. If allowedIds is empty and preferredId is null, then return "parse error".

  7. Return the tuple (allowedIds, preferredId).

The constant lookups of the 0th item in tuples above is due to the way that structured header parse results are generally packaged as tuples where the 0th item is the value of interest, and the 1st item is an associated ordered map of parameters. In all cases we ignore the parameters.

4.2. Updating the origin policy

To fetch an origin’s origin policy for an origin origin given client, allowedIds, and preferredId:
  1. Let cachedPolicy be the result of retrieving the cached origin policy given origin and client.

  2. If preferredId is contained in cachedPolicy’s IDs, then return cachedPolicy.

  3. Let url be the result of getting the origin policy manifest URL for origin.

  4. If url is null, then return the null policy.

  5. Let networkRequest be a new request whose url is url, client is client, service-workers mode is "none", destination is "manifest", mode is "same-origin", redirect mode is "error", credentials mode is "omit", referrer policy is "no-referrer", and cache mode is "no-cache".

  6. If cachedPolicy is not the null policy, and either allowedIds contains one of cachedPolicy’s IDs, or allowedIds contains latest, then:

    1. If preferredId is not null, then in parallel, perform a HTTP-network-or-cache fetch given networkRequest. (This will update the cache, but the response will not be used.)

    2. Return cachedPolicy.

  7. If allowedIds contains null, then:

    1. If preferredId is not null, then in parallel, perform a HTTP-network-or-cache fetch given networkRequest. (This will update the cache, but the response will not be used.)

    2. Return the null policy.

  8. Let networkResponse be the result of performing a HTTP-network-or-cache fetch given networkRequest. (Unlike the in parallel fetches, this is blocking.)

  9. Let networkPolicy be the result of getting an origin policy from a manifest response given networkResponse.

  10. If any of the following is true:

    then return networkPolicy.
  11. Return "failure".

We use HTTP-network-or-cache fetch, instead of fetch, to avoid a potential infinite recursion by way of the modifications to fetch in § 5.1 Fetch.

For the most part, the differences between the two do not matter: steps from fetch that handle upgrade-insecure-requests, HSTS, bad ports, FTP, mixed content checks, service workers, redirects, and MIME type checking are not applicable to our case. The notable difference is that CSP is bypassed for these fetches.

Although this specification uses the full HTTP-network-or-cache fetch mechanism to check the HTTP cache for an origin policy, and then re-parses the result each time, implementations could use a more efficient mechanism, as long as the results are observably equivalent. In such cases, implementations ought to take particular care around respecting the cache expiration and keying semantics.

The following provides a non-normative summary of the outcomes of the above algorithm under various conditions:
  • preferredId matches cachedPolicy: use cachedPolicy.

  • preferredId mismatches cachedPolicy or is not provided:

    • cachedPolicy is the null policy:

      • allowedIds contains null: use the null policy, and refresh the HTTP cache in the background.

      • allowedIds does not contain null: fetch networkPolicy, and use it if it matches allowedIds or preferredId.

    • cachedPolicy is not the null policy:

      • allowedIds matches one of cachedPolicy’s IDs: use cachedPolicy, and refresh the HTTP cache in the background if preferredId was provided.

      • allowedIds does not match any of cachedPolicy’s IDs but does contain null: use the null policy, and refresh the HTTP cache in the background if preferredId was provided.

      • allowedIds does not match any of cachedPolicy’s IDs and does not contain null: fetch networkPolicy, and use it if it matches allowedIds or preferredId.

Here latest always matches a non-null cachedPolicy and and latest-from-network always matches a fetched networkPolicy.

To retrieve the cached origin policy for an origin origin given an environment settings object client:
  1. Let url be the result of getting the origin policy manifest URL for origin.

  2. If url is null, return the null policy.

  3. Let cacheCheckRequest be a new request whose url is url, client is client, service-workers mode is "none", destination is "manifest", mode is "same-origin", redirect mode is "error", credentials mode is "omit", and cache mode is "only-if-cached".

  4. Let cachedResponse be the result of performing a HTTP-network-or-cache fetch given cacheCheckRequest.

  5. Return the result of getting an origin policy from a manifest response given cachedResponse.

See above for why this uses HTTP-network-or-cache fetch instead of fetch.

To get the origin policy manifest URL for an origin origin:
  1. If origin is not potentially trusthworthy, then return null.

  2. Return the result of parsing the concatenation of the serialization of origin and "/.well-known/origin-policy".

To get an origin policy from a response given a response response:
  1. If response’s status is not an ok status, then return the null policy.

  2. Let mimeType be the result of extracting a MIME type from response’s header list.

  3. If mimeType’s essence is not "application/originpolicy+json", then return the null policy.

  4. Wait for response’s body.

  5. Let string be the result of UTF-8 decoding response’s body (treating it as a byte sequence).

  6. Return the result of parsing a string into an origin policy given string.

4.3. Parsing

This section details the parsing and validation algorithms for the JSON structure of the origin policy, i.e. turning strings into origin policy structs.

To parse a string into an origin policy given a string string:
  1. Let parsed be the result of parsing JSON into Infra values given string. If this throws an exception, then return the null policy.

  2. If parsed is not a map, then return the null policy.

  3. Let ids be an empty list.

  4. If parsed["ids"] does exists and is a list, then for each potentialId in parsed["ids"]:

    1. If potentialId is a valid origin policy ID, then append potentialId to ids.

  5. If ids is empty, then return the null policy.

  6. Let featurePolicy be an empty feature policy directive.

  7. If parsed["features"] exists and is a map, and parsed["features"]["policy"] exists and is a string, then set featurePolicy to the result of parsing a feature policy directive given parsed["features"]["policy"].

  8. Let contentSecurityPolicies be an empty list.

  9. If parsed["content_security"] exists and is a map, then:

    1. If parsed["content_security"]["policies"] exists and is a list, then for each potentialPolicyString in parsed["content_security"]["policies"]:

      1. If potentialPolicyString is not a string, then continue.

      2. Let policy be the result of parsing a serialized CSP given potentialPolicyString, "header", and "enforce".

      3. If policy’s directive set is not empty, then append policy to contentSecurityPolicies.

    2. If parsed["content_security"]["policies_report_only"] exists and is a list, then for each potentialPolicyString in parsed["content_security"]["policies_report_only"]:

      1. If potentialPolicyString is not a string, then continue.

      2. Let policy be the result of parsing a serialized CSP given potentialPolicyString, "header", and "report".

      3. If policy’s directive set is not empty, then append policy to contentSecurityPolicies.

  10. Return a new origin policy whose IDs is ids, feature policy is featurePolicy, and content security policies is contentSecurityPolicies.

5. Patches to other specifications

The following patches to other specifications would be incorporated as appropriate once this specification graduates from incubation.

5.1. Fetch

Every response gets an associated origin policy, which is an origin policy. It is initially the null origin policy.

The main fetch algorithm is modified by inserting the following step after step 10. (This is after headers have been received and much of the other preliminaries processed, but the request body is likely still downloading.)

  1. Let originPolicy be the result of getting the origin policy for a response given internalResponse and request’s client.

  2. If originPolicy is "failure", then set response and internalResponse to a network error.

  3. Otherwise, set internalResponse’s origin policy to originPolicy.

5.2. HTML

Various parts of the HTML Standard need patching to support the originPolicyIds feature.

5.2.1. WindowOrWorkerGlobalScope

Every WindowOrWorkerGlobalScope object has an associated origin policy IDs, which is a FrozenArray<DOMString>. It is initially the result of creating a frozen array from the empty list.

partial interface mixin WindowOrWorkerGlobalScope {
  readonly attribute FrozenArray<DOMString> originPolicyIds;
};
self . originPolicyIds

Returns a frozen array containing the IDs of the origin policy that applies to the global object. These match the "ids" member from the origin policy manifest (omitting any values that are not valid). If the null origin policy is applied, then the array will be empty.

This can be especially useful in scenarios where the `Origin-Policy` header allows multiple possible origin policies, or the null origin policy, and the page wants a signal to determine which policy the browser retrieved.

The originPolicyIds attribute’s getter steps are to return this's origin policy IDs.

5.2.2. Navigation

During navigation, in particular in the create and initialize a Document object algorithm, we need to set the newly-created Window's origin policy IDs. To do so, as the last substep of the "Otherwise" step that creates a new realm and Window, modify the existing last step to save the created environment settings object as settingsObject, and then add the following step:

  1. Set settingsObject’s global object's origin policy IDs to the result of creating a frozen array from response’s origin policy's IDs.

5.2.3. Running a worker

In the run a worker algorithm, we need to set the newly-created WorkerGlobalScope's origin policy IDs. To do so, inside the perform the fetch steps, after step 2 (which calls fetch) insert the following step:

  1. Set worker global scope’s origin policy IDs to the result of creating a frozen array from response’s origin policy's IDs.

5.3. Service Worker

Various parts of the Service Worker specification need patching to support the originPolicyIds feature.

5.3.1. Model

Every service worker gains an associated origin policy IDs, which is a list of strings.

5.3.2. Update algorithm

The update algorithm needs to save the origin policy IDs from the response onto the service worker. To do so, insert the following step after step 6:

  1. Let origin policy IDs be an empty list.

Then insert the following step after step 9’s perform the fetch substep 9:

  1. Set origin policy IDs to response’s origin policy's IDs.

and finally add the following step after step 14:

  1. Set service worker’s origin policy IDs to origin policy IDs.

5.3.3. Run service worker algorithm

The run service worker algorithm needs to copy the origin policy IDs from the service worker onto the ServiceWorkerGlobalScope. To do so, insert the following step after step 7.7:

  1. Set workerGlobalScope’s origin policy IDs to the result of creating a frozen array from serviceWorker’s origin policy IDs.

5.4. Feature Policy

The process response policy and algorithm needs to be replaced with the following, given a response response and origin origin:

  1. Let policy be a clone of response’s origin policy's feature policy.

  2. Let header be the concatenation of the values of all header fields in response’s header list whose name is `Feature-Policy`, separated by U+002C (,) (according to [RFC7230, 3.2.2]).

  3. For each element returned by splitting header on commas:

    1. Let directive be the result of parsing a feature policy directive given element and origin.

    2. Perform merge directive with declared policy given directive and policy.

  4. Return policy.

The parse header from value and origin sub-algorithm is no longer necessary; its contents were inlined into the above rewrite.

5.5. Content Security Policy

The set response’s CSP list algorithm needs its step 1 replaced with the following:

  1. Set response’s CSP list to a clone of response’s origin policy's content security policies.

(The rest of the algorithm will then append to this list.)

6. Privacy and security considerations

6.1. Tracking

The use of HTTP responses and the HTTP cache as the source of truth for origin policies, as outlined in § 4.2 Updating the origin policy, brings with it the usual privacy considerations related to the HTTP cache. In particular, to avoid using an origin policy as a tracking vector, user agents need to implement the full HTTP cache semantics for origin policies, including recent innovations such as double-keying. Similarly, user agents need to ensure that when the user clears their HTTP cache entries for an origin, any cached origin policies are similarly cleared.

We mention these concerns here because although the processing model uses the HTTP cache directly, we anticipate that implementations might use a separate storage mechanism for origin policies behind the scenes, e.g. for performance reasons. That is, these concerns are taken care of automatically at the specification level, but might require extra care while implementing.

6.2. The use of a well-known URL

This specification forces the origin policy manifest to live at a specific URL, /.well-known/origin-policy. Compared to using a configurable location, this choice enhances security.

In particular, the action of setting a policy for an entire origin is an action that should involve the folks responsible for running the origin’s server, as it has cross-cutting effects upon every application hosted on an origin. That is, the use of a well-known URL ensures that individual web application developers, without access to the entire server and its configuration, cannot interfere with other web applications running on that same origin.

Consider, for example, MegaCorp, Inc.'s https://example.com, which hosts a mail application, a mapping application, a document editing application, and so on. It would be unfortunate indeed if one of these applications accidentally pinned a policy to the entire origin which didn’t account for the rest of the origin’s contents. To mitigate that risk, this specification forces the manifest to live in a shared location for all of https://example.com. The goal is explicitly to elicit the kind of discussion and compromise within an origin that needs to happen in order to deploy an origin policy safely.

7. IANA Considerations

7.1. The `Origin-Policy` header

This section provides the provisional registration of the header `Origin-Policy` in accordance with Registration Procedures for Message Header Fields. [RFC3864]

Header field name

Origin-Policy

Applicable protocol

http

Status

standard

Author/Change controller

WICG

Specification document(s)

https://wicg.github.io/origin-policy/ (see § 2.1 The `Origin-Policy` HTTP header)

7.2. The application/originpolicy+json MIME type

This section provides the provisional registration of the MIME type application/originpolicy+json in accordance with Media Type Specifications and Registration Procedures. [RFC6838]

Type name

application

Subtype name

originpolicy+json

Required parameters

N/A

Optional parameters

N/A

Encoding considerations

8bit (always UTF-8)

Security considerations

See § 6 Privacy and security considerations

Interoperability considerations

TODO

Published specification

https://wicg.github.io/origin-policy/ (see § 2.2 The origin policy manifest)

Applications that use this media type

Web browsers

Fragment identifier considerations

N/A

Additional information
Deprecated alias names for this type

N/A

Magic number(s)

N/A

File extension(s)

None. Often these files will be named origin-policy (with no extension) on typical server setups.

Macintosh file type code

Same as for application/json [RFC8259]

Person & email address to contact for further information

Domenic Denicola d@domenic.me

Intended usage

Common

Restrictions on usage

No restrictions apply.

Change controller

WICG

7.3. Well-known origin-policy URI

This section provides the provisional registration of the origin-policy well-known URI in the Well-Known URIs registry in accordance with Well-Known Uniform Resource Identifiers (URIs). [RFC8615]

Although the rest of this specification, and indeed the rest of the web platform, uses the term "URL", this section in particular intersects with a community that uses the term "URI".

URI suffix

origin-policy

Change controller

WICG

Specification document(s)

https://wicg.github.io/origin-policy/ (see § 2.3 The origin-policy well-known URL)

Related information

N/A

8. Acknowledgments

Thanks to Mark Nottingham for his Site-Wide HTTP Headers draft, which provided inspiration. Likewise, Takeshi Yoshino’s Origin-Wide CORS proposal attempts to address some of the same problems as origin policy. [SITE-WIDE-HEADERS] [ORIGIN-WIDE-CORS]

Thanks to Anne van Kesteren, Daniel Hausknecht, Daniel Vogelheim, Eric Portis, Ian Clelland, Jake Archibald, Jeffrey Yasskin, Mark Nottingham, Martin Thomson, and Nihanth Subramanya for being awesome!

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSP]
Mike West. Content Security Policy Level 3. URL: https://w3c.github.io/webappsec-csp/
[ENCODING]
Anne van Kesteren. Encoding Standard. Living Standard. URL: https://encoding.spec.whatwg.org/
[FEATURE-POLICY]
Ian Clelland. Feature Policy. URL: https://w3c.github.io/webappsec-feature-policy/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[MIMESNIFF]
Gordon P. Hemsley. MIME Sniffing Standard. Living Standard. URL: https://mimesniff.spec.whatwg.org/
[RFC3864]
G. Klyne; M. Nottingham; J. Mogul. Registration Procedures for Message Header Fields. September 2004. Best Current Practice. URL: https://tools.ietf.org/html/rfc3864
[RFC6797]
J. Hodges; C. Jackson; A. Barth. HTTP Strict Transport Security (HSTS). November 2012. Proposed Standard. URL: https://tools.ietf.org/html/rfc6797
[RFC6838]
N. Freed; J. Klensin; T. Hansen. Media Type Specifications and Registration Procedures. January 2013. Best Current Practice. URL: https://tools.ietf.org/html/rfc6838
[RFC8259]
T. Bray, Ed.. The JavaScript Object Notation (JSON) Data Interchange Format. December 2017. Internet Standard. URL: https://tools.ietf.org/html/rfc8259
[RFC8615]
M. Nottingham. Well-Known Uniform Resource Identifiers (URIs). May 2019. Proposed Standard. URL: https://tools.ietf.org/html/rfc8615
[SECURE-CONTEXTS]
Mike West. Secure Contexts. URL: https://w3c.github.io/webappsec-secure-contexts/
[SERVICE-WORKERS-1]
Alex Russell; et al. Service Workers 1. URL: https://w3c.github.io/ServiceWorker/
[STRUCTURED-HEADERS]
Mark Nottingham; Poul-Henning Kamp. Structured Headers for HTTP. ID. URL: https://httpwg.org/http-extensions/draft-ietf-httpbis-header-structure.html
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/
[WebIDL]
Boris Zbarsky. Web IDL. URL: https://heycam.github.io/webidl/

Informative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ORIGIN-WIDE-CORS]
Takeshi Yoshino. Origin-Wide CORS. URL: https://github.com/tyoshino/origin-wide-cors/blob/master/README.md
[RFC7541]
R. Peon; H. Ruellan. HPACK: Header Compression for HTTP/2. May 2015. Proposed Standard. URL: https://httpwg.org/specs/rfc7541.html
[SITE-WIDE-HEADERS]
Mark Nottingham. Site-Wide HTTP Headers. URL: https://mnot.github.io/I-D/site-wide-headers/
[XHR]
Anne van Kesteren. XMLHttpRequest Standard. Living Standard. URL: https://xhr.spec.whatwg.org/

IDL Index

partial interface mixin WindowOrWorkerGlobalScope {
  readonly attribute FrozenArray<DOMString> originPolicyIds;
};