Posting here in case anyone else comes across this. We have a custom express server serving both Payload and our frontend app. Payload admin is available via
/admin
The frontend app and Payload have quite different CSP settings. If you're using
https://helmetjs.github.io/- then you can chain helmet middleware and return different policies depending on the request path. For example...
// CSP policy via helmet
app.use(function (req, res, next) {
let middleware
// If we've mounted Payload CMS on /admin - change the CSP to suit.
if (req.path.startsWith('/admin')) {
middleware = helmet({
contentSecurityPolicy: {
'script-src': [
"'self'"
],
'img-src': ["'self'", 'data:', 'cdn.yourcdn.com'],
'media-src': ["'self'", 'data:', 'cdn.yourcdn.com'],
'default-src': ["'self'"]
}
})
} else {
middleware = helmet({
crossOriginEmbedderPolicy: false,
contentSecurityPolicy: {
// NOTE: Remove reportOnly when you're ready to enforce this CSP
// reportOnly: true,
directives: {
'connect-src': [
ENVIRONMENT === 'development' ? 'ws:' : null,
"'self'",
].filter(Boolean),
'script-src': [
"'strict-dynamic'",
"'self'", // Ignored by CSP 3 compliant browsers when strict-dynamic - here for backwards compat.
"'unsafe-inline'", // Ignored by CSP 3 compliant browsers when strict-dynamic - here for backwards compat.
'https:', // Ignored by CSP 3 compliant browsers when strict-dynamic - here for backwards compat.
'http:', // Ignored by CSP 3 compliant browsers when strict-dynamic - here for backwards compat.
(_, res) => `'nonce-${res.locals.cspNonce}'`
],
'script-src-attr': [
(_, res) => `'nonce-${res.locals.cspNonce}'`
],
'font-src': ["'self'"],
'frame-src': ["'self'"],
'img-src': ["'self'", 'data:', 'cdn.yourcdn.com'],
'media-src': ["'self'", 'data:', 'cdn.yourcdn.com'],
'default-src': ["'self'"],
'upgrade-insecure-requests': null
}
}
})
}
middleware(req, res, next)
})
Hope this helps ;-)
Star
Discord
online
Get dedicated engineering support directly from the Payload team.