nestjsip-addresscontent-security-policyhelmet.jsdynamic-ip

Dynamic IP Address Handling in Content Security Policy (CSP) with Helmet in a NestJS Backend


Question:

I am working on a web application with a React frontend and a NestJS backend. The backend uses Helmet to manage the Content Security Policy (CSP).

My frontend needs to connect to an API hosted on a physical scanner with a dynamic IP address (assigned via DHCP). The scanner's IP address is stored in the database and fetched dynamically by the frontend.


Current Setup:

Backend CSP Configuration with Helmet:

import helmet from 'helmet'

const defaultDirectives = helmet.contentSecurityPolicy.getDefaultDirectives()

app.use(
    helmet({
        contentSecurityPolicy: {
            useDefaults: true,
            reportOnly: false,
            directives: {
                ...defaultDirectives,
                'connect-src': ["'self'", '172.x.x.x'], // Static IP set here
                'script-src': ["'self'", '*.openstreetmap.org'],
                'img-src': ["'self'", 'data:', 'blob:', '*.openstreetmap.org'],
                'style-src': ["'self'", "'unsafe-inline'", 'fonts.googleapis.com'],
                'font-src': ["'self'", 'fonts.gstatic.com']
            }
        },
        crossOriginEmbedderPolicy: false,
        crossOriginOpenerPolicy: false,
        crossOriginResourcePolicy: false,
    })
)

Issue:

When the scanner's IP address changes (e.g., from 172.x.x.x at work to 192.x.x.x at home), I encounter the following error:

Refused to connect to 'http://192.x.x.x/api/connect' because it violates the following Content Security Policy directive: "connect-src 'self' 172.x.x.x".

It seems that the CSP configured in the backend is too restrictive, and I'm unsure how to set the IP address dynamically based on the scanner's changing IP.

Additionally, it feels like the backend's Helmet CSP configuration might be overriding the frontend's policy, leading to this issue.

What I’ve Tried

  1. I attempted to set the connect-src directive in CSP to allow the scanner's IP (172.x.x.x), but this does not account for the dynamic nature of the scanner's IP address.
  2. I tried using a meta tag for the CSP in the frontend, but it didn't resolve the issue because the backend CSP is overriding it, not sure about this one.

My Questions:

  1. How can I handle a dynamically changing IP address in the CSP using Helmet in NestJS?
  2. Is there a way to update the connect-src directive dynamically based on the IP stored in the database?
  3. Would disabling the CSP for connect-src (e.g., using '*' or 'unsafe-inline') be a valid approach without compromising too much security?
  4. What would be the best practice to maintain security while allowing these dynamic connections?

Any insights or suggestions would be greatly appreciated! 😊


Solution

  • Helmet maintainer here. Let's answer your four questions.

    To answer your first two questions, here's how you'd dynamically set the connect-src directive.

    I'd start by writing some generic middleware that stores the dynamic IP address. It might look something like this:

    app.use((req, res, next) => {
      res.locals.scannerIpAddress = dynamicallyFetchIpAddress();
      next();
    });
    

    Then you can set the Content-Security-Policy dynamically:

    app.use(
      helmet({
        contentSecurityPolicy: {
          // ...
          directives: {
            ...defaultDirectives,
            "connect-src": [
              "'self'",
              (req, res) => res.locals.scannerIpAddress,
            ],
            // ...
          },
        },
        // ...
      }),
    );
    

    Your third question is about disabling connect-src entirely. This could impact security because a rogue script could connect to some malicious server. However, it's up to you whether that's an acceptable risk.

    Your final question is about best security practices while allowing these dynamic connections. You'd want to make sure that the scanner IP address is safe and correct.