Content Security Policy (CSP)

Learn how to configure a Content Security Policy in your Tauri v2 application to protect against cross-site scripting and control which resources the webview can load.

A Content Security Policy (CSP) is a set of rules you give to the browser that says: "only load scripts, styles, images, and connections from these specific places, and nothing else." Without a CSP, a web page will run any script it receives — including malicious code injected by an attacker. With a CSP in place, even if an attacker manages to slip a harmful script into your page, the browser refuses to execute it because the script came from an unapproved source.

In a Tauri app, your frontend runs inside a webview. That webview is a browser engine, so it respects the same security rules as a regular browser. Tauri gives you a straightforward way to set a CSP through the app.security.csp field, and it even handles the tricky parts — like hashing local scripts and generating cryptographic nonces — automatically at build time. You also need ipc: and http://ipc.localhost in connect-src so capabilities can actually reach the backend.

Tauri handles the repetitive parts for you:

At compile time, Tauri scans your bundled HTML, JavaScript, and CSS files. It computes hashes of inline scripts and generates unique nonces for styles and external scripts, then appends them to your CSP. You only need to declare the sources that are specific to your application — such as external APIs, font services, or custom protocols.

What CSP protects against

The primary threat CSP defends against is cross-site scripting (XSS). An XSS attack happens when untrusted input ends up being executed as code in the browser. For example, if your app displays a user comment without sanitizing it, a malicious comment containing a <script> tag could steal data, hijack sessions, or deface the page.

CSP acts as a safety net. Even if a sanitization step fails, a properly configured policy will block the injected script because it does not match the allowed sources. CSP can also prevent other attacks like clickjacking (when combined with frame-ancestors) and mixed content warnings.

In a Tauri application, CSP also helps you control which native capabilities the frontend can reach. Tauri’s IPC bridge uses ipc: and http://ipc.localhost — if your CSP blocks those, your frontend silently loses all access to Rust commands.

How Tauri enforces the policy

Tauri injects the CSP as a <meta> tag into your main HTML file during the build process. The browser then enforces the policy on every page load. You do not need to configure any web server headers. The only place you define the policy is inside the tauri.conf.json file under the app.security.csp key.

The value you write there is the base policy. At build time, Tauri merges it with automatically generated hashes and nonces for your local bundled code. The final CSP that ends up in the <meta> tag is a combination of what you wrote and what Tauri computed.

No manual nonce or hash calculations:

You do not need to generate nonces in your server code or manually compute SHA-256 hashes for inline scripts. Tauri does that for every file that is part of your Vite build output.

Configuring CSP in tauri.conf.json

Open the src-tauri/tauri.conf.json file and locate the app object. Inside app, add a security object containing a csp field. The csp field can be a string or an object mapping directive names to source lists.

Here is a minimal configuration that keeps your app functional while being fairly restrictive:

src-tauri/tauri.conf.json
{
  "app": {
    "security": {
      "csp": {
        "default-src": "'self'",
        "connect-src": "ipc: http://ipc.localhost",
        "img-src": "'self' asset: http://asset.localhost blob: data:",
        "style-src": "'self' 'unsafe-inline'"
      }
    }
  }
}

This policy means:

  • default-src 'self' — by default, only load resources from the same origin as the page. This covers scripts, fonts, media, and other types that aren't explicitly listed.
  • connect-src ipc: http://ipc.localhost — allows the frontend to make connections to Tauri's IPC bridge. Without this line, every call to invoke() will fail.
  • img-src 'self' asset: http://asset.localhost blob: data: — images can come from your own origin, from Tauri’s asset protocol, from the local asset server, or from blob/data URIs (which are common for dynamically generated images).
  • style-src 'self' 'unsafe-inline' — stylesheets from the same origin are allowed, and inline styles are permitted. Inline styles are often unavoidable in React apps, but Tauri’s nonce mechanism still protects against injected styles.

Avoid 'unsafe-inline' for scripts:

Setting script-src 'unsafe-inline' disables the nonce-based protection for scripts and makes XSS easier. Tauri manages nonces for your bundled scripts automatically, so you rarely need this. Keep it out of your policy unless you have a very specific, well-understood reason.

Directives you are likely to need

The CSP specification defines many directives, but only a few are commonly relevant for a Tauri + React + Vite app. The following table lists the ones you will most likely touch.

DirectiveWhat it controls
default-srcFallback for any resource type not explicitly listed.
script-srcWhere JavaScript can be loaded and executed.
style-srcWhere CSS stylesheets and inline styles may come from.
img-srcAllowed sources for images.
connect-srcDestinations for fetch(), XHR, WebSocket, and Tauri IPC.
font-srcAllowed sources for web fonts.
media-srcAllowed sources for <audio> and <video>.
frame-srcWhich origins can be embedded in <iframe>.

Omitting connect-src will break all IPC:

The Tauri IPC bridge uses ipc: protocol and http://ipc.localhost. If those are not listed in connect-src, every Rust command you call from the frontend will be blocked with no visible error other than a CSP violation in the developer console.

Understanding Tauri-specific source values

Tauri’s webview uses several non-standard URL schemes that you must allow in your policy:

  • 'self' — the same origin as the HTML page. In development, this is usually http://localhost:1420. In production, Tauri serves the frontend from a custom protocol.
  • asset: — a custom protocol that loads bundled assets directly from the filesystem. Images and other static resources often use this scheme.
  • http://asset.localhost — a local HTTP server that serves assets in development mode. Useful when you need to reference local files outside of the custom protocol.
  • ipc: and http://ipc.localhost — the communication channels the frontend uses to call Rust functions via invoke(). These must be in connect-src.
  • customprotocol: — a placeholder for any custom protocol you might define. The default protocol for Tauri v2 is tauri://localhost, but the CSP needs the scheme customprotocol: to permit it.

