Skip to content

Commit

Permalink
Introduce "flexible" permissions policy behavior for script-construct…
Browse files Browse the repository at this point in the history
…ed `FencedFrameConfig`s (#140)
  • Loading branch information
blu25 authored Dec 11, 2024
1 parent 14f148d commit e6288c1
Showing 1 changed file with 106 additions and 71 deletions.
177 changes: 106 additions & 71 deletions spec.bs
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,9 @@ following [=struct/items=]:
:: a [=string=]
</dl>

A <dfn for=fencedframetype>permissions policy behavior</dfn> is either "<dfn for="permissions policy
behavior">`fixed`</dfn>" or "<dfn for="permissions policy behavior">`flexible`</dfn>".

The <dfn export for=fencedframetype>default fenced frame effective sandboxing flags</dfn> are a
[=sandboxing flag set=] with the following flags:

Expand Down Expand Up @@ -1300,6 +1303,9 @@ A <dfn export>fenced frame config instance</dfn> is a [=struct=] with the follow
: <dfn>effective sandboxing flags</dfn>
:: null, or a [=sandboxing flag set=]

: <dfn>permissions policy behavior</dfn>
:: a [=fencedframetype/permissions policy behavior=]

: <dfn>effective enabled permissions</dfn>
:: null, or a [=list=] of [=policy-controlled features=]

Expand Down Expand Up @@ -1354,6 +1360,10 @@ A <dfn export>fenced frame config instance</dfn> is a [=struct=] with the follow
:: |config|'s [=fenced frame config/effective sandboxing flags=] if null, otherwise |config|'s
[=fenced frame config/effective sandboxing flags=]'s [=effective sandboxing flags/value=]

: [=fenced frame config instance/permissions policy behavior=]
:: [=permissions policy behavior/flexible=] if |config|'s [=fenced frame config/effective
enabled permissions=] is null, [=permissions policy behavior/fixed=] otherwise.

