content-security-policy.md 7.5 KB

Content Security Policy

A Content Security Policy (CSP) tells the browser which sources a page may load scripts, images, fonts, and other resources from. NetBox does not send one by default. To send one, define it with the SECURE_CSP or SECURE_CSP_REPORT_ONLY configuration parameter.

NetBox's own pages are written to work under a strict script policy. Every <script> element NetBox renders carries the per-request nonce, and NetBox does not use inline event handler attributes or evaluate strings as code. This applies to scripts only. NetBox makes no equivalent promise for styles: it renders inline <style> elements and style= attributes, and none of them carry a nonce.

Example policy

The policy below is an example, not a default. It has been checked against the login page, object lists and detail pages, cable traces, Markdown images, GraphiQL, the Swagger and Redoc API documentation, and the REST API's browsable view, with and without NetBox Copilot turned on, as described below. Remote media and static hosts and a redirect-based single sign-on login were checked separately.

from django.utils.csp import CSP

SECURE_CSP = {
    'default-src': [CSP.SELF],
    'script-src': [
        CSP.SELF,
        CSP.NONCE,
        'https://static.copilot.netboxlabs.ai',
    ],
    'style-src': [CSP.SELF, CSP.UNSAFE_INLINE, 'https://fonts.googleapis.com'],
    'font-src': [CSP.SELF, 'data:', 'https://fonts.gstatic.com'],
    'img-src': [CSP.SELF, 'data:', 'https:'],
    'connect-src': [CSP.SELF],
    'worker-src': [CSP.SELF, 'blob:'],
    'base-uri': [CSP.SELF],
}

The policy has no object-src directive because cable traces embed their SVG in an <object> element, which falls back to default-src 'self'.

To try it without enforcing it, assign the same dictionary to SECURE_CSP_REPORT_ONLY instead. See Testing a policy below.

NetBox Copilot

The example allows only Copilot's own loader at static.copilot.netboxlabs.ai. If users turn on NetBox Copilot in their preferences, Copilot then loads React and Turbo from unpkg.com, and the browser blocks them under the example. Those scripts in turn load Stripe's script from js.stripe.com, which is never requested while they are blocked. To allow all of them, merge the entries below into the example policy. This is an excerpt, not a whole policy. script-src is the example's list with two entries appended, and frame-src is a new directive because the example does not define one.

SECURE_CSP = {
    # ...
    'script-src': [
        CSP.SELF,
        CSP.NONCE,
        'https://static.copilot.netboxlabs.ai',
        'https://unpkg.com',
        'https://js.stripe.com',
    ],
    'frame-src': [CSP.SELF, 'https://js.stripe.com'],
}

Allowing unpkg.com lets scripts from any npm package run on your NetBox pages, which weakens the protection the nonce gives. The alternative is 'strict-dynamic', described in Stronger script policies, which trusts the scripts that nonced scripts load instead of naming hosts. Requests Copilot makes after a user signs in to it were not checked, so a policy that stays quiet while Copilot loads may still report violations once it is in use.

What each directive allows

  • script-src allows scripts served by NetBox, scripts carrying the request's nonce, and NetBox Copilot's loader at static.copilot.netboxlabs.ai. Copilot is only loaded when it is enabled and a user has turned it on in their preferences. The further scripts it loads need the entries in NetBox Copilot.
  • style-src allows inline styles, plus Google Fonts stylesheets, which the Redoc page loads. See Styles below.
  • font-src allows NetBox's own fonts, the data: fonts embedded in the GraphiQL stylesheet, and fonts.gstatic.com for Redoc.
  • img-src allows https: so that Markdown images hosted elsewhere keep loading. NetBox keeps http and https image sources in rendered Markdown from any host. You can replace https: with a list of named hosts, but images from every other host will stop loading.
  • connect-src allows requests back to NetBox. The REST API and GraphQL tools, the Swagger try-it-out feature, and the start of a single sign-on login all depend on it.
  • worker-src allows the web workers GraphiQL starts from NetBox's own files, and the blob: worker Redoc starts.
  • base-uri stops an injected <base> element from redirecting relative URLs.

Remote media and static files

If uploaded media (image attachments, device type images) or static files are served from another host through the STORAGES parameter, add that host to the matching directives of the example policy above. Do not replace your policy with these lines: they show only the entries to append to the existing lists. Replace media.example.com and static.example.com with your hosts.

script-src: add 'https://static.example.com'
style-src:  add 'https://static.example.com'
font-src:   add 'https://static.example.com'
img-src:    add 'https://media.example.com' and 'https://static.example.com'
  • A media host goes in img-src. Use media-src or other directives too if the files are not images.
  • A static host goes in script-src, style-src, font-src, and img-src. NetBox's logos and icons are static files loaded as images.
  • The example policy already allows any https: image source, so img-src needs no change unless you replace https: with a list of hosts. Without the host in a narrowed list, the browser blocks every media and static image, including the logo and the device type images.

Styles

NetBox's nonce rule covers scripts only, and the example policy does not restrict inline styles. The reasons are that the nonce requirement exists to stop injected code from running, injected styles cannot run code, and NetBox renders a few dozen inline style= attributes, many with computed values such as colors and utilization widths. A style attribute cannot carry a nonce. Adding a nonce to style-src would also cause browsers to ignore 'unsafe-inline', which would block every one of them.

This differs from scripts, where NetBox supports a strict policy today. A strict style-src is not covered by this page.

Stronger script policies

The example uses 'self' plus a nonce. You can add 'strict-dynamic' to script-src, which lets nonced scripts load further scripts and makes browsers ignore 'self'. It has two trade-offs. Plugin <script src> tags that lack a nonce stop working, and any script that Copilot loads is trusted.

Testing a policy

Start with SECURE_CSP_REPORT_ONLY. The browser reports every violation in its developer console without blocking anything. Browse the pages your users rely on, including any plugin pages, and watch the console. When it stays quiet, move the policy to SECURE_CSP.

Test with DEBUG set to False. The Django debug toolbar and GraphiQL's debug integration inject inline scripts that a nonce policy reports. With DEBUG on, django-htmx also shows Django's error page for failed htmx requests and re-runs that page's inline scripts with eval, which a policy without 'unsafe-eval' blocks.

!!! note "Single sign-on"

Redirect-based SSO logins need `connect-src 'self'`, which the example includes. See the [authentication overview](authentication/overview.md) for the SSO requirements.