When you set default-src 'self', it does not automatically include asset:, ipc:, or customprotocol:. You must list them explicitly in the relevant directives.

Allowing external resources

Most real applications load resources from outside their own bundle: Google Fonts, analytics scripts, CDN-hosted images, or API endpoints. You add these domains to the appropriate directive.

Suppose your app uses the Inter font from Google Fonts, profile images from https://avatars.example.com, and talks to an API at https://api.myapp.com. Your CSP might look like this:

src-tauri/tauri.conf.json
{
  "app": {
    "security": {
      "csp": {
        "default-src": "'self'",
        "connect-src": "ipc: http://ipc.localhost https://api.myapp.com",
        "img-src": "'self' asset: http://asset.localhost blob: data: https://avatars.example.com",
        "style-src": "'self' 'unsafe-inline' https://fonts.googleapis.com",
        "font-src": "'self' https://fonts.gstatic.com"
      }
    }
  }
}

A few things to note:

  • style-src now includes https://fonts.googleapis.com so the stylesheet for the font can load.
  • font-src includes https://fonts.gstatic.com because that is where Google serves the actual font files.
  • connect-src includes your API domain so fetch() calls reach the backend.
  • img-src explicitly lists the avatar CDN.

This configuration keeps your app functional while still blocking scripts from any origin you didn't name.

Testing your CSP

During development, run your Tauri app and open the developer tools (right-click in the webview and select "Inspect Element", or use Ctrl+Shift+I). Look at the Console tab. If any resource is blocked by your policy, you will see a message like:

Refused to load the script 'https://evil.example.com/bad.js' because it violates the following Content Security Policy directive: "script-src 'self'".

Each violation tells you exactly which directive needs adjustment and which URL was blocked. Read the error, decide whether you trust that source, and add it to the corresponding directive.

If you want to test a stricter policy without breaking your app, temporarily use a report-only approach. Remove the csp field from tauri.conf.json and manually add a <meta> tag to your index.html that uses Content-Security-Policy-Report-Only instead of Content-Security-Policy. The browser will log violations but won't block anything. This is an advanced workflow; for most Tauri apps, testing directly with the Console is sufficient.

Common mistakes and how to fix them

Missing IPC sources

Problem: After adding a CSP, buttons that call Rust functions stop working. The console shows a CSP violation mentioning ipc: or http://ipc.localhost.

Fix: Make sure connect-src contains both ipc: and http://ipc.localhost, separated by a space.

"connect-src": "ipc: http://ipc.localhost"

Using 'unsafe-inline' for scripts

Problem: You added script-src 'unsafe-inline' because an inline script wasn't working. This weakens your CSP to the point where nonces are ignored.

Fix: Remove 'unsafe-inline' from script-src. Tauri automatically adds nonces to your bundled inline scripts, so they will still run. If you have inline scripts in index.html, Tauri will hash them. No manual step is required.

Forgetting to allow external images

Problem: Images from a CDN or blob URLs (used for file previews) appear broken.

Fix: Add the CDN domain to img-src, and keep blob: and data: if your app generates images dynamically.

"img-src": "'self' asset: http://asset.localhost blob: data: https://cdn.example.com"

Setting default-src too broadly

Problem: Using default-src * or default-src https: disables much of the protection. It is tempting when you're just trying to get things working, but it leaves the door open for XSS.

Fix: Start with default-src 'self' and add only the sources you need. It takes a few extra minutes of looking at console errors, but the resulting policy is actually doing its job.

What a correct configuration looks like in practice

Below is a complete example from a realistic Tauri + React + Vite application. The app uses a third-party API, Google Fonts, and displays user-uploaded images as blob URLs.

src-tauri/tauri.conf.json
{
  "$schema": "https://schema.tauri.app/config/2",
  "productName": "my-app",
  "version": "0.1.0",
  "identifier": "com.mycompany.myapp",
  "build": {
    "devUrl": "http://localhost:1420",
    "frontendDist": "../dist"
  },
  "app": {
    "windows": [
      {
        "title": "My App",
        "width": 1024,
        "height": 768
      }
    ],
    "security": {
      "csp": {
        "default-src": "'self'",
        "connect-src": "ipc: http://ipc.localhost https://api.myapp.com",
        "img-src": "'self' asset: http://asset.localhost blob: data: https://avatars.myapp.com",
        "style-src": "'self' 'unsafe-inline' https://fonts.googleapis.com",
        "font-src": "'self' https://fonts.gstatic.com",
        "media-src": "'self' blob:"
      }
    }
  }
}

With this policy, every resource the app needs is allowed, and everything else is blocked. The console stays clean, and the security boundary is meaningful.

If you see no CSP violations and your app works:

Your CSP is correctly configured. The browser is enforcing the rules silently in the background, and your users are protected from a wide class of script injection attacks.

How beginners should think about CSP

It helps to picture CSP as a guest list at a club. The bouncer (the browser) has a list of approved guests (sources you've listed). Anyone not on the list doesn't get in, no matter how convincing they look. When you write a CSP, you are handing the bouncer a list. If the list is too short, your own friends (your app's resources) get turned away. If the list is too long, unwanted guests slip through.

The goal is to write the shortest possible list that still lets your legitimate resources in. Tauri helps by automatically adding your bundled code to the list, so you only have to worry about external guests.

Summary

A strong CSP works best when it is part of a layered defense, not the only defense. See the capability system documentation for how to control which Rust commands each window can access.