: [=fenced frame config instance/effective enabled permissions=]
:: |config|'s [=fenced frame config/effective enabled permissions=] if null, otherwise
|config|'s [=fenced frame config/effective enabled permissions=]'s [=effective enabled
Expand Down Expand Up @@ -3615,22 +3625,32 @@ directive wouldn't give web sites enough control over their CSP rules. Introduce

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

The [=policy-controlled features=] available to {{Document}}s inside of a <{fencedframe}> are
determined exclusively by the [=fenced frame config=] that the <{fencedframe}> navigates to.
Specifically, the [=fenced frame 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 [=fenced frame config=] [=instantiate a config|instantiates=] a [=browsing
context/fenced frame config instance=] that is stored on the [=browsing context=] in the [=fenced
navigable container/fenced navigable=]. This browsing context's [=browsing context/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.
The [=policy-controlled features=] available to {{Document}}s inside of a <{fencedframe}>, as well
as the manner in which they are calculated, vary depending on how the [=fenced frame config=] that
the <{fencedframe}> navigates to is constructed.

A [=fenced frame config instance=] created via the {{FencedFrameConfig}} constructor on the web
platform will have a [=permissions policy behavior/flexible=] [=fencedframetype/permissions policy
behavior=], and the inner {{Document}} of the <{fencedframe}> it navigates will be allowed to
inherit permissions as long as they are part of the [=fenced frame allowed permissions=] list. All
other [=policy-controlled features=] will be disabled.

A [=fenced frame config instance=] created via a config-generating API that sets its [=fenced frame
config/effective enabled permissions=] will have a [=permissions policy behavior/fixed=]
[=fencedframetype/permissions policy behavior=], and the inner {{Document}} of the <{fencedframe}>
it navigates to will have the [=fenced frame config/effective enabled permissions=] be the exclusive
list of [=policy-controlled features=] that will be enabled in the {{Document}} (all others will be
disabled).

During a <{fencedframe}> navigation to a [=fenced frame config instance=] with a [=permissions
policy behavior/fixed=] [=fencedframetype/permissions policy behavior=], it compares the [=fenced
frame config instance/effective enabled permissions=] of the [=fenced frame config instance=] being
navigated to against the resulting {{Document}}'s [=Document/permissions policy=]'s [=permissions
policy/inherited policy=]. The navigation only succeeds if each inherited feature whose [=inherited
policy for a feature|inherited policy value=] is "`Enabled`" also appears in the [=fenced frame
config instance/effective enabled permissions=] [=fenced frame config instance=]. 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
Expand All @@ -3640,7 +3660,8 @@ consideration of whether its [=navigation params/origin=] is [=same origin=] wit
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:
Considering all of the above, we get the following interesting implications for [=permissions policy
behavior/fixed=] [=fencedframetype/permissions policy behavior=] navigations:

* 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
Expand Down Expand Up @@ -3690,6 +3711,11 @@ Considering all of the above, we get the following interesting implications:
The patches in the below section "fence" the appropriate [[PERMISSIONS-POLICY]] and [[HTML]]
algorithms to achieve the outcomes described in the above explanatory content.

<h4 id=permissions-policy-definitions>Definitions</h4>

The <dfn>fenced frame allowed permissions</dfn> are either "`private-aggregation`",
"`shared-storage`", or "`shared-storage-select-url`".

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

<div id=allow-attribute-fenced-frame algorithm=allow-attribute-fenced-frame>
Expand All @@ -3711,47 +3737,56 @@ algorithms to achieve the outcomes described in the above explanatory content.
</div>

<div algorithm>
Create a new algorithm, called <dfn>Derive a permissions policy directly from a fenced frame
config instance</dfn>.

Given null or an [=element=] (|container|), this algorithm returns a new [=permissions policy=].
Create a new algorithm, called <dfn>Create a permissions policy for a fenced navigable</dfn>.

1. Let |inherited policy| be a new [=ordered map=].
Given a [=fenced navigable container=] (|container|) and an [=origin=] (|origin|), this algorithm
returns a new [=Permissions Policy=].

1. Let |effective permissions| be an empty [=list=].

1. Let |fenced frame config| be |container|'s [=Node/node document=]'s [=navigable/active browsing
1. Let |fencedFrameConfig| be |container|'s [=Node/node document=]'s [=navigable/active browsing
context=]'s [=browsing context/fenced frame config instance=].

1. If |fenced frame config| is not null, and |fenced frame config|'s [=fenced frame config/
effective enabled permissions=] are not null, set |effective permissions| to
|fenced frame config|'s [=fenced frame config/effective enabled permissions=].
1. Let |inheritedPolicy| be a new [=ordered map=].

1. If |fencedFrameConfig| is not null and |fencedFrameConfig|'s [=fenced frame config
instance/permissions policy behavior=] is [=permissions policy behavior/fixed=], then:

1. [=list/For each=] |feature| [=supported features|supported=]:
1. [=list/For each=] |feature| [=supported features|supported=]:

1. If |effective permissions| [=list/contains=] |feature|, then set |inherited policy|[feature]
to "`Enabled`".

Otherwise, set |inherited policy|[feature] to "`Disabled`".
1. If |fencedFrameConfig|'s [=fenced frame config instance/effective enabled permissions=]
[=list/contains=] |feature|, then set |inheritedPolicy|[feature] to "`Enabled`".

Otherwise, set |inheritedPolicy|[feature] to "`Disabled`".

Note: While this doesn't take the <{fencedframe/allow}> attribute into consideration, it
will have already been checked by the time this is called because of [=Should navigation
response to navigation request be blocked by Permissions Policy?=]. Any policy specified in
<{fencedframe/allow}> that is too restrictive would have cause the fenced frame to not load,
and any policy that is more permissive than what is specified in the [=fenced frame
config/effective enabled permissions=] will be ignored.

1. Otherwise:

1. [=list/For each=] |feature| [=supported features|supported=]:

1. If |feature| matches one of the [=fenced frame allowed permissions=], then set
|inheritedPolicy|[feature] to the result of running [$Define an inherited policy for
feature in container at origin$] given |feature|, |container|, and |origin|.

Otherwise, set |inheritedPolicy|[feature] to "`Disabled`".

1. Let |policy| be a new [=permissions policy=], with [=permissions policy/inherited policy=]
|inherited policy| and [=permissions policy/declared policy=] a new [=ordered map=].
|inheritedPolicy| and [=permissions policy/declared policy=] initialized to two new [=ordered
maps=].

1. Return |policy|.

Note: While this algorithm doesn't take the <{fencedframe/allow}> attribute into consideration, it
will have already been checked by the time this is called because of [=Should navigation response
to navigation request be blocked by Permissions Policy?=]. Any policy specified in
<{fencedframe/allow}> that is too restrictive would have cause the fenced frame to not load, and
any policy that is more permissive than what is specified in the [=fenced frame config/effective
enabled permissions=] will be ignored.
</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=].
|matchAll| that defaults to false, this algorithm returns a new [=permissions policy=].

