Skip to content

Content Security Policy

When you need this

Only if your site already sends a Content Security Policy. If it does not, do not add one for Gutternote. A new policy can block other parts of your site.

To find the policy the browser gets, check the response headers and the page source:

Terminal window
curl -sI https://staging.example.com/ | grep -i content-security-policy

A page can get more than one policy, from the framework, host, CDN or a <meta> tag. Each is enforced on its own, so each must allow Gutternote.

Directives

Merge these into the directives you already have:

script-src https://cdn.gutternote.com;
connect-src https://api.gutternote.com https://storage.googleapis.com;
img-src https://storage.googleapis.com blob:;
style-src 'unsafe-inline';
font-src https://cdn.gutternote.com;

font-src is optional. Without it, the widget uses the system font.

If your policy has only default-src, a new directive replaces that fallback for its type. Copy your existing sources into each directive you add.

Sources by feature

DirectiveSourceNeeded for
script-srccdn.gutternote.comwidget.js and the bundles it loads later: page capture, emoji, markdown, themes.
connect-srcapi.gutternote.comConfiguration, sessions, threads, uploads.
connect-srcstorage.googleapis.comImage uploads. They go straight to storage.
img-srcstorage.googleapis.comShowing attachments.
img-srcblob:The preview before upload.
style-src'unsafe-inline'The widget’s stylesheet in its shadow root.
font-srccdn.gutternote.comOptional web fonts.

Google avatars come from lh3.googleusercontent.com. If that is blocked, the widget shows initials. Google sign-in opens a popup, so no frame-src is needed.

Nonces and strict-dynamic

The widget’s <style> cannot take your nonce. Browsers ignore 'unsafe-inline' when the same directive has a nonce or hash. If production needs nonced styles, load Gutternote only on preview or staging with its own policy.

With 'strict-dynamic', browsers ignore host sources in script-src. Put your nonce on the Gutternote script tag. Then check that the later bundles still load.

Examples

Next.js, adding the sources only on previews:

next.config.js
const preview = process.env.VERCEL_ENV === 'preview'
const gn = (s) => (preview ? ` ${s}` : '')
const policy = [
"default-src 'self'",
`script-src 'self'${gn('https://cdn.gutternote.com')}`,
`connect-src 'self'${gn('https://api.gutternote.com https://storage.googleapis.com')}`,
`img-src 'self' data:${gn('https://storage.googleapis.com blob:')}`,
`style-src 'self'${gn("'unsafe-inline'")}`,
].join('; ')
module.exports = {
async headers() {
return [{ source: '/(.*)', headers: [{ key: 'Content-Security-Policy', value: policy }] }]
},
}

A meta tag, placed before the resources it covers:

<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' https://cdn.gutternote.com; connect-src 'self' https://api.gutternote.com https://storage.googleapis.com; img-src 'self' https://storage.googleapis.com blob:; style-src 'self' 'unsafe-inline'"
/>

Test with Report-Only

Send the new policy as Content-Security-Policy-Report-Only first. The browser logs what it would block and blocks nothing. When the console is clear on the pages reviewers use, switch to Content-Security-Policy.

Test the parts that load later:

  1. Attach an image or take a capture. This tests storage.
  2. Open the reaction picker. This tests a later bundle.
  3. Reload an attachment. This tests img-src.

What a missing source looks like

MissingResult
CDN in script-srcNo widget. Or capture, emoji search or formatting fails.
API in connect-srcNo widget. It cannot read its configuration.
Storage in connect-srcImage upload fails.
Storage in img-srcAttachments show as broken images.
blob: in img-srcThe preview before upload is blank.
'unsafe-inline'The widget has no styles, or does not show.
CDN in font-srcSystem font. Nothing else breaks.

If it still blocks

  • Read the console. It names the directive. Check whether the message came from the enforced policy or the Report-Only one.
  • Check every policy the page gets, not just the one you edited.
  • After each change, reload, clear the console, and repeat the action that failed.
  • No CSP error and no widget: check the key and the origin. See The widget does not appear.

Self-hosted

Replace cdn.gutternote.com and api.gutternote.com with your own origins, and the storage origin with your object store. Keep widget.js, its later bundles and the fonts/ folder together.