diff --git a/README.md b/README.md index 87dfdf1..bc951fd 100644 --- a/README.md +++ b/README.md @@ -68,20 +68,44 @@ For more on the policy format, see [the dedicated document](./policy-format.md). ### Fetching and applying the origin policy -Browsers then fetch and cache origin policies for a given origin. They can optionally do so proactively (e.g. for frequently-visited origins), but generally will be driven by the web application sending a HTTP response header requesting that a given origin policy be fetched and applied: +Browsers then fetch and cache origin policies for a given origin. They can optionally do so proactively (e.g. for frequently-visited origins), but generally will be driven by the web application sending a HTTP response header requesting that a given origin policy be fetched and applied. + +For more on the model for fetching and updating origin policies, including motivations behind the design, see [the dedicated document](./version-negotiation.md). Here we summarize the most common patterns applications will probably use: + +#### Optional-but-suggested policy + +``` +Origin-Policy: preferred="my-policy", allowed=("my-old-policy" null) +``` + +This allows a previous revision of the policy (identified by `"my-old-policy"`), or no policy at all (`null`), but specifies that the `"my-policy"` revision is preferred. This response might be processed with the old or null origin policy if those are in the HTTP cache, but in that case, the browser will perform an asynchronous update to fetch the latest policy for use with future responses. + +If a _different_ origin policy is found in the HTTP cache, apart from `"my-policy"` or `"my-old-policy"`, then the response will use the null origin policy, since that is allowed. + +#### Latest available policy, if any + +``` +Origin-Policy: preferred=latest-from-network, allowed=(latest null) +``` + +This says that any cached origin policy from `/.well-known/origin-policy` can be used, but if no such policy is cached, then the null policy will be used instead. In either case, the latest policy will be fetched asynchronously. + +This is essentially a simplification of the previous version, where the server operator is expressing fewer constraints on the exact contents of the policy. + + +#### Mandatory policy ``` -Origin-Policy: allowed=(null "my-policy" "my-old-policy"), preferred="my-policy" +Origin-Policy: allowed=("my-policy") ``` -Here the header specifies allowed and preferred policies, which are matched against the JSON document's `"ids"` values. This allows servers to take on a variety of behaviors, including: +This says that the only origin policy that is allowed is one identified by `"my-policy"`. The origin policy must be fetched, from the cache or network, and must have an `"ids"` value that contains `"my-policy"`, before the response can be processed. If such an origin policy cannot be found, then the response will be treated as a network error. -* Require that a given origin policy be available (either from the cache or via fetching) and applied, before proceeding with page initialization -* Allow a previous revision of the policy, or no policy at all, to apply, but in the background do an asynchronous update of the policy so that future resource fetches will apply the preferred one. +This makes the most sense if the origin policy contains security-critical policies which would not be acceptable to continue without. -For more on the model for fetching and updating origin policies, see [the dedicated document](./version-negotiation.md). +### Policy expiry -Another important note is that the policy items in question automatically stop applying (in a policy item-specific way) when the origin policy stops applying. So, for example, removing the `"content_security"` member of the origin policy manifest above would cause any future loads that use that origin policy to not include the CSP in question. Combined with the usual HTTP cache expiry mechanisms for the `/.well-known/origin-policy` resource, this allows a general "max age" mechanism for origin-wide configuration, similar to the `max-age` parameter of [HSTS](https://tools.ietf.org/html/rfc6797), but for all policy items. +Policy items in question automatically stop applying (in a policy item-specific way) when the origin policy stops applying. So, for example, removing the `"content_security"` member of the origin policy manifest above would cause any future loads that use that origin policy to not include the CSP in question. Combined with the usual HTTP cache expiry mechanisms for the `/.well-known/origin-policy` resource, this allows a general "max age" mechanism for origin-wide configuration, similar to the `max-age` parameter of [HSTS](https://tools.ietf.org/html/rfc6797), but for all policy items. ### Configurable policy items diff --git a/index.src.html b/index.src.html index 257ac18..8ead2b3 100644 --- a/index.src.html +++ b/index.src.html @@ -208,7 +208,7 @@

Examples

Then, change the `Origin-Policy` response header to indicate that this new policy is preferred:
-  Origin-Policy: allowed=("policy-1" "policy-2"), preferred="policy-2"
+  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: @@ -260,6 +260,34 @@

