diff --git a/index.bs b/index.bs index aca382d6f7..636595db68 100644 --- a/index.bs +++ b/index.bs @@ -392,7 +392,12 @@ spec:SRI; urlPrefix: https://w3c.github.io/webappsec-subresource-integrity metadata which is listed in the current policy. Details in [[#external-hash]]. - 11. Reports generated for inline violations will contain a sample + 11. Hash-based source expressions may now match scripts based on their URL hashes. + + 12. Hash-based source expressions may now allow `eval()` execution based on the hash of + the content of the eval. + + 13. Reports generated for inline violations will contain a sample attribute if the relevant directive contains the `'report-sample'` expression. @@ -692,7 +697,7 @@ spec:SRI; urlPrefix: https://w3c.github.io/webappsec-subresource-integrity ; Keywords: keyword-source = "'self'" / "'unsafe-inline'" / "'unsafe-eval'" - / "'strict-dynamic'" / "'unsafe-hashes'" + / "'strict-dynamic'" / "'strict-dynamic-url'" / "'unsafe-hashes'" / "'report-sample'" / "'unsafe-allow-redirects'" / "'wasm-unsafe-eval'" / "'trusted-types-eval'" / "'report-sha256'" / "'report-sha384'" @@ -705,6 +710,8 @@ spec:SRI; urlPrefix: https://w3c.github.io/webappsec-subresource-integrity base64-value = 1*( ALPHA / DIGIT / "+" / "/" / "-" / "_" )*2( "=" ) ; Digests: 'sha256-[digest goes here]' + url-hash-source = "'url-" hash-algorithm "-" base64-value "'" + eval-hash-source = "'eval-" hash-algorithm "-" base64-value "'" hash-source = "'" hash-algorithm "-" base64-value "'" hash-algorithm = "sha256" / "sha384" / "sha512" @@ -1546,8 +1553,13 @@ spec:SRI; urlPrefix: https://w3c.github.io/webappsec-subresource-integrity [=ASCII case-insensitive=] match for the string "`'trusted-types-eval'`", then skip the following steps. - 1. If |source-list| contains a [=source expression=] which is an [=ASCII case-insensitive=] match for the - string "`'unsafe-eval'`", then skip the following steps. + 1. If the result of executing [[#match-eval-hash-to-source-list]] on |source-list| and |sourceString| + is "`Matches`", then skip the following steps. + + 1. If |source-list| does not contain a [=source expression=] that is a match for the + "`'eval-hash-source'`" grammar and |source-list| contains a [=source expression=] which is an + [=ASCII case-insensitive=] match for the string "`'unsafe-eval'`", then skip the + following steps. 1. Let |violation| be the result of executing [[#create-violation-for-global]] on |global|, |policy|, and "`script-src`". @@ -1813,13 +1825,23 @@ Content-Type: application/reports+json 1. If |url|'s scheme is not an HTTP(S) scheme, then return |url|'s scheme. + 2. Let |result| be the result of executing [[#strip-https-url]] on |url|. + + 3. Return the result of executing the URL serializer on |result|. + +

Strip HTTP(S) URL

+ Given a [=/URL=] |url|, this algorithm returns the URL with potentially + sensitive fields blanked. + + 1. Assert: |url|'s scheme is an HTTP(S) scheme. + 2. Set |url|’s fragment to the empty string. 3. Set |url|’s username to the empty string. 4. Set |url|’s password to the empty string. - 5. Return the result of executing the URL serializer on |url|. + 5. Return |url|.