Rewrite step 1 to read:

Expand All @@ -3763,7 +3798,7 @@ algorithms to achieve the outcomes described in the above explanatory content.
4. [=list/For each=] |feature| [=supported features|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|.
container at origin$] on |feature|, |container|, |origin|, and |matchAll|.

1. Set <var ignore>inherited policy</var>[|feature|] to |isInherited|.
</div>
Expand All @@ -3778,7 +3813,7 @@ algorithms to achieve the outcomes described in the above explanatory content.
Rewrite step 1 to read:

1. If |container| is a [=fenced navigable container=], then let |policy| be the result of running
[=derive a permissions policy directly from a fenced frame config instance=] given |container|.
[=create a permissions policy for a fenced navigable=] given |container| and |origin|.

Otherwise, Let |policy| be the result of running [$Create a Permissions Policy for a
navigable$] given |container| and |origin|.
Expand Down Expand Up @@ -3828,28 +3863,28 @@ algorithms to achieve the outcomes described in the above explanatory content.
has not yet been assigned to the browsing context. We should consider storing the instance
inside |navigationParams| and reference it from here instead.

1. Let |permissions policy| be the result of [$Create a Permissions Policy for a navigable|
1. Let |permissionsPolicy| 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 almost identical to the [=permissions policy=] that will be [=derive a
permissions policy directly from a fenced frame config instance|created=] when the navigation
constructs the ultimate {{Document}} for this pending navigation. The difference is that this
algorithm, just like when it is called on iframes, will include all of the permissions
specified in the <{fencedframe/allow}> attribute, even if that permission isn't specified in
the [=fenced frame config=]'s [=fenced frame config/effective enabled permissions=]. 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 and
unconditionally installed on the {{Document}}. However, the recreation will not include any
additional enabled permissions that are not included in the [=fenced frame config/effective
enabled permissions=], effectively locking the enabled permissions to only what is specified in
[=fenced frame config/effective enabled permissions=].

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

Note: This is almost identical to the [=permissions policy=] that will be [=create a
permissions policy for a fenced navigable|created=] when the navigation constructs the ultimate
{{Document}} for this pending navigation. The difference is that this algorithm, just like when
it is called on iframes, will include all of the permissions specified in the
<{fencedframe/allow}> attribute, even if that permission isn't specified in the [=fenced frame
config=]'s [=fenced frame config/effective enabled permissions=]. 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 and unconditionally installed
on the {{Document}}. However, the recreation will not include any additional enabled
permissions that are not included in the [=fenced frame config/effective enabled permissions=],
effectively locking the enabled permissions to only what is specified in [=fenced frame
config/effective enabled permissions=].

1. Let |inheritedPolicy| be |permissionsPolicy|'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. If |inheritedPolicy|[|effective permission|] is "Disabled", return "`Blocked`".

1. Return "`Allowed`."
</div>
Expand Down Expand Up @@ -3886,22 +3921,22 @@ algorithms to achieve the outcomes described in the above explanatory content.
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
document in that container (|origin|), and an optional [=boolean=] |matchAll| 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
|container|'s [=Node/node document=], |origin|, and |matchAll| 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
|matchAll| [=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
7. If |matchAll| 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>
Expand All @@ -3910,25 +3945,25 @@ algorithms to achieve the outcomes described in the above explanatory content.
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
an optional [=boolean=] |matchAll| 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 [=permissions policy/declared policy=],

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

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

1. Otherwise, return "`Disabled`".

Rewrite step 5 to read:

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

<div algorithm=declared-origin-patch>
Expand Down

0 comments on commit e6288c1

Please sign in to comment.