CSP for Google Fonts

style-src https://fonts.googleapis.com; font-src https://fonts.gstatic.com

Google Fonts needs two directives: style-src https://fonts.googleapis.com for the stylesheet and font-src https://fonts.gstatic.com for the font files. The <link> in Google’s CSS API docs points at https://fonts.googleapis.com/css2?family=…, and the CSS that comes back names each file on the other host, for example src: url(https://fonts.gstatic.com/s/inter/v20/….woff2). Each directive allows one half, so one without the other still breaks the font.

The policy

Content-Security-Policy: default-src 'self'; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self'

The builder finds one low point: a <meta> tag ignores frame-ancestors, so send this as a header. Google’s own tag platform CSP guide writes the pair the same way for Tag Manager’s preview mode: https://fonts.googleapis.com in style-src, https://fonts.gstatic.com in font-src.

The usual mix-ups

  • Hosts swapped. fonts.gstatic.com in style-src or fonts.googleapis.com in font-src allows nothing the fonts need: the stylesheet or the files are refused, the text falls back to another font, and the console names the directive that refused them.
  • No font-src at all. It falls back to default-src, so with default-src 'self' the stylesheet arrives and every font file is refused.
  • 'unsafe-inline' isn’t needed. The fonts come in an external stylesheet, so nothing here asks for inline styles. The builder rates 'unsafe-inline' in style-src medium; drop it unless your own markup needs it.
  • @import counts as a stylesheet. @import url("https://fonts.googleapis.com/css2?family=Inter") in your CSS is checked against style-src too.
  • 'strict-dynamic' does nothing for styles. It only changes how scripts are trusted, so a nonce-based policy keeps https://fonts.googleapis.com in style-src.
  • Stripe Elements fonts. When you pass a Google Fonts URL to Elements as cssSrc, Stripe’s guide says connect-src must allow it as well.

Self-hosting removes both

Serve the files from your own origin and 'self' covers the stylesheet and the fonts. next/font/google in Next.js does this at build time, so the browser never asks Google.

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-latin.woff2") format("woff2");
  font-display: swap;
}

In HTML and nginx

<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter&display=swap">
add_header Content-Security-Policy "default-src 'self'; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self'" always;

Try it in the CSP Builder

everything that is fetched and has no directive of its own
stylesheets and inline styles
web fonts
<object>, <embed> and <applet>
the URLs a <base> element may point at
who may frame this page

A nonce has to be fresh for every response, so 'nonce-REPLACE_ME' is a placeholder: your server fills it in and puts the same value in the <script nonce=…> tag.

1 finding worst low

6 directives

Content-Security-Policy

default-src 'self'; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; object-src 'none'; base-uri 'self'; frame-ancestors 'self'

The same policy as a meta tag

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; object-src 'none'; base-uri 'self'">

A meta tag only works in HTML, has to be in the <head>, and is read once: a header can be changed per response.

Findings · worst low

  • low A <meta> tag ignores frame-ancestors. If this policy goes into HTML, that directive is doing nothing there: send it as a header.

Every directive, in plain words

DirectiveWhat it governsSources
default-srceverything that is fetched and has no directive of its own
  • 'self' this origin only
style-src
→ default-src
stylesheets and inline styles
  • 'self' this origin only
  • https://fonts.googleapis.com the host fonts.googleapis.com over https
font-src
→ default-src
web fonts
  • 'self' this origin only
  • https://fonts.gstatic.com the host fonts.gstatic.com over https
object-src
→ default-src
<object>, <embed> and <applet>
  • 'none' nothing of this kind may load
base-urithe URLs a <base> element may point at
  • 'self' this origin only
frame-ancestorswho may frame this page
  • 'self' this origin only

Checked against CSP Level 3: every directive, every source keyword, and the fallback chain that carries a directive to default-src. Nothing is uploaded, and no request is made to the site whose policy you paste.

Runs in your browser. Nothing you enter is sent anywhere.