2

I have a site that is loading image tiles from openstreetmap. These images are served out from a number of sub-domains of openstreetmap.org, such as:

  • a.tile.openstreetmap.org
  • b.tile.openstreetmap.org
  • c.tile.openstreetmap.org

To allow this I added an img-src to my Content-Security-Policy *.openstreetmap.orgso that all subdomains should be valid.

The full img-src now reads:

img-src https: data: *.openstreetmap.org;

And for the sake of completeness the full Content-Security-Policy is:

default-src 'self'; script-src https: 'unsafe-inline' 'unsafe-eval'; style-src https: 'unsafe-inline'; img-src https: data: *.openstreetmap.org;

This seems to work as intended when the site first loads, and the page containing the maps is navigated to. There are actually multiple pages containing maps and both work.

However upon the user hitting "Refresh" the following errors occur:

Refused to connect to 'https://a.tile.openstreetmap.org/12/2011/1356.png' because it violates the document's Content Security Policy.
GET https://a.tile.openstreetmap.org/12/2010/1355.png net::ERR_FAILED

If I open Chrome dev tools and use the "Right Click on Refresh" -> "Empty Cache and Hard Reload" option to reload the page, then the image tiles load again, but only once subsequent "normal" refreshes produce the same error.

This site is using a 'serviceworker.js' and the locally servered pages are cached, but the tiles from openstreetmap.org are not. I have however manually emptied this cache, and verified that the cached versions have the correct Content-Security-Policy, so maybe the existence of the serviceworker.js is a misnomer.

EDIT 1

Posting the question made me investigate the serviceworker.js further. I removed it, and the issue was no longer present. Reinstating the service worker brought the issue back. So that is the cause.

There is nothing from openstreetmap.org in the Application cache.

EDIT 2

Further investigation has tracked down the cause, but has got me no closer to the solution. The first load works as my 'serviceworker.js' is operating on a cache-first, then network basis. So the first load theres no cache. Subsequent loads there is a cache, but the images from openstreetmap.org aren't in it.

The relevant section is:

self.addEventListener('fetch', function (event) {
    event.respondWith(
        // cache first then network
        // but not for different origin calls, this includes API
        caches.open("my-cache-" + self.sw_version)
            .catch((msg) => { console.log("caches.open(): ", msg); })
            .then(cache => {

                return cache.match(event.request)
                    .catch((msg) => { console.log("cache.match(): ", msg); })
                    .then(response => {

                        return response || fetch(event.request)
                            .catch((msg) => { console.log("fetch(event.request): ", msg); })
                            .then(response => {

                            // if it was from a differnt origin - simply return it
                            if (!event.request.url.match(event.request.referrer))
                                return response;

                            console.log("ServiceWorker.caching: ", event.request.url);
                            cache.put(event.request, response.clone());
                            return response;
                    });
                });
        })
    );
});

Which has identified the problem as being when the service worker makes the fetch() request after not finding it in the cache.

fetch(event.request):  TypeError: Failed to fetch

Which implies that the fetch() from within the serviceworker.js isn't using the correct Content-Security-Policy, but is using one that the request violates..

The network tab of the dev tools, is showing this for the request / response..

Request

Request URL: https://b.tile.openstreetmap.org/12/2011/1355.png
Referrer Policy: no-referrer-when-downgrade

Response

Origin: https://localhost:9443
Referer: https://localhost:9443/my-app/?
Sec-Fetch-Mode: cors
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36

Edit 3

Forcing the fetch() to use the same Content-Security-Policy by explicitly setting it allowed the request to work. But this is a hackey fix, as its now declared in two places (the website as a whole, and in the serviceworker.js) so if there is a "proper" fix, or even just an explanation as to why this is required, I would still be interested.

Edit 4

I've come back to this today to have another stab. I realized that there was an issue with my checking for remote urls - I was passing the referrer URL to the match function unescaped so it wasn't behaving as expected. I've corrected that now, and moved some code around to make it clearer (to myself).

self.addEventListener('fetch', function (event) {

    // https://stackoverflow.com/a/6969486 - escape a string to use in regex
    if (!event.request.url.match(event.request.referrer.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'))) {
        fetch(event.request)
            .catch((msg) => { console.log("fetch(event.request).remoteorigin: ", { event: event, error: msg }); })
            .then(response => {
                return response;
            });
    }
    else {
        event.respondWith(
            // cache first then network
            // but not for different origin calls, this includes API
            caches.open("crt-scada-" + self.sw_version)
                .catch((msg) => { console.log("caches.open(): ", msg); })
                .then(cache => {
                    return cache.match(event.request)
                        .catch((msg) => { console.log("cache.match(): ", msg); })
                        .then(response => {
                            return response || fetch(event.request)
                                .catch((msg) => { console.log("fetch(event.request).sameorigin: ", { event: event, error: msg }); })
                                .then(response => {
                                    cache.put(event.request, response.clone());
                                    return response;
                                });

                        });
                })
        );
    }
});

Now I have the weird situation where it reports the same error - but also loads the tiles from openstreetmap.

Any advice on why this might be happening would be gratefully accepted.

Morvael
  • 3,051
  • 2
  • 30
  • 51
  • Forcing explicit declaration of the Headers onto each request seemed to cause all sorts of issues with other fetch()s so i had to remove it. Still looking for a solution / explanation to this – Morvael Sep 11 '19 at 07:30
  • There's this: https://qubyte.codes/blog/content-security-policy-and-service-workers I have something similar, although I am using the same content security policy for the sw thread. That link points out that when the sw uses fetch() it uses the connect-src, which may be worth trying, although for me that changed the error, didn't fix it. – philw Oct 29 '20 at 15:08

0 Answers0