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

Possible feature discussion: opaque streams #983

Open
ricea opened this issue Feb 7, 2019 · 3 comments
Open

Possible feature discussion: opaque streams #983

ricea opened this issue Feb 7, 2019 · 3 comments

Comments

@ricea
Copy link
Collaborator

ricea commented Feb 7, 2019

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.

@MattiasBuelens
Copy link
Collaborator

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.

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 TransformStream from 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?

@ricea
Copy link
Collaborator Author

ricea commented Feb 14, 2019

In the case of fetch: wouldn't it make more sense to transfer the whole Response instead?

Yes, probably. 😄

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?

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 TransformStream from 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.

@MattiasBuelens
Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants