Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add subresource integrity support for ES modules, through importmaps #10269

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
213 changes: 185 additions & 28 deletions source
Expand Up @@ -2705,6 +2705,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute

<ul class="brief">
<li><dfn data-x-href="https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata">parse integrity metadata</dfn></li>
<li><dfn data-x-href="https://w3c.github.io/webappsec-subresource-integrity/#the-integrity-attribute">the requirements of the integrity attribute</dfn></li>
<li><dfn data-x-href="https://w3c.github.io/webappsec-subresource-integrity/#get-the-strongest-metadata">get the strongest metadata from set</dfn></li>
</ul>
</dd>
Expand Down Expand Up @@ -26982,6 +26983,10 @@ document.body.appendChild(wbr);</code></pre>
data-x="attr-link-integrity">integrity</code> attribute, if it is specified, or the empty string
otherwise.</p></li>

<li><p>If <var>el</var> does not have an <code data-x="attr-link-integrity">integrity</code>
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
attribute, then set <var>integrity metadata</var> to the result of <span>resolving a module
integrity metadata</span> with <var>url</var> and <var>settings object</var>.</p></li>

yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
<li><p>Let <var>referrer policy</var> be the current state of <var>el</var>'s <code
data-x="attr-link-referrerpolicy">referrerpolicy</code> attribute.</p></li>

Expand Down Expand Up @@ -62605,6 +62610,12 @@ o............A....e

<dt>"<code data-x="">module</code>"</dt>
<dd>
<p>If <var>el</var> does not have an <code data-x="attr-script-integrity">integrity</code>
attribute, then set <var>options</var>'s <span
data-x="concept-script-fetch-options-integrity">integrity metadata</span> to the result of
<span>resolving a module integrity metadata</span> with <var>url</var> and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be cleaner for this to branch on options integrity metadata being the empty string? I guess it's technically different. So either way we should probably test what integrity="" plus import map integrity does.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this way would be clearer to devs, but can be convinced either way.
It is tested in https://chromium-review.googlesource.com/c/chromium/src/+/5441822 (lines 55 and 110)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines 55 and 110 of which file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<var>settings object</var>.</p>

yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
<p><span>Fetch an external module script graph</span> given <var>url</var>, <var>settings
object</var>, <var>options</var>, and <var>onComplete</var>.</p>
</dd>
Expand Down Expand Up @@ -106242,12 +106253,51 @@ document.querySelector("button").addEventListener("click", bound);
<span data-x="concept-script-fetch-options-fetch-priority">fetch priority</span>.</p></dd>
</dl>

<p>For any given <span>script fetch options</span> <var>options</var>, the <dfn>descendant script
fetch options</dfn> are a new <span>script fetch options</span> whose <span data-x="struct
item">items</span> all have the same values, except for the <span
data-x="concept-script-fetch-options-integrity">integrity metadata</span>, which is instead the
empty string, and the <span data-x="concept-script-fetch-options-fetch-priority">fetch
priority</span>, which is instead "<code data-x="">auto</code>".</p>
<p>To <dfn>get the descendant script fetch options</dfn> given a <span>script fetch options</span>
<var>originalOptions</var>, a <span>URL</span> <var>url</var>, and an <span>environment settings
object</span> <var>settingsObject</var>:</p>

<ol>
<li><p>Let <var>newOptions</var> be a copy of <var>originalOptions</var>.</p></li>

<li><p>Let <var>integrity</var> be the empty string.</p></li>

<li><p>If <var>settingsObject</var>'s <span
data-x="concept-settings-object-global">global object</span> is a <code>Window</code> object,
then set <var>integrity</var> to the result of <span>resolving a module integrity metadata</span>
with <var>url</var> and <var>settingsObject</var>.</p></li>

<li><p>Set <var>newOptions</var>'s <span
data-x="concept-script-fetch-options-integrity">integrity metadata</span> to
<var>integrity</var>.</p></li>

<li><p>Set <var>newOptions</var>'s <span
data-x="concept-script-fetch-options-fetch-priority">fetch priority</span> to "<code
data-x="">auto</code>".</p></li>

<li><p>Return <var>newOptions</var>.</p></li>
</ol>
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved

yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
<p>To <dfn data-x="resolving a module integrity metadata">resolve a module
integrity metadata</dfn>, given a <span>URL</span> <var>url</var> and an <span>environment
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
settings object</span> <var>settingsObject</var>:</p>
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved

<ol>
<li><p><span>Assert</span>: <var>settingsObject</var>'s <span
data-x="concept-settings-object-global">global object</span> is a <code>Window</code>
object.</p></li>

yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
<li><p>Let <var>map</var> be <var>settingsObject</var>'s <span
data-x="concept-settings-object-global">global object</span>'s <span
data-x="concept-window-import-map">import map</span>.</p></li>

<li><p>If <var>map</var>'s <span
data-x="concept-import-map-integrity">integrity</span>[<var>url</var>] does not <span
data-x="map exists">exist</span>, then return the empty string.</p></li>

<li><p>Return <var>map</var>'s <span
data-x="concept-import-map-integrity">integrity</span>[<var>url</var>].</p></li>
</ol>

<hr>

Expand Down Expand Up @@ -106536,7 +106586,7 @@ document.querySelector("button").addEventListener("click", bound);
</ol>

<p>To <dfn export id="fetch-a-module-script-tree">fetch an external module script graph</dfn>
given a <var>URL</var> <var>url</var>, an <span>environment settings object</span>
given a <span>URL</span> <var>url</var>, an <span>environment settings object</span>
<var>settingsObject</var>, a <span>script fetch options</span> <var>options</var>, and an
algorithm <var>onComplete</var>, run these steps. <var>onComplete</var> must be an algorithm
accepting null (on failure) or a <span>module script</span> (on success).</p>
Expand Down Expand Up @@ -108331,6 +108381,29 @@ dictionary <dfn dictionary>PromiseRejectionEventInit</dfn> : <span>EventInit</sp
</table>
</div>

<div class="example" id="example-import-map-integrity">
<p>Import maps can also be used to provide modules with integrity metadata to be used in
<cite>Subresource Integrity</cite> checks. <ref>SRI</ref>
</p>

<p>The following import map illustrates this:</p>

<pre><code class="json" data-x="">{
"imports": {
"a": "/a-1.mjs",
"b": "/b-1.mjs",
"c": "/c-1.mjs"
},
"integrity": {
"/a-1.mjs": "sha384-Li9vy3DqF8tnTXuiaAJuML3ky+er10rcgNR/VqsVpcw+ThHmYcwiB1pbOxEbzJr7",
"/d-1.mjs": "sha384-MBO5IDfYaE6c6Aao94oZrIOiC6CGiSN2n4QUbHNPhzk5Xhm0djZLQqTpL0HzTUxk"
}
}</code></pre>
<p>The above example provides integrity metadata to be enforced on the modules <code
data-x="">/a-1.mjs</code> and <code data-x="">/d-1.mjs</code>, even if the latter is not defined
as an import in the map.</p>
</div>

<hr>

<p>The <span>child text content</span> of a <code>script</code> element representing an
Expand All @@ -108340,11 +108413,13 @@ dictionary <dfn dictionary>PromiseRejectionEventInit</dfn> : <span>EventInit</sp
<ul>
<li><p>It must be valid JSON. <ref>JSON</ref></p></li>

<li><p>The JSON must represent a JSON object, with at most the two keys "<code
data-x="">imports</code>" and "<code data-x="">scopes</code>".</p></li>
<li><p>The JSON must represent a JSON object, with at most the three keys "<code
data-x="">imports</code>", "<code data-x="">scopes</code>", and "<code
data-x="">integrity</code>".</p></li>

<li><p>The values corresponding to the "<code data-x="">imports</code>" and "<code
data-x="">scopes</code>" keys, if present, must themselves be JSON objects.</p></li>
<li><p>The values corresponding to the "<code data-x="">imports</code>", "<code
data-x="">scopes</code>", and "<code data-x="">integrity</code>" keys, if present,
must themselves be JSON objects.</p></li>

<li><p>The value corresponding to the "<code data-x="">imports</code>" key, if present, must be
a <span>valid module specifier map</span>.</p></li>
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -108353,6 +108428,10 @@ dictionary <dfn dictionary>PromiseRejectionEventInit</dfn> : <span>EventInit</sp
JSON object, whose keys are <span data-x="valid URL string">valid URL strings</span> and whose
values are <span data-x="valid module specifier map">valid module specifier
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
maps</span>.</p></li>

<li><p>The value corresponding to the "<code data-x="">integrity</code>" key, if present, must
be a JSON object, whose keys are <span data-x="valid URL string">valid URL strings</span> and
whose values fit <span>the requirements of the integrity attribute</span>.</p></li>
</ul>

