-
Notifications
You must be signed in to change notification settings - Fork 342
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
referrer same-origin constraint is a footgun for people trying to "copy" a Request #245
Comments
Note, this is really problem anywhere the attribute has stronger setter constraints compared to what the getter can return. The 'navigate' mode has a similar problem. |
Bad header values in CORS result in a network error. Forbidden headers throw in the Headers classd (and invalid values are accepted without question, not ignored). I wonder what @jakearchibald and @domenic think about this. (Note that saying this is about setter/getter is confusing, since these attributes only have a getter. You can influence what they return through the constructor, but there's a number of reasons why you cannot create all the kinds of objects a user agent can.) |
This confuses me. Steps 4 to 6 of Headers append algorithm are:
None of those throw. We only throw for immutable headers or if the name/value contain illegal characters. |
Okay, I guess I'm okay with silently ignoring certain values and maybe logging something to the console? |
I am confused why people are trying to copy requests in this way instead of doing |
It doesn't let you change the URL of the request. In this case they wanted to sanitize the search string in the URL. |
@wanderview for that scenario a synthetic redirect might be better? |
In this case they wanted to return the normal fetch'd response, but use a sanitized URL when saving in the cache. This particular problem would get easier if chrome implemented ignoreSearch. Regardless, I have seen this pattern in the wild many times now. |
What pattern? Workarounds for |
The pattern of wanting to copy all evt.request values, but use a different url. Anyway, I'm tired of arguing this issue. Its a common mistake I see. We can try to put some seat belts on the API here or not. I'm fine either way. |
I'm not trying to argue, I'm just trying to figure out what the problem is. I don't have the same context available as you, e.g., I have only seen the code you pasted in OP. If it's common for folks to want to change the URL of a request, but nothing else, perhaps that's a utility we should introduce? |
I think something like that would help devs. |
Naive question, why not |
@domenic we could do that. Though |
Note that if that's the feature this is a duplicate of #191 where I did point out that |
Right, the issue is that |
Another case where someone is being caught out by this "copy a Request" footgun: https://bugs.chromium.org/p/chromium/issues/detail?id=573937#c15 |
I tried to figure out what would have to silent fail:
Not entirely sure which of those would be typically hit, but it's less than I expected so maybe this is worth doing. Any new thoughts @wanderview? |
Another variant of the "it's hard to tweak requests" issue: https://mobile.twitter.com/RReverser/status/751837867350122497 |
Right, that is bullet point 3 above. |
I can't remember why creating a |
Because it's tricky to allow while not opening up new security holes. |
F2F:
|
@annevk What do you think? |
I don't like adding url to RequestInit (folks will then want to pass it as the first argument which will be really confusing and I'm not even sure if that's feasible). I'm also not a big fan of changing modes without there being a request to do so. I think I still prefer silent failure (so certain things we just drop rather than throw for). |
FWIW I am copying requests in the fetch event, because I need to insert an ETag header (retrieving the original response from the service-worker cache). The following function does this, but imo is really bloatcode. Would this fall into the same category as the issue described in OP? Would be nice if there is an easier and shorter solution. function insertETagFromResponse(request, response) {
var headers = new Headers();
// Copy all original headers
for (var header in request.headers.keys()) {
var value = request.headers[header];
if (value) {
headers.append(header, value);
}
}
// Add previous ETag value to receive 304 response.
headers.append('If-None-Match', response.headers.get('ETag'));
return new Request(request.url, {
method: 'GET',
headers: headers,
body: request.body,
mode: request.mode === 'navigate' ? 'cors' : request.mode,
credentials: request.credentials,
cache: request.cache,
redirect: request.redirect,
referrer: request.referrer,
integrity: request.integrity
});
} |
I see... Well, hopefully we can at least pass original referrer using a custom header as a workaround for analytics. |
To be clear, you can set any same-origin referrer. And yes, you could use a custom header. Anyway, this is off-topic for this discussion, which is about making the Request constructor failing less. |
@annevk Well, it was slightly related to the original issue (referrer), but I agree. Thanks for the explanations! |
Just popping in to say I hit this issue to trying to add a cache-bust to requests because Chrome doesn't support cache modes yet. In this case though I only add the cache-bust to same-origin requests so I'm hitting the issue with something the boils down to new Request(new Url(event.request.url), event.request) |
Yeah, that would hit it. No need for |
Yep, yep. I wrote that it boiled down to something like that. In the real code the url passed through a helper to handle the cache-bust stuff. |
@annevk, the PR is pretty different from the f2f conclusion. I'm not sure I agree with it. To summarize the difference, the f2f decision wanted to allow this:
But you seem to want to require this:
Why must a new URL require the same semantics as a brand new Request? Why can't an existing Request have its URL modified? For example, our proposed URL property on init would allow a cache busting param to be added to an existing Request without disturbing the Request window value. This would let things like basic auth, etc, continue to work. AFAICT from the spec if window is set in init, then a TypeError is thrown (step 10, although it makes little sense with step 11.) Personally I would like to see us provide an "override the URL on an existing Request" operation since that is what we have seen developers need to do. I don't particularly think the current PR provides this. |
#245 (comment) mentions a number of things. This does not attempt to address all of them. Overriding the url can be considered separately. Not entirely sure what the implications of that would be. |
Also, looking at my response to that comment you could have indicated sooner you were not happy with that 😞 |
Sorry, I didn't see it with the other responses on the thread. I saw your responses about overriding the mode further down. |
I think that's somewhat more complicated as I indicated before. It also makes the API-shape very weird.
Nope, but it does solve the original problem. Unless you think that should no longer be solved? |
I still don't think this really addresses the use case that devs repeatedly try to perform (same request, but different URL). Since we disagree perhaps we could get @jakearchibald or others with a devs perspective to break the impasse. I'll go with whatever you collectively agree on. |
I think @annevk's PR is a step in the right direction, but agree that it's really confusing how you change URL. Some thoughts: Allow mutations So developers could just do An API like Clone with changes
I'm not sure any of this is better than just adding |
Allowing mutations is a rather big change. We could make request mutable, but then someone will have to figure out what invariants need to be preserved, etc. Also, I thought we decided at some point we liked it being immutable... Cloning with changes has the teeing drawback. "Just adding" url to It's still unclear to me why solving this is mutually exclusive with applying the PR. |
F2F Notes:
|
I think we also decided to land the PR, right? |
I certainly am. No one else objected. |
Just in case you're waiting for me, I defer to the TPAC decision. |
Thanks @wanderview and @jakearchibald. I opened a new issue for the F2F notes: #391. I'll land the PR as a way of fixing the other issue raised in this bug. That still leaves URLs, for which we don't have a plan. If someone has a proposal for that a new issue would be good. |
Folks are using the Request constructor in unexpected ways and therefore it is throwing in unexpected ways (in particular when mode is “navigate” or when setting referrer to a cross-origin URL). This will make it throw less, while not really being less useful. Fixes #245.
Folks are using the Request constructor in unexpected ways and therefore it is throwing in unexpected ways (in particular when mode is “navigate” or when setting referrer to a cross-origin URL). This will make it throw less, while not really being less useful. Fixes #245.
The referrer changes to Request constructor should be in Firefox 54. See: https://bugzilla.mozilla.org/show_bug.cgi?id=1298823 |
Lengthy discussion but without any resolution. Was any of this implemented? How to clone the request changing the URL? |
|
Actually, answering to the question above
Looks like this hasn't been implemented in Chrome yet.
|
Right, see pointers to browser bugs in #377 (comment). |
It looks like they closed the issue on March 21st. I was able to do the following inside a service worker to act as a proxy for urls to forward requests to a domain self.addEventListener('fetch', function(event) {
var url = "https://baseserver.com";
var req = new Request(url, event.request);
fetch(req).then(response => {
console.log(response);
});
}); |
Why is this? I can manually override the body and I can read the body of the original request with Edit: I was able to keep the body of a post message while changing the URL with the following: self.addEventListener('fetch', event => {
let url = "https://baseserver.com/post";
event.respondWith(new Promise(resolve => {
event.request.text().then(text => {
let newReq = new Request(url, event.request);
resolve(fetch(new Request(newReq, {body: text})));
});
}));
}); It feels dirty but it works ¯\_(ツ)_/¯ |
Recently I saw a website in the wild attempting to do this:
This will work just fine during development on localhost, because .referrer will most likely always be same-origin. When the site is posted on twitter, for example, it will be visited through a t.co redirector. This results in a t.co referrer which is cross-origin.
So the site that worked fine in local development will blow up when its published to twitter. This seems like a bit of a footgun.
We could make new Request() silently ignore the value if its invalid instead of throwing. This is somewhat similar to using bad header values. They just get ignored.
The text was updated successfully, but these errors were encountered: