Skip to content

Commit

Permalink
Spec all permissions policy patches and integration (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
blu25 authored Apr 30, 2023
1 parent 0582b0e commit cc80a57
Showing 1 changed file with 273 additions and 2 deletions.
275 changes: 273 additions & 2 deletions spec.bs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,11 @@ spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/
text: historyHandling; url: navigation-hh
text: referrerPolicy; url: navigation-referrer-policy
text: attempt to populate the history entry's document; url: attempt-to-populate-the-history-entry's-document
text: navigation params; url: navigation-params
for: navigation params
text: response; url: navigation-params-response
text: navigable; url: navigation-params-navigable
text: origin; url: navigation-params-origin
for: history handling behavior
text: replace; url: hh-replace
for: document state
Expand All @@ -109,6 +111,21 @@ spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/
urlPrefix: nav-history-apis.html
for: Window
text: navigable; url: window-navigable
urlPrefix: webappapis.html
for: environment
text: target browsing context; url: concept-environment-target-browsing-context
urlPrefix: document-sequences.html
for: browsing context
text: active document; url: active-document
spec: fetch; urlPrefix: https://fetch.spec.whatwg.org/
type: dfn
text: queue a cross-origin embedder policy CORP violation report; url: queue-a-cross-origin-embedder-policy-corp-violation-report
text: should request be blocked due to a bad port; url: block-bad-port
spec: mixed-content; urlPrefix: https://w3c.github.io/webappsec-mixed-content/
type: dfn
text: should fetching request be blocked as mixed content; url: should-block-fetch
spec: CSP; urlPrefix: https://w3c.github.io/webappsec-csp/
type: dfn
urlPrefix: interactive-elements.html
text: accesskey attribute command; url: using-the-accesskey-attribute-to-define-a-command-on-other-elements
text: previously focused element; url: previously-focused-element
Expand All @@ -125,6 +142,16 @@ spec: RFC8941; urlPrefix: https://www.rfc-editor.org/rfc/rfc8941.html
text: structured header; url: #section-1
for: structured header
text: token; url: name-tokens
spec: permissions-policy; urlPrefix: https://w3c.github.io/webappsec-permissions-policy
type: dfn
text: ASCII-serialized policy directive; url: serialized-policy-directive
text: serialized permissions policy; url: serialized-permissions-policy
text: supported features; url: supported-features
text: declared policy; url: declared-policy
text: the special value *; url: the-special-value
text: permissions policy; url: permissions-policy
for: permissions
text: matches; url: matches
spec: CSP; urlPrefix: https://w3c.github.io/webappsec-csp/
type: dfn
text: directive value; url: directive-value
Expand Down Expand Up @@ -258,6 +285,7 @@ dl, dd {
<dd>[=Global attributes=]</dd>
<dd><code>[=width=]</code> — Horizontal dimension</dd>
<dd><code>[=height=]</code> — Vertical dimension</dd>
<dd><code><{fencedframe/allow}></code>[=Permissions policy=] to be applied to the <{fencedframe}>'s contents</dd>
<dt>[=Accessibility considerations=]:</dt>
<dd><p class=XXX>TODO</p></dd>
<dt>[=DOM interface=]:</dt>
Expand All @@ -270,6 +298,7 @@ interface HTMLFencedFrameElement : HTMLElement {
[CEReactions] attribute FencedFrameConfig? config;
[CEReactions] attribute DOMString width;
[CEReactions] attribute DOMString height;
[CEReactions] attribute DOMString allow;
};
</xmp>
</dd>
Expand Down Expand Up @@ -344,6 +373,14 @@ The <dfn attribute for=HTMLFencedFrameElement>config</dfn> IDL attribute getter
1. <span class=XXX>TODO</span>
</div>

The <dfn element-attr for=fencedframe>allow</dfn> attribute, when specified, determines the
[=container policy=] that will be used when the [=Document/permissions policy=] for a {{Document}}
in the <{fencedframe}>'s [=fenced navigable container/fenced navigable=] is initialized. Its value
must be a [=serialized permissions policy=]. [[!PERMISSIONS-POLICY]]

The IDL attribute <dfn attribute for=HTMLFencedFrameElement>allow</dfn> must [=reflect=] the
respective content attribute of the same name.

<h3 id=dimension-attributes>Dimension attributes</h3>

This section details monkeypatches to [[!HTML]]'s <a
Expand Down Expand Up @@ -1024,8 +1061,6 @@ Note: This is because we need to ensure that we do not leak <var ignore>creator<
document's referrer|referrer=], [=Document/origin=], [=creator base url=], [=Document/policy
container=], across the fenced frame boundary.

Issue: Ensure we are doing the right thing for [=Document/permissions policy=].

<h3 id=nested-traversables>Nested traversables</h3>

<h4 id=nested-traversables-intro>Introduction</h4>
Expand Down Expand Up @@ -1731,3 +1766,239 @@ specification is printed below:
/fenced-frame/cspee.https.html
/fenced-frame/embedder-csp-not-propagate.https.html
</wpt>

<h3 id=permissions-policy-changes>Permissions Policies</h3>

*This introductory sub-section is non-normative.*

The [=policy-controlled features=] available to {{Document}}s inside of a <{fencedframe}> are
determined exclusively by the {{FencedFrameConfig}} that the <{fencedframe}> navigates to.
Specifically, the {{FencedFrameConfig}}'s [=fencedframeconfig/config=]'s [=fenced frame
config/effective enabled permissions=] defines the exclusive list of [=policy-controlled features=]
that will be enabled in the {{Document}} (all others will be disabled).

During navigation, the {{FencedFrameConfig}}'s [=fencedframeconfig/config=] [=instantiate a
config|instantiates=] a [=navigable/fenced frame config instance=] that is stored on the [=fenced
navigable container/fenced navigable=]. This navigable's [=navigable/fenced frame config
instance=]'s [=fenced frame config instance/effective enabled permissions=] is consulted [=Should
navigation response to navigation request be blocked by Permissions Policy?|during navigation=]. A
<{fencedframe}> navigation can only succeed if the [=Document/permissions policy=] for the
navigation's resulting {{Document}} has an [=permissions policy/inherited policy=] such that the
[=inherited policy for a feature|inherited policy value=] is "`Enabled`" for each feature in the
[=fenced frame config/effective enabled permissions=]. Otherwise the environment the <{fencedframe}>
is embedded in is deemed unsuitable for the [=fenced frame config=], and the navigation is blocked.

