Skip to content

Instantly share code, notes, and snippets.

@annevk annevk/coop.md Secret
Last active Nov 12, 2019

Embed
What would you like to do?

This is a semi-formal definition of the Cross-Origin-Opener-Policy header, to be merged into the HTML Standard pending various refactoring efforts. (See also the discussion in whatwg/html issue #3740.)

This should only work over a secure context.

To ensure robust isolation the processing model also needs to account for the Cross-Origin-Embedder-Policy header to some extent. That header is mostly defined in https://mikewest.github.io/corpp/, but this document defines its effect on the Cross-Origin-Opener-Policy header processing model.

Document's have an associated cross-origin opener-policy (a cross-origin opener policy). A cross-origin opener policy is null (Cross-Origin-Opener-Policy header isn't set) or a struct consisting of a sameness ("same-origin" or "same-site"), an unsafe-allow-outgoing (a boolean), and a cross-origin embedder-policy (a boolean). (XXX: document creation needs to use "obtaining a cross-origin opener-policy".)

about:blank documents inherit cross-origin opener-policy from their creator's top-level browsing context's active document at the time of creation.

Note: When A embeds B and B creates a popup, this means the popup's initial about:blank document has origin B and A's cross-origin opener-policy.

To obtain a cross-origin opener-policy from a response response and a cross-origin opener policy initiator:

  1. Let value be the result of getting Cross-Origin-Opener-Policy from response's header list.
  2. If value is null, then return null.
  3. Let decodedValue be value, isomorphic decoded. (XXX: we should change Fetch so we can merge this into step 1.)
  4. Let sameness be "same-origin".
  5. Let unsafeAllowOutgoing be false.
  6. If value is "unsafe-inherit", then:
    1. If initiator is null, then return null.
    2. Set sameness to initiator's sameness and unsafeAllowOutgoing to initiator's unsafe-allow-outgoing.
  7. Otherwise:
    1. Let values be the result of strictly splitting decodedValue on HTTP tab or space. (XXX: strictly splitting requires a single code point at the moment.)
    2. If values[0] is not "same-origin" or "same-site", then return null.
    3. If values's size is greater than 2, then return null.
    4. If values's size is 2 and values[1] is not "unsafe-allow-outgoing", then return null.
    5. Set sameness to values[0].
    6. If values's size is 2, then set unsafeAllowOutgoing to true.
  8. Let coop be a cross-origin opener policy whose sameness is sameness, unsafe-allow-outgoing is unsafeAllowOutgoing, and cross-origin embedder-policy is false.
  9. If coop's sameness is "same-origin" and unsafe-allow-outgoing is false, then set coop's cross-origin embedder-policy to the result of obtaining a cross-origin embedder-policy from response. (XXX: the integration probably needs some slight adjustments as even if COOP ends up being null, we'd still want to respect COEP for non-COOP purposes. If we process COEP before COOP we could pass COEP into this algorithm instead.)
  10. Return coop.

Note: the Cross-Origin-Opener-Policy header part is roughly equivalent to this non-normative ABNF:

Cross-Origin-Opener-Policy = sameness [ RWS outgoing ] / inherit

sameness = %s"same-origin" / %s"same-site" ; case-sensitive
outgoing = %s"unsafe-allow-outgoing" ; case-sensitive
inherit  = %s"unsafe-inherit" ; case-sensitive

To match cross-origin opener-policies, given A, originA, B, and originB:

  1. If A is null and B is null, then return true.
  2. If A or B is null, then return false.
  3. If A's sameness is B's sameness, A's unsafe-allow-outgoing is B's unsafe-allow-outgoing, and A's cross-origin embedder-policy is B's cross-origin embedder-policy then:
    1. If A's sameness is "same-origin" and originA is same origin with originB, then return true.
    2. If A's sameness is "same-site", originA's scheme is originB's scheme, and originA's host is same site with originB's host, then return true.
  4. Return false.

Then, when navigating from a document doc in a top-level browsing context bc to a response response:

  1. Let currentCOOP be doc's cross-origin opener-policy.

  2. Let currentOrigin be doc's origin.

  3. Let potentialCOOP be the result of obtaining a cross-origin opener-policy from response and currentCOOP.

  4. Let potentialOrigin be response's URL's origin. TODO: this should take CSP into account as that can make it an opaque origin (and future policies that can do similar things).

  5. If bc's popup sandboxing flag set is not empty and potentialCOOP is non-null, then navigate bc to a network error and abort these steps.

  6. If the result of matching currentCOOP, currentOrigin, potentialCOOP, and potentialOrigin is false and one of the following is false

    • doc is the initial about:blank document
    • currentCOOP's unsafe-allow-outgoing is true
    • potentialCOOP is null

    then:

    1. Create a new browsing context group, move loading of response to a new top-level browsing context newTLBC in that browsing context group.
    2. If bc's popup sandboxing flag set is not empty, then:
      1. Assert: potentialCOOP is null.
      2. Set newTLBC's popup sandboxing flag set to bc's popup sandboxing flag set.
    3. Discard bc. [Note: this does not close bc's browsing context group, except if it was the sole top-level browsing context in which case it could be collected.]
  7. Otherwise, [do the normal thing].

Note: the intent is that this runs for each response, including redirects. User agents are expected to optimize away the many browsing context groups that would be created through a redirect chain, by keeping track as to whether it is needed at all.

@jyasskin

This comment has been minimized.

Copy link

jyasskin commented Mar 20, 2019

Drive-by nit: "compare ..." should return some description of how the arguments relate, but the compare algorithm here just returns true or false. e.g. "Compare 3 and 4" could return "less" but not "true". Is there a more semantic description of what that operation is trying to do?

@annevk

This comment has been minimized.

Copy link
Owner Author

annevk commented Jul 2, 2019

I changed compare to match.

@ParisMeuleman

This comment has been minimized.

Copy link

ParisMeuleman commented Nov 8, 2019

Nit: In obtain a cross-origin opener-policy, should probably include a failure case similar to 7.iii for instances where the header is empty or only spaces.

Nit: In navigating from a document, I think 6.ii is redundant with 5.

In the matching, does "originA's host is same site with originB's host" mean same registrable domain as suggested in CORP: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy_(CORP)#Usage ?

thanks!

@annevk

This comment has been minimized.

Copy link
Owner Author

annevk commented Nov 10, 2019

Pâris, yeah, see same site in https://url.spec.whatwg.org/. The first nit will be clearer once we reference a bunch of algorithms for the operations (which should take of those cases) and the second doesn't apply since potentialCOOP is different.

@mikewest

This comment has been minimized.

Copy link

mikewest commented Nov 12, 2019

Spelling nit: If we modify the header syntax slightly, we can make it match the structured header syntax for items. That is, instead of two, space-separated tokens (Cross-Origin-Opener-Policy: same-site unsafe-allow-outgoing), we can have one token with a parameter (Cross-Origin-Opener-Policy: same-site; unsafe-allow-outgoing).

I don't think this makes a huge difference in practice, but if a one-character change allows us to align with The Future Of Parsing HTTP Headers™, it's probably worthwhile.

@annevk

This comment has been minimized.

Copy link
Owner Author

annevk commented Nov 12, 2019

@mikewest I strongly encourage you to review whatwg/html#4902 (comment) which would also enable that and improve some other things too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.