Report a |violation| @@ -3771,7 +3793,11 @@ Content-Type: application/reports+json for="request">integrity metadata and this directive's value is "`Matches`", return "`Allowed`". - 3. If |directive|'s value contains a source + 3. If the result of executing [[#match-url-hash-to-source-list]] on + this directive's value and |request| is + "`Matches`", return "`Allowed`". + + 4. If |directive|'s value contains a source expression that is an ASCII case-insensitive match for the "`'strict-dynamic'`" keyword-source: @@ -3783,9 +3809,17 @@ Content-Type: application/reports+json Note: "`'strict-dynamic'`" is explained in more detail in [[#strict-dynamic-usage]]. - 4. If the result of executing [[#match-request-to-source-list]] on - |request|, |directive|'s value, and |policy|, - is "`Does Not Match`", return "`Blocked`". + 5. If |directive|'s value does not contain a + source expression that is a match for the + "`url-hash-source`" grammar: + + 1. If the result of executing [[#match-request-to-source-list]] on + |request|, |directive|'s value, and + |policy|, is "`Does Not Match`", return "`Blocked`". + + Note: "`'strict-dynamic-url'`" doesn't ignore `host-source` + and `scheme-source` expressions, unlike "`'strict-dynamic'`" + and "`url-hash-source`". 2. Return "`Allowed`". @@ -3819,7 +3853,8 @@ Content-Type: application/reports+json 1. If |directive|'s value contains a source expression that is an ASCII case-insensitive match for - the "`'strict-dynamic'`" keyword-source: + the "`'strict-dynamic'`" or + "`'strict-dynamic-url'`" keyword-sources: 1. If the |request|'s parser metadata is "parser-inserted", return "`Blocked`". @@ -4262,6 +4297,90 @@ Content-Type: application/reports+json 9. Return "`Matches`". +

+ Get relative URL string +
+ + Given a {{URL}} |resourceURL| and a {{URL}} |documentURL|, + this algorithm returns a {{String}} that represents the relative path of + |resourceURL| according to |documentURL| if they are same origin. + + 1. If |documentURL|'s scheme is not + `http` or `https`, return empty string. + + 2. If |documentURL|'s origin is not equal to + |resourceURL|'s origin, return empty string. + + 3. If |resourceURL|'s path is empty, return empty string. + + 4. Let |documentURLPath| and |resourceURLPath| be the result of running the + URL path serializer on |documentURL| and |resourceURL| respectively. + + 5. FIXME(properly define): Remove the part of |documentURLPath| after the last + U+002F SOLIDUS character (`/`). + + 6. Let |documentURLTokens| and |resourceURLPathTokens| be the result of + strictly splitting |documentURLPath| + and |resourceURLPath| respectively on the U+002F SOLIDUS character (`/`). + + 7. Let |minLen| be the smaller of |documentURLPathTokens|' [=list/size=] + and |resourceURLPathTokens|' [=list/size=]. + + 8. Let |commonPrefixLen| be zero. + + 9. [=list/iterate|For each=] |index| of [=the exclusive range=] 0 to |minLen|: + + 1. If |documentURLPathTokens|[|index|] is equal to + |resourceURLPathTokens|[|index|], increment |commonPrefixLen|. + + 10. Let |levelDifference| be |documentURLPathTokens|' + [=list/size=] - |commonPrefixLen|. + + 11. Let |resultTokens| be an empty [=list=]. + + 12. [=list/iterate|For each=] |index| of [=the exclusive range=] 0 to |levelDifference|: + + 1. [=list/append|Append=] ".." to |resultTokens|. + + 13. [=list/iterate|For each=] |index| of [=the exclusive range=] |commonPrefixLen| + 1 to |resourceURLPathTokens|' [=list/size=]: + + 1. [=list/append|Append=] |resourceURLPathTokens|[|index|] to |resultTokens|. + + 14. Let |result| be an empty string. + + 15. [=list/iterate|For each=] |index| of [=the exclusive range=] 0 to |resultTokens|' [=list/size=] - 1: + + 1. Append |resultTokens|[|index|] to |result|. + + 2. Append U+002F SOLIDUS character (`/`) to |result|. + + 16. Append the last element in |resultTokens| to |result|. + + 17. Let |query| be |resourceURL|'s query. + + 18. If |query| is a non-empty string: + + 1. Append U+003F (?) to |result|. + 2. Append |query| to |result|. + + 19. Return |result|. + + Note: |result| will never contain a fragment. + +
+ Relative URL string for |resourceURL|="https://example.com/script.js?query" and + |documentURL|="https://example.com" is "script.js?query". + + Relative URL string for |resourceURL|="https://example.com/script.js?query" and + |documentURL|="https://example.com/index.html" is "script.js?query". + + Relative URL string for |resourceURL|="https://example.com/script.js?query" and + |documentURL|="https://example.com/abc/index.html" is "../script.js?query". + + Relative URL string for |resourceURL|="https://example.com/abc/script.js?query" and + |documentURL|="https://example.com/index.html" is "abc/script.js?query". +
+

Element Matching Algorithms

@@ -4325,10 +4444,12 @@ Content-Type: application/reports+json 2. If |type| is "`script`", "`script attribute`" or "`navigation`" and |expression| matches the keyword-source - "`'strict-dynamic'`", return "`Does Not Allow`". + "`'strict-dynamic'`" or + "`'strict-dynamic-url'`", return "`Does Not Allow`". - Note: `'strict-dynamic'` only applies to scripts, not other resource - types. Usage is explained in more detail in [[#strict-dynamic-usage]]. + Note: `'strict-dynamic'` and `'strict-dynamic-url'` only apply to + scripts, not other resource types. Usage is explained in more detail + in [[#strict-dynamic-usage]]. 3. If |expression| is an ASCII case-insensitive match for the `keyword-source` "`'unsafe-inline'`", @@ -4358,13 +4479,17 @@ Content-Type: application/reports+json Source lists that do not allow all inline behavior when |type| is '`script`' or '`script attribute`' due to the presence of - '`strict-dynamic`', but allow all inline behavior + '`strict-dynamic`' or '`strict-dynamic-url`', but + allow all inline behavior otherwise:
       'unsafe-inline' 'strict-dynamic'
+      'unsafe-inline' 'strict-dynamic-url'
+
       http://example.com 'strict-dynamic' 'unsafe-inline'
     
+
@@ -4404,53 +4529,132 @@ Content-Type: application/reports+json set |unsafe-hashes flag| to `true`. Break out of the loop. 5. If |type| is "`script`" or "`style`", or |unsafe-hashes flag| is - `true`: + `true`, return result of executing [[#match-value-hash-to-source-list]] on + |source|, "`content`" and |list|. - 1. Set |source| to the result of executing UTF-8 encode - on the result of executing JavaScript string converting - on |source|. + Note: Hashes apply to inline <{script}> and inline <{style}>. If the + "`'unsafe-hashes'`" source expression is present, + they will also apply to event handlers, style attributes and `javascript:` + navigations. - 2. For each |expression| of |list|: + ISSUE(w3c/webappsec-csp#426): This should handle `'strict-dynamic'` for dynamically inserted inline scripts. - 1. If |expression| matches the `hash-source` grammar: + 6. Return "`Does Not Match`". - 1. Let |algorithm| be null. +
+ Does the hash of |request|'s URL match |source list|? +
- 2. If |expression|'s `hash-algorithm` part is an - ASCII case-insensitive match for "sha256", set - |algorithm| to SHA-256. + Given a source list |source list| and request |request|, + this algorithm returns "`Matches`" or "`Does Not Match`". It can only + return "`Matches`" for requests and documents with HTTP(S) schemes. - 3. If |expression|'s `hash-algorithm` part is an - ASCII case-insensitive match for "sha384", set - |algorithm| to SHA-384. + 1. If |request|'s url's scheme is not + an HTTP(S) scheme, then return "`Does Not Match`". - 4. If |expression|'s `hash-algorithm` part is an - ASCII case-insensitive match for "sha512", set - |algorithm| to SHA-512. + 2. Let |requestURL| be the result of executing [[#strip-https-url]] on + |request|'s url. - 5. If |algorithm| is not null: + 3. If the result of executing [[#match-value-hash-to-source-list]] on + |requestURL|, "`url-hash`", and |source list| is "`Matches`", return + "`Matches`". - 1. Let |actual| be the result of base64 encoding the - result of applying |algorithm| to |source|. + 4. Let |global| be the |request|'s [=request/client=]'s [=/global object=]. - 2. Let |expected| be |expression|'s `base64-value` part, - with all '`-`' characters replaced with '`+`', and all '`_`' characters - replaced with '`/`'. + 5. If |global| is not a {{Window}}, return "`Does Not Match`". - Note: This replacement normalizes hashes expressed in [=base64url - encoding=] into [=base64 encoding=] for matching. + 6. If |global|'s [=associated document|document=]'s [=Document/URL=]'s + scheme is not an HTTP(S) scheme, then return + "`Does Not Match`". - 3. If |actual| is identical to - |expected|, return "`Matches`". + 7. Let |documentURL| to be the result of executing [[#strip-https-url]] + on |global|'s [=associated document|document=]'s [=Document/URL=]. - Note: Hashes apply to inline <{script}> and inline <{style}>. If the - "`'unsafe-hashes'`" source expression is present, - they will also apply to event handlers, style attributes and `javascript:` - navigations. + 8. Let |relativeURL| be the result of executing [[#get-relative-url-string]] + on |requestURL| and |documentURL|. - ISSUE(w3c/webappsec-csp#426): This should handle `'strict-dynamic'` for dynamically inserted inline scripts. + 9. If |relativeURL| is empty string, return "`Does Not Match`". - 6. Return "`Does Not Match`". + 10. Return the result of executing + [[#match-value-hash-to-source-list]] on |relativeURL|, "`url-hash`", and + |source list|. + + +
+ Does the hash of |sourceString| match |source list|? +
+ + Given a source list |source list| and a source string |sourceString|, + this algorithm returns "`Matches`" or "`Does Not Match`". + + 1. Return the result of executing + [[#match-value-hash-to-source-list]] on |sourceString|, "`eval-hash`", + and |source list|. + +
+ Is |expression| of |hash type|? +
+ + Given a {{String}} |hash type| and a source-expression + |expression|, this algorithm returns "`Yes`" or "`No`". + + 1. If |hash type| is "url-hash" and |expression| matches the + `url-hash-source` grammar, return "`Yes`". + + 2. If |hash type| is "eval-hash" and |expression| matches the + `eval-hash-source` grammar, return "`Yes`". + + 3. If |expression| matches the `hash-source` grammar, + return "`Yes`". + + 4. Return "`No`". + +
+ Does the hash of |value| match |source list|? +
+ + Given a {{String}} |value|, {{String}} |hash type|, and a source list + |source list| type , this algorithm returns "`Matches`" or "`Does Not Match`". + + 1. Set |value| to the result of executing UTF-8 encode + on the result of executing JavaScript string converting + on |value|. + + 2. For each |expression| of |source list|: + + 1. If the result of executing [[#is-expression-of-hash-type]] with + |hash type| and |expression| is "`Yes`": + + 1. Let |algorithm| be null. + + 2. If |expression|'s `hash-algorithm` part is an + ASCII case-insensitive match for "sha256", set + |algorithm| to SHA-256. + + 3. If |expression|'s `hash-algorithm` part is an + ASCII case-insensitive match for "sha384", set + |algorithm| to SHA-384. + + 4. If |expression|'s `hash-algorithm` part is an + ASCII case-insensitive match for "sha512", set + |algorithm| to SHA-512. + + 5. If |algorithm| is not null: + + 1. Let |actual| be the result of base64 encoding the + result of applying |algorithm| to |value|. + + 2. Let |expected| be |expression|'s `base64-value` part, + with all '`-`' characters replaced with '`+`', and all '`_`' characters + replaced with '`/`'. + + Note: This replacement normalizes hashes expressed in [=base64url + encoding=] into [=base64 encoding=] for matching. + + 3. If |actual| is identical to + |expected|, return "`Matches`". + + 3. Return "`Does Not Match`".

Directive Algorithms

@@ -5006,6 +5210,60 @@ Content-Type: application/reports+json untrusted data. This includes applications or frameworks that tend to determine script locations at runtime. + Similarly, the "`'strict-dynamic-url'`" source expression allows + you to deploy a policy based on "`url-hash-source`"s in a + backwards compatible way. + + If present in a `script-src` or `default-src` directive, + "`'strict-dynamic-url'`" has two main effects: + + 1. "`'unsafe-inline'`" keyword-source will be + ignored when loading script. + + 2. Script requests which are triggered by non-"parser-inserted" + <{script}> elements are allowed. + + "`'strict-dynamic-url'`" doesn't ignore + host-source, scheme-source and + `'self'`. However, `url-hash-source`s ignore + these expressions. + + This allows you to deploy `url-hash-source`s in a + backwards compatible way, without requiring user-agent sniffing. + +
+ Suppose MegaCorp, Inc. presently deploys the following lax policy: + +
+      Content-Security-Policy: script-src https: 'unsafe-inline'
+    
+ + And serves the following HTML with that policy active: + +
+      ...
+      <script src="https://example.com/script.js" ></script>
+      ...
+    
+ + MegaCorp, Inc. now wants to deploy a more strict policy using `url-hash-source`s: + +
+      Content-Security-Policy: script-src https: 'unsafe-inline' 'strict-dynamic-url' 'url-hash-EAaArVRs5qV39C9S3zO0z9ynVoWeZkuNfeMpsVDQnOk='
+    
+ + User agents that understand `url-hash-source`s will allow + the script and any non-parser inserted scripts it loads. + + User agents that don't understand `url-hash-source`s will + see the policy as "`https: 'unsafe-inline'`". This lax policy will also + allow the script to be loaded. + + Note: This policy can't use "`'strict-dynamic'`" because + older user agents that need to receive a lax fallback policy will ignore the + `https:` source expression due to "`'strict-dynamic'`". +
+

Usage of "`'unsafe-hashes'`"