At the same time, to make sure that a <{fencedframe}>'s embedder does not directly influence content
in the frame based on that navigation's [=navigation params/origin=] (since the origin is derived
from cross-site data), this specification modifies various [[PERMISSIONS-POLICY]] algorithms such
that a <{fencedframe}> {{Document}}'s [=permissions policy/inherited policy=] is computed without
consideration of whether its [=navigation params/origin=] is [=same origin=] with its embedder's.
Therefore a feature can only be enabled inside of a <{fencedframe}> if its embedder *explicitly*
delegates it via [=the special value *=] [=allowlist=].

Considering all of the above, we get the following interesting implications:

* If a [=policy-controlled feature|feature=] that [=list/exists=] in the [=fenced frame
config/effective enabled permissions=] has a [=policy-controlled feature/default allowlist=] of
[=the special value *=], and no \`<a http-header>`Permissions-Policy`</a>\` header is served on
the <{fencedframe}> embedder, and the <{fencedframe/allow}> attribute is empty, the navigation
inside the <{fencedframe}> will succeed, and the resulting {{Document}} will be [=allowed to
use=] the [=policy-controlled feature|feature=] (i.e., it will be enabled).

* If a [=policy-controlled feature|feature=] that [=list/exists=] in the [=fenced frame
config/effective enabled permissions=] has a [=policy-controlled feature/default allowlist=] of
<a for="default allowlist">`'self'`</a>, and no \`<a http-header>`Permissions-Policy`</a>\`
header is served on the <{fencedframe}> embedder, and the <{fencedframe/allow}> attribute is
empty, the navigation inside the <{fencedframe}> will be blocked.

Note: This is because ordinarily this [=policy-controlled feature|feature=] would only be enabled
if the subframe's {{Document}} was [=same origin=] with its embedder, a check this specification
avoids for fenced frames, since the <{fencedframe}>s {{Document}}'s [=Document/origin=] is
derived from cross-site data. Therefore, we simply "fail close".

* If a [=policy-controlled feature|feature=] that [=list/exists=] in the [=fenced frame
config/effective enabled permissions=] has a [=policy-controlled feature/default allowlist=] of
<a for="default allowlist">`'self'`</a>, and the <{fencedframe}>'s <{fencedframe/allow}>
attribute contains the [=policy-controlled feature|feature=] but no [=allowlist=], the rules
described in <a href=allow-attribute-fenced-frame>The `allow` attribute section</a>, the default
[=allowlist=] for the feature will be `'src'` which is meant to represent the embedder-supplied
navigation [=URL=], for which there is none when navigating a <{fencedframe}>, as the navigation
[=URL=] is determined by the [=fenced frame config=]. The navigation will be blocked.

* If a [=policy-controlled feature|feature=] that [=list/exists=] in the [=fenced frame
config/effective enabled permissions=] has a [=policy-controlled feature/default allowlist=] of
<a for="default allowlist">`'self'`</a> but either the \`<a
http-header>`Permissions-Policy`</a>\` header served on the <{fencedframe}>'s embedder *OR* the
<{fencedframe/allow}> attribute sets that [=policy-controlled feature|feature=]'s [=allowlist=]
to [=the special value *=], then the navigation inside the <{fencedframe}> will succeed, and the
resulting {{Document}} is [=allowed to use=] the [=policy-controlled feature|feature=].

* If a navigation inside a <{fencedframe}> would otherwise succeed, but the [=response=] on the
navigation inside the <{fencedframe}> is served with a \`<a
http-header>`Permissions-Policy`</a>\` header that sets the [=allowlist=] to "`none`" or an
otherwise incompatible [=origin=] for a feature in the [=fenced frame config/effective enabled
permissions=], the navigation still succeeds, but the {{Document}} in the <{fencedframe}> is
**not** [=allowed to use=] the feature.

Note: This is OK because it is the <{fencedframe}>'s content *itself* that is making the decision
to disable a particular feature, not its embedder environment.

The patches in the below section "fence" the appropriate [[PERMISSIONS-POLICY]] algorithms to
achieve the outcomes described in the above explanatory content.

<h4 id=permissions-policy-patches>Algorithm patches</h4>

<div id=allow-attribute-fenced-frame algorithm=allow-attribute-fenced-frame>
Rename the <a href=https://w3c.github.io/webappsec-permissions-policy/#iframe-allow-attribute>The
`allow` attribute of the `iframe` element</a> section to "The `allow` attribute of the `iframe`
and `fencedframe` element", and rewrite the section to read:

<{iframe}> and <{fencedframe}> elements have an respective `allow` attributes (<{iframe}>:
<{iframe/allow}>; <{fencedframe}>: <{fencedframe/allow}>), which contain an [=ASCII-serialized
policy directive=].

The allowlist for the features named in the attribute may be empty; in that case, the default
value for the [=allowlist=] is "`src`", which represents the origin of the URL in the iframe's
<{iframe/src}> attribute, or the fencedframe's [=fenced frame config=].

When not empty, the <{iframe}>'s <{iframe/allow}> or <{fencedframe}>'s <{fencedframe/allow}>
attribute will result in adding an [=allowlist=] for each [=supported feature=] to the <{iframe}>
or <{fencedframe}>'s [=container policy=].
</div>

<div algorithm=create-permissions-policy>
Modify the [$Create a Permissions Policy for a navigable$] algorithm:

Given null or an element (|container|), an [=origin=] (|origin|), and an optional [=boolean=]
|fenced| that defaults to false, this algorithm returns a new Permissions Policy.

Rewrite step 1 to read:

1. [=Assert=]: if not null, <var ignore>container</var> is either a [=navigable container=] or a
[=fenced navigable container=].

Rewrite step 4 to read:

4. For each |feature| supported:

1. Let |isInherited| be the result of running [$Define an inherited policy for feature in
container at origin$] on |feature|, |container|, |origin|, and |fenced|.

2. Set <var ignore>inherited policy</var>[|feature|] to |isInherited|.
</div>

<div algorithm=create-permissions-policy-response>
Modify the [$Create a Permissions Policy for a navigable from response$] algorithm to read:

Given null, a [=navigable container=], or a [=fenced navigable container=] (|container|), an
[=origin=] (|origin|), and a [=response=] (<var ignore>response</var>), this algorithm returns a
new [=Document/permissions policy=].

Add a step before step 1 that reads:

1. Let |fenced| be true if |container| is a [=fenced navigable container=], and false otherwise.

Modify what is *now* step 2 of the algorithm to read:

2. Let <var ignore>policy</var> be the result of running [$Create a Permissions Policy
for a navigable$] given |container|, |origin|, and |fenced|.
</div>

<div algorithm=attempt-populate-history-patches>
Modify [[HTML]]'s [=attempt to populate the history entry's document=] algorithm. Add a step
before the step inside the [=queue a task|queued task=] starting with "If
|failure| is true, then:" that reads:

8. Otherwise, if the result of [=Should navigation response to navigation request be blocked by
Permissions Policy?=] given <var ignore>navigationParams</var> is "`Blocked`", then set
|failure| to true.

Note: If this algorithm returns "`Blocked`", the pre-existing {{Document}} in the <{fencedframe}>
does not stick around; an error page will be loaded.
</div>

<div algorithm=permissions-policy-block-request>
Create a new algorithm called <dfn>Should navigation response to navigation request be blocked by
Permissions Policy?</dfn> in [[!HTML]].

Given a [=navigation params=] (|navigationParams|), this algorithm returns "`Blocked`" or
"`Allowed`":

1. Let |navigable| be |navigationParams|'s [=navigation params/navigable=].

1. If |navigable| is not a [=fenced navigable container/fenced navigable=], then return
"`Allowed`".

1. Let |origin| be |navigationParams|'s [=navigation params/origin=].

1. Let |effective permissions| be the |navigable|'s [=navigable/fenced frame config instance=]'s
[=fenced frame config instance/effective enabled permissions=].

1. Let |permissions policy| be the result of [$Create a Permissions Policy for a navigable|
creating a permissions policy$] given |navigable|'s [=fenced navigable container=], |origin|,
and <var ignore>fenced</var> set to true.

Note: This is identical to the [=Document/permissions policy=] that will be created when the
navigation constructs the ultimate {{Document}} for this pending navigation. We create it now
and run tests on it since this is the appropriate time to determine if a navigation will fail,
and then throw it away. If the navigation succeeds, it will be recreated identically and
unconditionally installed on the {{Document}}.

1. Let |inherited policy| be |permissions policy|'s [=permissions policy/inherited policy=].

1. [=list/For each=] |effective permission| of |effective permissions|:

1. If |inherited policy|[|effective permission|] is "Disabled", return "`Blocked`".

1. Return "`Allowed`."
</div>

<div algorithm=define-inherited-policy-in-container-patches>
Modify the [$Define an inherited policy for feature in container at origin$] algorithm to
read:

Given a feature (|feature|), null or a [=navigable container=] (|container|), an [=origin=] for a
document in that container (|origin|), and an optional [=boolean=] |fenced| that defaults to
false, this algorithm returns the [=permissions policy/inherited policy=] for that feature.

Rewrite step 3 to read:

3. If the result of executing [$Is feature enabled in document for origin?$] on |feature|,
|container|'s [=Node/node document=], |origin|, and |fenced| is "Disabled", return
"Disabled".

Note: We don't have to rewrite step 2, which also delegates to the same algorithm, to pass in the
|fenced| [=boolean=] because step 2 has to do with checking to see if |feature| is enabled
|container|'s [=Node/node document=], not the {{Document}} hosted *inside* |container|.

Rewrite step 7 to read:

7. If |fenced| is false, |feature|'s [=policy-controlled feature/default allowlist=] is
`'self'`, and |origin| is [=same origin=] with |container|'s [=Node/node document=]'s
origin, return `"Enabled"`.
</div>

<div algorithm=is-feature-enabled-patches>
Modify the [$Is feature enabled in document for origin?$] algorithm to read:

Given a feature (|feature|), a {{Document}} object (|document|), an [=url/origin=] (|origin|), and
an optional [=boolean=] |fenced| that defaults to false, this algorithm returns "`Disabled`" if
|feature| should be considered disabled, and "`Enabled`" otherwise.

Rewrite step 3 to read:

3. If |feature| is present in |policy|'s [=declared policy=],

1. If |fenced| is set to false, and the [=allowlist=] for |feature| in |policy|'s [=declared
policy=] [=permissions/matches=] |origin|, then return "`Enabled`".

2. Otherwise, if |fenced| is set to true, and the [=allowlist=] for |feature| in |policy|'s
[=declared policy=] is [=the special value *=], then return "`Enabled`".

3. Otherwise, return "`Disabled`".

Rewrite step 5 to read:

5. If |fenced| is set to false, |feature|'s [=policy-controlled feature/default allowlist=] is
`'self'`, and |origin| is [=same origin=] with |document|'s origin, return "Enabled".
</div>

0 comments on commit cc80a57

Please sign in to comment.