Examples

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 [=origin policy/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: @@ -267,7 +295,7 @@

Examples

Origin-Policy: allowed=(null) - When the browser recieves this header, it will ensure that no origin policy is applied for the response, since only the [=null policy=] is allowed. It will also evict any cache entries for https://example.com/.well-known/origin-policy. + 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.
@@ -280,14 +308,16 @@

The `Origin-Policy` HTTP header

`Origin-Policy` is a [=structured header/dictionary=] structured header. The dictionary has two keys: [[!STRUCTURED-HEADERS]] -* allowed, whose value is an [=structured header/inner list=] that contains [=valid origin policy IDs=] as strings, or the token null, and -* preferred, whose value is a [=valid origin policy ID=]. +* allowed, whose value is an [=structured header/inner list=] that contains [=valid origin policy IDs=] as strings, or the token null, or the token latest; and +* preferred, whose value is either a [=valid origin policy ID=], or the token latest-from-network. At least one of these two entries needs to be provided for the header to have useful behavior. +

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

+ No [=structured header/parameters=] are used in any locations within the structured header. -The 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 [[#from-response]]. In general, the fallback behavior for malformed headers is to behave the same as if `Origin-Policy: allowed=(null)` is sent, and thus use the [=null policy|null origin policy=] for processing the [=response=]. The processing model for omitting the header is to use any previously-cached [=/origin policy=] for the [=/origin=]. +The 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 [[#from-response]]. In general, the fallback behavior for malformed headers is to behave the same as if `Origin-Policy: allowed=(null)` is sent, and thus use the [=null origin policy=] for processing the [=response=]. The processing model for omitting the header is to use any previously-cached [=/origin policy=] for the [=/origin=].

The origin policy manifest

@@ -399,7 +429,7 @@

Data model

: content security policies :: A [=list=] of [=content security policy object|content security policies=]. [[!CSP]] -The null policy is an [=/origin policy=] whose [=origin policy/IDs=] is a list containing the single item null, and who has empty lists for its [=origin policy/feature policy=] and [=origin policy/content security policies=]. As enforced by the processing model, no other policy can have a null in its [=origin policy/IDs=] list. +The null policy is an [=/origin policy=] whose [=origin policy/IDs=] is a list containing the single item null, and who has empty lists for its [=origin policy/feature policy=] and [=origin policy/content security policies=]. As enforced by the processing model, no other policy can have a null in its [=origin policy/IDs=] list. A [=/string=] is a valid origin policy ID if all of the following are true: diff --git a/version-negotiation.md b/version-negotiation.md index 6ef475b..23a9774 100644 --- a/version-negotiation.md +++ b/version-negotiation.md @@ -30,23 +30,15 @@ There must be a way to express that a new origin policy should apply even to tak Conceptually, origin policies are stored in the HTTP cache, under the URL `$origin/.well-known/origin-policy`. In particular, this means they are double-keyed [in the same way as the HTTP cache](https://github.com/whatwg/fetch/issues/904): that is, by (origin of document, origin of top-document). This prevents them from being used for cross-site tracking. This also means that clearing of the cache should generally clear any origin policy. -When the browser makes a request to _url_, it: - -* Checks if it has something cached, and not expired, for `$that_origin/.well-known/origin-policy`. If so, this is the _candidate origin policy_; otherwise the _candidate origin policy_ is the null policy. -* Makes the request to _url_. -* The response can contain a header of the form `Origin-Policy: allowed=("policyA" "polB" "polC" null), preferred="policyZ"`. This indicates that policies with identifiers `"policyA"`, `"polB"`, `"polC"`, or `"policyZ"` are acceptable to the site, or the null origin policy. Call these the _list of acceptable policies_. If the response contains no such header, then the _list of acceptable policies_ contains just the null policy. -* Checks the _list of acceptable policies_ against the _candidate origin policy_. - * If the _candidate origin policy_ has an ID that is in the _list of acceptable origin policies_, then: - * Apply the _candidate origin policy_ and load the response. (This might apply the null policy.) - * If _candidate origin policy_ does not contain an ID that matches the preferred origin policy (indicated by the `preferrered=` portion), then the browser sends a low-priority request to `$that_origin/.well-known/origin-policy` to refresh the HTTP cache, but it won't apply for this page load. - * Otherwise, if the _list of acceptable origin policies_ only contains the null policy but _candidate origin policy_ is not the null policy, then: - * Apply the null policy anyway, and load the response with it. - * In the background, re-fetch `$that_origin/.well-known/origin-policy` to refresh the HTTP cache. - * Otherwise, the browser makes a request (on the same connection, if HTTP/2), to `$that_origin/.well-known/origin-policy`. It delays any processing of the response for _url_ until the new policy has been loaded. If the new policy's identifier still doesn't match the _list of acceptable policies_, then the result of the origin navigation request is a network error. - -Here, the IDs for an origin policy are found inside its JSON document, e.g. `"ids": ["policyA", "polB"]`. - -Note the distinction between string-based policy identifiers, surrounded by quotes (e.g. `"policyA"`), and the `null` token, which is not quoted. This distinction is given to us by the structured headers specification. +A policy can have several identifiers, found in its JSON document as, for example, `"ids": ["policyA", "polB"]`. + +The `Origin-Policy` header can express that it allows, or prefers, specifically-identified policies. The header also can express that it allows any policy, with the token `lastest`, or no policy at all, with the token `null`. Finally, the header can express that it prefers the latest origin policy from the network, with the token `latest-from-network`. + +* ID-based matching is used when the web application wants to express constraints on the contents of the policy. +* `latest` and `latest-from-network` are used when the web application wants to ensure there is an origin policy, but does not want to take on the maintenance burden of expressing ID-based constraints. +* `null` is used when the web application is OK with a given response being processed without an origin policy, for example to avoid an extra server round-trip. + +(Note the distinction between string-based policy identifiers, surrounded by quotes, and the special tokens, which are not quoted. This distinction is given to us by the structured headers specification.) ### Evaluation