Why are FQDN (ending with a dot) not working in CSP?

206 Views Asked by At

I have a FQDN in my CSP:

The source list for the Content Security Policy directive 'img-src' contains an invalid source: 'xxx.de.'. It will be ignored.

This is my csp:

default-src 'self'; script-src 'self' 'unsafe-inline' 
'unsafe-eval' www.xxx.de; style-src 'self' 'unsafe-inline'; 
font-src 'self' data:; img-src 'self' xxx.de.; frame-src 'self' 
www.youtube-nocookie.com xxx.de;

Why are domains having a dot at the end not allowed in CSP?

I need to link to this URL, and I need to have a CSP, this is my current CSP, the spec does not appear to allow me to describe the URL I have, how can I work around this?

3

There are 3 best solutions below

3
Grim On BEST ANSWER

The spec is simply wrong. A dot as the last character of the domain should be allowed.

8
Quentin On

Because the specs say so.

The host-source:

host-source = [ scheme-part "://" ] host-part [ ":" port-part ] [ path-part ]

requires a host-part:

host-part = "*" / [ "*." ] 1*host-char *( "." 1*host-char )

which has to end with a host-char:

host-char = ALPHA / DIGIT / "-"

… which cannot be a ..

The path-part:

path-part = path-absolute (but not including ";" or ",") ; path-absolute is defined in section 3.3 of RFC 3986.

is defined elsewhere:

path-absolute ; begins with "/" but not "//"

… which has to start with / so cannot consist entirely of ..

1
VonC On

Here is my problem:

https://upload.wikimedia.org./wikipedia/commons/thumb/8/8a/2006-02-13_Drop_before_impact.jpg/396px-2006-02-13_Drop_before_impact.jpg 
                           ^^^

I can neither reference it in the img-src-attribute nor change the URL nor remove the csp-directive. I can not display that image.

You have a unique situation where the URL you need to allow through your CSP contains a trailing dot in the domain name, and CSP does not support such syntax.
Since you cannot change the URL or remove the CSP, you are essentially locked into a scenario where the standard tools at your disposal are not sufficient.

Given these constraints, one approach would be to create a server-side proxy that fetches the image from the FQDN with the trailing dot and then serves it under your own domain. Your server-side code could make an HTTP request to the original URL, fetch the image, and then serve it as a response to a request to a URL on your own domain.
That way, you can add your own domain to the img-src directive in your CSP, and you are in compliance with the CSP specifications.

For example, in a Python Flask app, a simple proxy endpoint might look like this:

import requests
from flask import Flask, Response, request

app = Flask(__name__)

@app.route('/proxy_image/<path:subpath>')
def proxy_image(subpath):
    base_url = "https://upload.wikimedia.org./wikipedia/commons/"
    target_url = f"{base_url}{subpath}"
    
    response = requests.get(target_url)
    
    if response.status_code == 200:
        return Response(response.content, content_type=response.headers['Content-Type'])
    else:
        return Response(f"Unable to fetch image. Status code: {response.status_code}", status=response.status_code)

if __name__ == '__main__':
    app.run()

Then you could update your CSP to allow images from your own domain:

img-src 'self' your-domain.com;

That will allow you to display the image without modifying the original URL or removing the CSP directive.


The IP of the Server running your Flask app differs from the browsers IP.
In order to recognize the session in Wikimedia, the IP address must be the same.

If Wikimedia enforces IP-based session restrictions, then a server-side proxy solution will not work because it makes requests from the server's IP address, not the client's IP address.

An alternative approach could be to use a service worker in the client's browser to intercept requests to URLs with the problematic domain and redirect them to the correct URL without the trailing dot. That would preserve the client's IP address.

You would need to:

  • Register a service worker in your web app.
  • Inside the service worker, listen for fetch events.
  • If a fetch event is for a URL that matches the problematic domain with the trailing dot, alter the URL to remove the trailing dot before making the network request.

For example, service-worker.js, in the public directory of your web server:

self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);
  
  if (url.hostname === 'upload.wikimedia.org.') {
    const newUrl = url.toString().replace('upload.wikimedia.org.', 'upload.wikimedia.org');
    event.respondWith(fetch(newUrl));
  }
});

That script is listening for fetch events with self.addEventListener. It checks if the hostname matches 'upload.wikimedia.org.' (with the trailing dot).

  • If it matches, we create a newUrl string where we have replaced the hostname with 'upload.wikimedia.org' (without the trailing dot).
  • We then call event.respondWith(fetch(newUrl)) to make a network request to the new URL and respond with that.

Register the service worker in the main JavaScript file of your web application. This would go in a script that runs on your page load:

// main.js (or another appropriate file)
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
      .then((registration) => {
        console.log('ServiceWorker registration successful with scope: ', registration.scope);
      }, (err) => {
        console.error('ServiceWorker registration failed: ', err);
      });
  });
}

And make sure that the script where you registered the service worker is included in your HTML file. If your script is in a file called main.js, it might look something like this:

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <!-- ... other head elements ... -->
</head>
<body>
  <!-- ... your body content ... -->

  <script src="/main.js"></script>
</body>
</html>