streams
streams copied to clipboard
Possible feature discussion: opaque streams
Opaque streams are something we might wish to add in future. An opaque stream is a stream you can't read from, but you can pass it off to APIs that can.
With opaque streams, instead of response.body returning null for an opaque response, it would return an opaque stream.
Currently:
const response = await fetch(url, {mode: 'no-cors'});
const body = response.body; // "body" is null
But with opaque streams:
const response = await fetch(url, {mode: 'no-cors'});
const body = response.body; // "body" is non-null!
const reader = body.getReader(); // throws!
So far we haven't gained anything, but we can now pass our stream off to someone who can use it, eg.
postMessage(body, origin, [body]);
If origin has access to url, they can get a reader on body and read from it as usual.
The goal is that any platform API that produces or consumes opaque data will be composable via streams.
An opaque stream can also be thought of as a stream with an ACL attached to it.
The exact mechanism by which some contexts and APIs are authorised to read/write/transform opaque streams is TBD, along with the security threat model.
Opaque streams are something we might wish to add in future. An opaque stream is a stream you can't read from, but you can pass it off to APIs that can.
With opaque streams, instead of
response.bodyreturningnullfor an opaque response, it would return an opaque stream.
In the case of fetch: wouldn't it make more sense to transfer the whole Response instead? Then you could also have access to the response headers and status code from the other origin.
const response = await fetch(url, {mode: 'no-cors'});
postMessage(response, origin, [response]);
But then what would be the benefit of initiating a no-cors fetch from the initial origin and then transferring it, when you could just as well initiate the fetch from the other origin?
const reader = body.getReader(); // throws!
What can you do with an opaque stream, other than transferring it?
Can you pipe an opaque readable stream into an writable stream? I guess that would require the writable stream to either be or become opaque as well. For example:
- You could pipe an opaque readable stream into the writable side of a
TextDecoderStream. This would have to turn the readable side opaque as well. - You could pipe an opaque readable stream into the writable side of a previously transferred
TransformStreamfrom the other origin.
Could you observe the result of an pipe between opaque streams? Is the initiator of the pipe allowed to know whether the pipe completes successfully or with an error through the pipe's Promise? Can the initiator abort the pipe using an AbortSignal?
In the case of
fetch: wouldn't it make more sense to transfer the wholeResponseinstead?
Yes, probably. :smile:
But then what would be the benefit of initiating a
no-corsfetchfrom the initial origin and then transferring it, when you could just as well initiate thefetchfrom the other origin?
I feel that there's something here, but I don't know what it is yet.
@yutakahirano pointed out that we'd quite like to do away with opaque responses and no-cors altogether.
- You could pipe an opaque readable stream into the writable side of a
TextDecoderStream. This would have to turn the readable side opaque as well.
Yes.
- You could pipe an opaque readable stream into the writable side of a previously transferred
TransformStreamfrom the other origin.
I hadn't thought of that. I think the creator of the TransformStream would still have to opt-in to this behaviour, so they couldn't be tricked into leaking information through a side-channel.
Could you observe the result of an pipe between opaque streams?
The internals of pipes are hidden from user JavaScript to make them more optimisable, but this also makes them a theoretically safe way to manipulate opaque streams.
Is the initiator of the pipe allowed to know whether the pipe completes successfully or with an error through the pipe's
Promise?
I'm not sure. Certainly we'd have to replace the rejection reason with something harmless. But even the fact that it failed may be too sensitive to expose.
Can the initiator abort the pipe using an
AbortSignal?
A difficult question. It requires a detailed threat model, which I don't think we can create until we have some concrete use cases.
I hadn't thought of that. I think the creator of the TransformStream would still have to opt-in to this behaviour, so they couldn't be tricked into leaking information through a side-channel.
I think if the readable side of the transform stream also becomes opaque, it should be safe. Not an expert though. 😛
I'm not sure. Certainly we'd have to replace the rejection reason with something harmless. But even the fact that it failed may be too sensitive to expose.
Even the promise resolving tells the initiator that the readable stream became closed. I think that might be used to estimate the "size" of the readable stream?
A difficult question. It requires a detailed threat model, which I don't think we can create until we have some concrete use cases.
Agreed. For the moment, I don't see many benefits, and a lot of potential security issues. I think this would be better solved by making Response transferable, unless we find more compelling use cases for opaque streams.