Skip to content

Content security policy

Every response carries a strict content security policy. Only Valet’s own scripts run. There are no inline scripts anywhere, which is why the theme code that runs before first paint is a file.

default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob: https:;
font-src 'self' data:;
connect-src 'self';
media-src 'self' blob:;
frame-src 'none';
frame-ancestors 'none';
base-uri 'none';
form-action 'self';
object-src 'none'
  • script-src 'self' means injected script doesn’t run, even if a service’s text gets into the page some way. React escapes text anyway. This is the second lock.
  • connect-src 'self' means page code can only talk to Valet. A script couldn’t send data elsewhere.
  • frame-ancestors 'none' means no other site can frame Valet. X-Frame-Options: DENY says the same to older browsers.
  • base-uri 'none' and form-action 'self' stop two ways of sending things elsewhere.

Three parts of the policy are looser than the rest, and you should know why.

  • style-src 'unsafe-inline': React sets inline style attributes. So injected CSS isn’t blocked by the policy. Valet’s own appearance.css passes through a cleaner so it can’t close its tag.
  • img-src https:: cards can show remote images, such as feed thumbnails. A remote image loads from your browser.
  • frame-src: 'none', unless you add an iframe integration. Then its origin is added, and only that one.

appearance.font.url adds that font’s origin to style-src and font-src. A Google Fonts stylesheet adds fonts.gstatic.com too. Nothing else adds to the list.

HeaderValueWhy
X-Content-Type-OptionsnosniffThe browser believes the declared type.
Referrer-Policysame-originAddresses don’t leak to other sites through links.
X-Frame-OptionsDENYNo framing.
Permissions-Policymicrophone=(self), camera=(), geolocation=(), payment=(), usb=()Microphone for Valet only. The rest are off.
Cross-Origin-Opener-Policysame-originOther windows can’t get a handle on the page.
Cache-Controlno-store on /api/ by defaultLive data and anything behind sign-in is never kept by a browser or proxy cache.

Pictures through the image proxy carry a tighter policy of their own.

A plugin’s browser module, data/integrations/<kind>.web.js, is served from Valet’s origin, so script-src 'self' allows it. That’s why plugins need your trust. See plugins.

The CSP stops scripts on the page. A check on the server stops other sites. Any request to /api/ that isn’t a GET or HEAD is refused if its Origin doesn’t match the host. Sign-in routes are covered too.

Request bodies are capped before anything reads them: 256 KB, or 8 MB for a chat. Uploads are limited to 50 MB a file.