Skip to content

Commit c58393b

Browse files
committed
Define opaque-response blocking
This is good enough for early review, but there are a number of issues that still need resolving: https://github.com/annevk/orb/labels/mvp. There are also some inline TODO comments. A PR against HTML is needed to ensure it passes the appropriate metadata for media element and classic script requests. We might also want to depend on HTML for parsing JavaScript.
1 parent 9d17d81 commit c58393b

File tree

1 file changed

+305
-7
lines changed

1 file changed

+305
-7
lines changed

fetch.bs

Lines changed: 305 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ urlPrefix:https://w3c.github.io/hr-time/#;spec:hr-time
3737
urlPrefix:https://tc39.es/ecma262/#;type:dfn;spec:ecma-262
3838
url:realm;text:realm
3939
url:sec-list-and-record-specification-type;text:Record
40+
url:sec-parsetext;text:ParseText
41+
url:prod-Script;text:Script
42+
url:script-record;text:Script Record
4043
</pre>
4144

4245
<pre class=biblio>
@@ -1970,6 +1973,17 @@ Unless stated otherwise, it is false.
19701973

19711974
<p class=note>This flag is for exclusive use by HTML's render-blocking mechanism. [[!HTML]]
19721975

1976+
<p class=XXX>A <a for=/>request</a> has an associated
1977+
<dfn export for=request>no-cors media request state</dfn> ...
1978+
1979+
<p class=note>This is for exclusive use by the <a>opaque-response-safelist check</a>.
1980+
1981+
<p>A <a for=/>request</a> has an associated
1982+
<dfn for=request>no-cors JavaScript fallback encoding</dfn> (an <a for=/>encoding</a>). Unless
1983+
stated otherwise, it is <a for=/>UTF-8</a>.
1984+
1985+
<p class=note>This is for exclusive use by the <a>opaque-response-safelist check</a>.
1986+
19731987
<hr>
19741988

19751989
<p>A <a for=/>request</a> has an associated
@@ -3042,6 +3056,285 @@ run these steps:
30423056
</ol>
30433057

30443058

