weserv / images

Source code of wsrv.nl (formerly images.weserv.nl), to be used on your own server(s).
https://wsrv.nl/
BSD 3-Clause "New" or "Revised" License
1.97k stars 193 forks source link

Some more potentially relevant response headers #325

Closed GitBoudewijn closed 6 months ago

GitBoudewijn commented 2 years ago

Hi!

I did some more research on response headers and found the following that would be useful for you to have:

1. Cross-Origin-Resource-Policy: cross-origin

To use some features like high precision timing with Performance.now() you have to have cross-origin isolation on your site: http://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy

This requires all resources to have the Cross-Origin-Resource-Policy header for non-CORS requests (like <img> tags without crossorigin or CSS background-image).

2. X-Content-Type-Options: nosniff

Pretty much every other image host has this header on all images. Not sure if it's really needed since you transform all the images so the chance that one of them could be interpreted as a script is pretty low. But apparently site security scanners expect this to be set so that could be a benefit as well.

3. Access-Control-Expose-Headers: *

You can do this:

fetch('//images.weserv.nl/?url=images.weserv.nl/lichtenstein.jpg', {
    'method': 'HEAD',
}).then(response=>{
    console.log(Object.fromEntries(response.headers));
});

But this only allows you to read the default safelisted headers: http://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header

It would be useful to be able to read all headers, for example Age and X-Images-API, and then maybe you can add some more headers like X-Width/X-Height or X-Original-Size (content length of the original image) or maybe X-Requested (timestamp when the image was requested by your server).

Thanks!

kleisauke commented 2 years ago
  1. We don't use SharedArrayBuffer or Performance.now(), so I'm not sure if there are any benefits of setting this header. Setting the CORP header on our site also requires that any sub-resources we load are properly allowed via CORS or CORP, which we cannot ensure at this moment on our homepage (for example, we use Algolia search as an external service).
  2. Indeed, this header is only relevant for user generated content. It's probably redundant to set this header, since all our images are processed. Also, I'm reluctant to set this header only to aid site security scanners.
  3. I think you can just use output=json in this case. For example:
    fetch('https://images.weserv.nl/static/lichtenstein.jpg?output=json').then(async (response) => {
      let data = await response.json();
      console.log(data.width);
      console.log(data.height);
    });

    I'm not sure whether there is much demand or any uses cases for the X-Original-Size or X-Requested headers.

Note that any response header we set comes at the expense of performance, although in most cases this is minimal. Also to ensure API compatibility, we cannot add headers and remove them afterwards, therefore we're a bit conservative in such cases. As always, feel free to use our source code to host your own solution.

GitBoudewijn commented 2 years ago
  1. I'm sorry maybe I wasn't clear. With 'you' I meant you as the user. So if I want to use those features on my site and have your images on my site, the images need to have the Cross-Origin-Resource-Policy: cross-origin header.

  2. Yeah that's true, I just thought that it doesn't have any downside either.

  3. X-Original-Size would be so you can calculate the amount of filesize reduction so you can compare different formats and quality settings and calculate how much bandwidth you've saved. X-Requested would be so that you can know when the image was actually updated, since the regular headers are modified by CloudFlare. But you're right that these could also be added to the JSON, although including it in the headers would mean you can access all of it at once together with the image. Also not all the information from the headers is included in the JSON.

kleisauke commented 2 years ago
  1. You're right, the CORP header is necessary for websites that have opt-in to a cross-origin isolated state and make non-CORS requests to images.weserv.nl. See for example this live demo. I just deployed this to our testing environment (t0.nl) and will add this header on our production environment later this week.

  2. The only drawback I can think of is that our response size increases; but that is negligible these days.

  3. Thanks for the use-case! I've just implemented X-Original-Size as X-Upstream-Response-Length by doing this on our testing environment (t0.nl):

    location / {
        weserv proxy;
    
        add_header X-Upstream-Response-Length $upstream_response_length;
    }

    (see for an example: https://t0.nl/?url=images.weserv.nl/lichtenstein.jpg&w=200) Regarding X-Requested, could that be implemented as X-Cache-Status (add_header X-Cache-Status $upstream_cache_status;)? Every time an upstream request is made, you can infer that as cf-cache-status != HIT and X-X-Cache-Status != HIT. I've also added this header to our testing environment.

kleisauke commented 2 years ago

Commit https://github.com/weserv/images/commit/f52d724a57949f4f1fc7bc45c9f25119a94d4b84 (which has just been rolled out to production) adds the following response headers to the API:

  1. Cross-Origin-Resource-Policy: cross-origin.
  2. X-Cache-Status: <MISS|BYPASS|EXPIRED|STALE|UPDATING|REVALIDATED|HIT>, see: https://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_cache_status
  3. X-Upstream-Response-Length: <LENGTH>.

In addition to that, I just enabled the X-Content-Type-Options: nosniff header in Cloudflare (SSL/TLS -> Edge Certificates -> HTTP Strict Transport Security (HSTS) -> No-Sniff Header -> On).

I still need to think about adding the Access-Control-Expose-Headers header, and what value(s) to put in there.

kleisauke commented 6 months ago

I'll close this issue for now, please feel free to re-open if there's still a problem.