<p>A <dfn>valid module specifier map</dfn> is a JSON object that meets the following
Expand All @@ -108376,22 +108455,30 @@ dictionary <dfn dictionary>PromiseRejectionEventInit</dfn> : <span>EventInit</sp

<h5>Import map processing model</h5>

<p>Formally, an <dfn>import map</dfn> is a <span>struct</span> with two <span data-x="struct
<p>Formally, an <dfn>import map</dfn> is a <span>struct</span> with three <span data-x="struct
item">items</span>:</p>

<ul>
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
<li><p><dfn data-x="concept-import-map-imports">imports</dfn>, a <span>module specifier
map</span>; and</p></li>
map</span>;</p></li>

<li><p><dfn data-x="concept-import-map-scopes">scopes</dfn>, an <span>ordered map</span> of
<span data-x="URL">URLs</span> to <span data-x="module specifier map">module specifier
maps</span>.</p>
maps</span>; and</p></li>

<li><p><dfn data-x="concept-import-map-integrity">integrity</dfn>, a <span>module integrity
map</span>.</p></li>
</ul>

<p>A <dfn>module specifier map</dfn> is an <span>ordered map</span> whose <span data-x="map
key">keys</span> are <span data-x="string">strings</span> and whose <span data-x="map
value">values</span> are either <span data-x="URL">URLs</span> or nulls.</p>

<p>A <dfn>module integrity map</dfn> is an <span>ordered map</span> whose <span data-x="map
key">keys</span> are <span data-x="URL">URLs</span> and whose <span data-x="map
value">values</span> are <span data-x="string">strings</span> that will be used as <span
data-x="concept-request-integrity-metadata">integrity metadata</span>.</p>

<p>An <dfn>empty import map</dfn> is an <span>import map</span> with its <span
data-x="concept-import-map-imports">imports</span> and <span
data-x="concept-import-map-scopes">scopes</span> both being empty maps.</p>
Expand Down Expand Up @@ -108465,20 +108552,40 @@ dictionary <dfn dictionary>PromiseRejectionEventInit</dfn> : <span>EventInit</sp
</ol>
</li>

<li><p>Let <var>normalizedIntegrity</var> be an empty <span>ordered map</span>.</p></li>

<li>
<p>If <var>parsed</var>["<code data-x="">integrity</code>"] <span data-x="map
exists">exists</span>, then:</p>

<ol>
<li><p>If <var>parsed</var>["<code data-x="">integrity</code>"] is not an <span>ordered
map</span>, then throw a <code>TypeError</code> indicating that the value for the "<code
data-x="">integrity</code>" top-level key needs to be a JSON object.</p></li>

<li><p>Set <var>normalizedIntegrity</var> to the result of <span>normalizing a module
integrity map</span> given <var>parsed</var>["<code data-x="">integrity</code>"] and
<var>baseURL</var>.</p></li>
</ol>
</li>

<li>
<p>If <var>parsed</var>'s <span data-x="map key">keys</span> <span data-x="list
contains">contains</span> any items besides "<code data-x="">imports</code>" or "<code
data-x="">scopes</code>", then the user agent should <span>report a warning to the
console</span> indicating that an invalid top-level key was present in the import map.</p>
contains">contains</span> any items besides "<code data-x="">imports</code>", "<code
data-x="">scopes</code>", or "<code data-x="">integrity</code>", then the user agent should
<span>report a warning to the console</span> indicating that an invalid top-level key was
present in the import map.</p>

<p class="note">This can help detect typos. It is not an error, because that would prevent any
future extensions from being added backward-compatibly.</p>
</li>

yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
<li><p>Return an <span>import map</span> whose <span
data-x="concept-import-map-imports">imports</span> are <var>sortedAndNormalizedImports</var> and
data-x="concept-import-map-imports">imports</span> are <var>sortedAndNormalizedImports</var>,
whose <span data-x="concept-import-map-scopes">scopes</span> are
<var>sortedAndNormalizedScopes</var>.</p></li>
<var>sortedAndNormalizedScopes</var>, and whose <span
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
data-x="concept-import-map-integrity">integrity</span> are
<var>normalizedIntegrity</var>.</p></li>
</ol>

<div class="example" id="example-import-map-normalization">
Expand Down Expand Up @@ -108627,6 +108734,57 @@ dictionary <dfn dictionary>PromiseRejectionEventInit</dfn> : <span>EventInit</sp
turn gives "<code data-x="">foo/bar/</code>" a higher priority than "<code data-x="">foo/</code>"
during <span data-x="resolve a module specifier">module specifier resolution</span>.</p>

<p>To <dfn data-x="normalizing a module integrity map">normalize a module integrity map</dfn>,
given an <span>ordered map</span> <var>originalMap</var>:</p>

<ol>
<li><p>Let <var>normalized</var> be an empty <span>ordered map</span>.</p></li>

<li>
<p><span data-x="list iterate">For each</span> <var>key</var> → <var>value</var> of
<var>originalMap</var>:</p>

<ol>
<li>
<p>Let <var>resolvedURL</var> be the result of <span>resolving a URL-like module
specifier</span> given <var>key</var> and <var>baseURL</var>.</p>

<p class="note">Unlike "<code data-x="">imports</code>", keys of the integrity map are treated
as URLs, not module specifiers. However, we use the <span data-x="resolving a URL-like module
specifier">resolve a URL-like module specifier</span> algorithm to prohibit "bare" relative
URLs like <code data-x="">foo</code>, which could be mistaken for module specifiers.</p>
</li>

<li>
<p>If <var>resolvedURL</var> is null, then:</p>

yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
<ol>
<li><p>The user agent may <span>report a warning to the console</span> indicating that
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
the key failed to resolve.</p></li>

<li><p><span>Continue</span>.</p></li>
</ol>
</li>

<li>
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
<p>If <var>value</var> is not a <span>string</span>, then:</p>
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved

<ol>
<li><p>The user agent may <span>report a warning to the console</span> indicating that
<span data-x="concept-request-integrity-metadata">integrity metadata</span> values need to
be <span data-x="string">strings</span>.</p></li>
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved

<li><p><span>Continue</span>.</p></li>
</ol>
</li>

<li><p>Set <var>normalized</var>[<var>resolvedURL</var>] to <var>value</var>.</p></li>
</ol>
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
</li>

<li><p>Return <var>normalized</var>.</p></li>
</ol>

<p>To <dfn data-x="normalizing a specifier key">normalize a specifier key</dfn>, given a <span>string</span> <var>specifierKey</var> and a <span>URL</span> <var>baseURL</var>:</p>

<ol>
Expand Down Expand Up @@ -109258,7 +109416,7 @@ import "https://example.com/foo/../module2.mjs";</code></pre>

<li><p>Let <var>referencingScript</var> be null.</p></li>

<li><p>Let <var>fetchOptions</var> be the <span>default classic script fetch
<li><p>Let <var>originalFetchOptions</var> be the <span>default classic script fetch
options</span>.</p></li>

<li><p>Let <var>fetchReferrer</var> be "<code data-x="">client</code>".</p></li>
Expand All @@ -109273,16 +109431,11 @@ import "https://example.com/foo/../module2.mjs";</code></pre>
<li><p>Set <var>settingsObject</var> to <var>referencingScript</var>'s <span
data-x="concept-script-settings-object">settings object</span>.</p></li>

<li><p>Set <var>fetchOptions</var> to the new <span>descendant script fetch options</span> for
<var>referencingScript</var>'s <span data-x="concept-script-script-fetch-options">fetch
options</span>.</p></li>

<li><p><span>Assert</span>: <var>fetchOptions</var> is not null, as
<var>referencingScript</var> is a <span>classic script</span> or a <span>JavaScript module
script</span>.</p></li>

<li><p>Set <var>fetchReferrer</var> to <var>referencingScript</var>'s <span
data-x="concept-script-base-url">base URL</span>.</p></li>

<li><p>Set <var>originalFetchOptions</var> to <var>referencingScript</var>'s <span
data-x="concept-script-script-fetch-options">fetch options</span>.</p></li>
</ol>

<div class="example">
Expand Down Expand Up @@ -109321,6 +109474,10 @@ import "https://example.com/foo/../module2.mjs";</code></pre>
</ol>
</li>

<li><p>Let <var>fetchOptions</var> be the result of <span data-x="get the descendant script
fetch options">getting the descendant script fetch options</span> given
<var>originalFetchOptions</var>, <var>settingsObject</var>, and <var>url</var>.</p></li>

yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
<li><p>Let <var>destination</var> be <code data-x="">"script"</code>.</p></li>

<li><p>Let <var>fetchClient</var> be <var>settingsObject</var>.</p></li>
Expand Down