3059+
<h3 id=orb>Opaque-response blocking</h3>
3060+
3061+
<div class=note>
3062+
<p>Opaque-response blocking, also known as <abbr>ORB</abbr>, is a network filter that blocks access
3063+
to <a>opaque filtered responses</a>. These responses would likely would not have been useful to the
3064+
fetching party. Blocking them reduces information leakage to potential attackers.
3065+
3066+
<p>Essentially, CSS, JavaScript, images, and media (audio and video) can be requested across
3067+
origins without the <a>CORS protocol</a>. And unfortunately except for CSS there is no MIME type
3068+
enforcement. This algorithm aims to block as many responses as possible that are not one of these
3069+
types (or are newer variants of those types) to avoid leaking their contents through side channels.
3070+
3071+
<p>The network filter combines pro-active blocking based on response headers, sniffing a limited
3072+
set of bytes, and ultimately falls back to a full parse due to unfortunate (lack of) design
3073+
decisions in the early days of the web platform. As a result there are still quite a few responses
3074+
whose secrets can end up being revealed to attackers. Web developers are strongly encouraged to use
3075+
the `<code http-header>Cross-Origin-Resource-Policy</code>` response header to defend them.
3076+
</div>
3077+
3078+
3079+
<h4 id=orb-algorithm>The opaque-response-safelist check</h4>
3080+
3081+
<p>The <dfn>opaque-response-safelist check</dfn>, given a <a for=/>request</a> <var>request</var>
3082+
and a <a for=/>response</a> <var>response</var>, is to run these steps:
3083+
3084+
<ol>
3085+
<li><p>Let <var>mimeType</var> be the result of <a>extracting a MIME type</a> from
3086+
<var>response</var>'s <a for=response>header list</a>.
3087+
3088+
<li><p>Let <var>nosniff</var> be the result of <a>determining nosniff</a> given
3089+
<var>response</var>'s <a for=response>header list</a>.
3090+
3091+
<li>
3092+
<p>If <var>mimeType</var> is not failure, then:
3093+
3094+
<ol>
3095+
<li><p>If <var>mimeType</var> is an <a>opaque-response-safelisted MIME type</a>, then return
3096+
true.
3097+
3098+
<li><p>If <var>mimeType</var> is an <a>opaque-response-blocklisted-never-sniffed MIME type</a>,
3099+
then return false.
3100+
3101+
<li><p>If <var>response</var>'s <a for=response>status</a> is 206 and <var>mimeType</var> is an
3102+
<a>opaque-response-blocklisted MIME type</a>, then return false.
3103+
3104+
<li><p>If <var>nosniff</var> is true and <var>mimeType</var> is an
3105+
<a>opaque-response-blocklisted MIME type</a> or its <a for="MIME type">essence</a> is
3106+
"<code>text/plain</code>", then return false.
3107+
</ol>
3108+
3109+
<li><p>If <var>request</var>'s <a for=request>no-cors media request state</a> is
3110+
"<code>subsequent</code>", then return true.
3111+
3112+
<li><p>If <var>response</var>'s <a for=response>status</a> is 206 and
3113+
<a>validate a partial response</a> given 0 and <var>response</var> returns invalid, then return
3114+
false.
3115+
<!-- TODO Integrate https://wicg.github.io/background-fetch/#validate-a-partial-response into Fetch -->
3116+
3117+
<li><p>Let <var>bytes</var> be the result of running
3118+
<a>obtain a copy of the first 1024 bytes of response</a> given <var>response</var>.
3119+
3120+
<li><p>If <var>bytes</var> is failure, then return false.
3121+
3122+
<li>
3123+
<p>If the <a>audio or video type pattern matching algorithm</a> given <var>bytes</var> does not
3124+
return undefined, then:
3125+
3126+
<ol>
3127+
<li><p>If <var>requests</var>'s <a for=request>no-cors media request state</a> is not
3128+
"<code>initial</code>", then return false.
3129+
3130+
<li><p>If <var>response</var>'s <a for=response>status</a> is not 200 or 206, then return false.
3131+
3132+
<li><p>Return true.
3133+
</ol>
3134+
3135+
<li><p>If <var>requests</var>'s <a for=request>no-cors media request state</a> is not
3136+
"<code>N/A</code>", then return false.
3137+
3138+
<li><p>If the <a>image type pattern matching algorithm</a> given <var>bytes</var> does not return
3139+
undefined, then return true.
3140+
3141+
<li>
3142+
<p>If <var>nosniff</var> is true, then return false.
3143+
3144+
<p class=note>This check is made late as unfortunately images and media are always sniffed.
3145+
3146+
<li><p>If <var>response</var>'s <a for=response>status</a> is not an <a>ok status</a>, then return
3147+
false.
3148+
3149+
<li>
3150+
<p>If <var>mimeType</var> is failure, then return true.
3151+
3152+
<p class=note>This could be improved at somewhat significant cost. See
3153+
<a href=https://github.com/annevk/orb/issues/28>annevk/orb #28</a>.
3154+
3155+
<li><p>If <var>mimeType</var>'s <a for="MIME type">essence</a> <a for=string>starts with</a>
3156+
"<code>audio/</code>", "<code>image/</code>", or "<code>video/</code>", then return false.
3157+
3158+
<li><p>Return <a>determine if response is JavaScript and not JSON</a> given <var>response</var>.
3159+
</ol>
3160+
3161+
<hr>
3162+
3163+
<p>To <dfn>obtain a copy of the first 1024 bytes of response</dfn>, given a <a for=/>response</a>
3164+
<var>response</var>, run these steps:
3165+
3166+
<ol>
3167+
<li><p>Let <var>first1024Bytes</var> be null.
3168+
3169+
<li>
3170+
<p><a for=/>In parallel</a>:
3171+
3172+
<ol>
3173+
<li><p>Let <var>bytes</var> be the empty <a for=/>byte sequence</a>.
3174+
3175+
<li><p>Let <var>transformStream</var> be a new {{TransformStream}}.
3176+
3177+
<li>
3178+
<p>Let <var>transformAlgorithm</var> given a <var>chunk</var> be these steps:
3179+
3180+
<ol>
3181+
<li><p><a for=ReadableStream>Enqueue</a> <var>chunk</var> in <var>transformStream</var>.
3182+
3183+
<li>
3184+
<p>If <var>first1024Bytes</var> is null, then:
3185+
3186+
<ol>
3187+
<li><p>Let <var>chunkBytes</var> be
3188+
<a lt="get a copy of the bytes held by the buffer source">a copy of the bytes held by</a>
3189+
<var>chunk</var>.
3190+
3191+
<li><p>Append <var>chunkBytes</var> to <var>bytes</var>.
3192+
3193+
<li>
3194+
<p>If <var>bytes</var>'s <a for="byte sequencue">length</a> is greater than 1024, then:
3195+
3196+
<ol>
3197+
<li><p>Truncate <var>bytes</var> from the end so that it only contains 1024 bytes.
3198+
3199+
<li><p>Set <var>first1024Bytes</var> to <var>bytes</var>.
3200+
</ol>
3201+
</ol>
3202+
</ol>
3203+
3204+
<li><p>Let <var>flushAlgorithm</var> be this step: if <var>first1024Bytes</var> is null, then set
3205+
<var>first1024Bytes</var> to <var>bytes</var>.
3206+
3207+
<li><p><a for=TransformStream>Set up</a> <var>transformStream</var> with
3208+
<a for="TransformStream/set up"><i>transformAlgorithm</i></a> set to
3209+
<var>transformAlgorithm</var> and <a for="TransformStream/set up"><i>flushAlgorithm</i></a> set
3210+
to <var>flushAlgorithm</var>.
3211+
3212+
<li><p>Set <var>response</var>'s <a for=response>body</a>'s <a for=body>stream</a> to the result
3213+
of <var>response</var>'s <a for=response>body</a>'s <a for=body>stream</a>
3214+
<a for=TransformStream>piped through</a> <var>transformStream</var>.
3215+
</ol>
3216+
3217+
<li><p>Wait until <var>first1024Bytes</var> is non-null or <var>response</var>'s
3218+
<a for=response>body</a>'s <a for=body>stream</a> is <a for=ReadableStream>errored</a>.
3219+
3220+
<li><p>If <var>first1024Bytes</var> is null, then return failure.
3221+
3222+
<li>Return <var>first1024Bytes</var>.
3223+
</ol>
3224+
3225+
<hr>
3226+
3227+
<p>To <dfn>determine if response is JavaScript and not JSON</dfn> given a <a for=/>response</a>
3228+
<var>response</var>, run these steps:</p>
3229+
3230+
<ol>
3231+
<li><p>Let <var>responseBodyBytes</var> be null.
3232+
3233+
<li>
3234+
<p>Let <var>processBody</var> given a <a for=/>byte sequence</a> <var>bytes</var> be these steps:
3235+
3236+
<ol>
3237+
<li><p>Set <var>responseBodyBytes</var> to <var>bytes</var>.
3238+
3239+
<li><p>Set <var>response</var>'s <a for=response>body</a> to the <a for="body with type">body</a>
3240+
of the result of <a for=BodyInit>safely extracting</a> <var>bytes</var>.
3241+
</ol>
3242+
3243+
<li><p>Let <var>processBodyError</var> be this step: set <var>responseBodyBytes</var> to failure.
3244+
3245+
<li><p><a>Fully read</a> <var>response</var>'s <a for=response>body</a> given <a>processBody</a>
3246+
and <var>processBodyError</var>.
3247+
3248+
<li><p>Wait for <var>responseBodyBytes</var> to be non-null.
3249+
3250+
<li><p>If <var>responseBodyBytes</var> is failure, then return false.
3251+
3252+
<li><p><a for=/>Assert</a>: <var>responseBodyBytes</var> is a <a for=/>byte sequence</a>.
3253+
3254+
<li>
3255+
<p>If <a>parse JSON bytes to a JavaScript value</a> given <var>responseBodyBytes</var> does not
3256+
throw, then return false. If it throws, catch the exception and ignore it.
3257+
3258+
<p class=note>If there is an exception, <var>response</var> is not JSON. If there is not, it is.
3259+
3260+
<li><p>Let <var>potentialMIMETypeForEncoding</var> be the result of <a>extracting a MIME type</a>
3261+
given <var>response</var>'s <a for=response>header list</a>.
3262+
3263+
<li>
3264+
<p>Let <var>encoding</var> be the result of <a>legacy extracting an encoding</a> given
3265+
<var>potentialMIMETypeForEncoding</var> and <var>request</var>'s
3266+
<a for=request>no-cors JavaScript fallback encoding</a>.
3267+
3268+
<p class=note>Equivalently to <a>fetch a classic script</a>, this ignores the
3269+
<a for="MIME type" lt=essence>MIME type essence</a>.
3270+
3271+
<li><p>Let <var>sourceText</var> be the result of <a for=/>decoding</a>
3272+
<var>responseBodyBytes</var> given <var>encoding</var>.
3273+
3274+
<li><p>If <a>ParseText</a>(<var>sourceText</var>, <a>Script</a>) returns a <a>Script Record</a>,
3275+
then return true.
3276+
<!-- Ideally HTML owns this so ECMAScript changes don't end up impacting Fetch. We could
3277+
potentially make this use "create a classic script" instead with some mock data. Maybe that is
3278+
better? -->
3279+
3280+
<li><p>Return false.
3281+
</ol>
3282+
3283+
3284+
<h4 id=orb-mime-type-sets>New MIME type sets</h4>
3285+
3286+
<p class=note>The definitions in this section are solely for the purpose of abstracting parts of the
3287+
<a>opaque-response-safelist check</a>. They are not suited for usage elsewhere.
3288+
3289+
<p>An <dfn>opaque-response-safelisted MIME type</dfn> is a <a>JavaScript MIME type</a> or a
3290+
<a for=/>MIME type</a> whose <a for="MIME type">essence</a> is "<code>text/css</code>" or
3291+
"<code>image/svg+xml</code>".
3292+
3293+
<p>An <dfn>opaque-response-blocklisted MIME type</dfn> is an <a>HTML MIME type</a>,
3294+
<a>JSON MIME type</a>, or <a>XML MIME type</a>.
3295+
3296+
<p>An <dfn>opaque-response-blocklisted-never-sniffed MIME type</dfn> is a <a for=/>MIME type</a>
3297+
whose <a for="MIME type">essence</a> is one of:
3298+
3299+
<ul class=brief>
3300+
<li>"<code>application/gzip</code>"
3301+
<li>"<code>application/msexcel</code>"
3302+
<li>"<code>application/mspowerpoint</code>"
3303+
<li>"<code>application/msword</code>"
3304+
<li>"<code>application/msword-template</code>"
3305+
<li>"<code>application/pdf</code>"
3306+
<li>"<code>application/vnd.ces-quickpoint</code>"
3307+
<li>"<code>application/vnd.ces-quicksheet</code>"
3308+
<li>"<code>application/vnd.ces-quickword</code>"
3309+
<li>"<code>application/vnd.ms-excel</code>"
3310+
<li>"<code>application/vnd.ms-excel.sheet.macroenabled.12</code>"
3311+
<li>"<code>application/vnd.ms-powerpoint</code>"
3312+
<li>"<code>application/vnd.ms-powerpoint.presentation.macroenabled.12</code>"
3313+
<li>"<code>application/vnd.ms-word</code>"
3314+
<li>"<code>application/vnd.ms-word.document.12</code>"
3315+
<li>"<code>application/vnd.ms-word.document.macroenabled.12</code>"
3316+
<li>"<code>application/vnd.msword</code>"
3317+
<li>"<code>application/vnd.openxmlformats-officedocument.presentationml.presentation</code>"
3318+
<li>"<code>application/vnd.openxmlformats-officedocument.presentationml.template</code>"
3319+
<li>"<code>application/vnd.openxmlformats-officedocument.spreadsheetml.sheet</code>"
3320+
<li>"<code>application/vnd.openxmlformats-officedocument.spreadsheetml.template</code>"
3321+
<li>"<code>application/vnd.openxmlformats-officedocument.wordprocessingml.document</code>"
3322+
<li>"<code>application/vnd.openxmlformats-officedocument.wordprocessingml.template</code>"
3323+
<li>"<code>application/vnd.presentation-openxml</code>"
3324+
<li>"<code>application/vnd.presentation-openxmlm</code>"
3325+
<li>"<code>application/vnd.spreadsheet-openxml</code>"
3326+
<li>"<code>application/vnd.wordprocessing-openxml</code>"
3327+
<li>"<code>application/x-gzip</code>"
3328+
<li>"<code>application/x-protobuf</code>"
3329+
<li>"<code>application/x-protobuffer</code>"
3330+
<li>"<code>application/zip</code>"
3331+
<li>"<code>multipart/byteranges</code>"
3332+
<li>"<code>multipart/signed</code>"
3333+
<li>"<code>text/event-stream</code>"
3334+
<li>"<code>text/csv</code>"
3335+
</ul>
3336+
3337+
30453338

30463339
<h2 id=http-extensions>HTTP extensions</h2>
30473340

@@ -4846,19 +5139,23 @@ these steps:
48465139
<li><p>Set <var>response</var> and <var>actualResponse</var> to the result of running
48475140
<a>HTTP-network-or-cache fetch</a> given <var>fetchParams</var>.
48485141

4849-
<li>
4850-
<p>If <var>request</var>'s <a for=request>response tainting</a> is "<code>cors</code>" and a
4851-
<a>CORS check</a> for <var>request</var> and <var>response</var> returns failure, then return a
4852-
<a>network error</a>.
5142+
<li><p>If <var>request</var>'s <a for=request>response tainting</a> is "<code>opaque</code>",
5143+
<var>response</var>'s <a for=response>status</a> is not a <a>redirect status</a>, and the
5144+
<a>opaque-response-safelist check</a> given <var>request</var> and <var>response</var> returns
5145+
false, then return a <a>network error</a>.
48535146

4854-
<p class="note no-backref">As the <a>CORS check</a> is not to be applied to
4855-
<a for=/>responses</a> whose <a for=response>status</a> is 304 or 407, or <a for=/>responses</a>
4856-
from a service worker for that matter, it is applied here.
5147+
<li><p>If <var>request</var>'s <a for=request>response tainting</a> is "<code>cors</code>" and
5148+
the <a>CORS check</a> for <var>request</var> and <var>response</var> returns failure, then return
5149+
a <a>network error</a>.
48575150

48585151
<li><p>If the <a>TAO check</a> for <var>request</var> and <var>response</var> returns failure,
48595152
then set <var>request</var>'s <a for=request>timing allow failed flag</a>.
48605153
</ol>
48615154

5155+
<p class=note>As the <a>opaque-response-safelist check</a>, <a>CORS check</a>, and
5156+
<a>TAO check</a> are not to be applied to <a for=/>responses</a> whose <a for=response>status</a>
5157+
is 304 or 407, or to <a for=/>responses</a> from a service worker, they are applied here.
5158+
48625159
<li>
48635160
<p>If either <var>request</var>'s <a for=request>response tainting</a> or <var>response</var>'s
48645161
<a for=response>type</a> is "<code>opaque</code>", and the
@@ -8421,6 +8718,7 @@ Mohamed Zergaoui,
84218718
Mohammed Zubair Ahmed<!-- M-ZubairAhmed; GitHub -->,
84228719
Moritz Kneilmann,
84238720
Ms2ger,
8721+
Nathan Froyd,
84248722
Nico Schlömer,
84258723
Nicolás Peña Moreno,
84268724
Nidhi Jaju,

0 commit comments

Comments
 (0)