Files
gitbook/src/lib/csp.ts
T
Samy Pessé 6bd6ddd031 Use an assetPrefix to serve all assets from same URLs (#172)
* Test an assetPrefix

* Fix condition

* Use the env instead

* Use it for CSP
2024-02-19 16:32:33 +01:00

64 lines
2.2 KiB
TypeScript

import { SpaceIntegrationScript } from '@gitbook/api';
import { merge } from 'content-security-policy-merger';
import { headers } from 'next/headers';
import { filterOutNullable } from './typescript';
const assetsDomain = process.env.GITBOOK_ASSETS_PREFIX
? new URL(process.env.GITBOOK_ASSETS_PREFIX).host
: undefined;
/**
* Get the current nonce for the current request.
*/
export function getContentSecurityPolicyNonce(): string {
const headersList = headers();
const nonce = headersList.get('x-nonce');
if (!nonce) {
throw new Error('No nonce found in headers');
}
return nonce;
}
/**
* Create a nonce for a Content Security Policy.
*/
export function createContentSecurityPolicyNonce(): string {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
return nonce;
}
/**
* Generate a Content Security Policy header for a space.
*/
export function getContentSecurityPolicy(scripts: SpaceIntegrationScript[], nonce: string): string {
// We need to allow loading any image or download any file
// to support image and OpenAPI blocks where the content reference could be external.
//
// Since I can't get the nonce to work for inline styles, we need to allow unsafe-inline
const defaultCSP = `
default-src 'self' ${assetsDomain};
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' 'unsafe-inline' 'unsafe-eval' ${assetsDomain} integrations.gitbook.com https://cdn.iframe.ly;
style-src 'self' ${assetsDomain} fonts.googleapis.com 'unsafe-inline';
img-src * 'self' blob: data: files.gitbook.com ${assetsDomain};
connect-src * 'self' integrations.gitbook.com app.gitbook.com ${assetsDomain};
font-src 'self' fonts.gstatic.com ${assetsDomain};
frame-src *;
object-src 'none';
base-uri 'self' ${assetsDomain};
form-action 'self' ${assetsDomain};
frame-ancestors 'none';
`;
const result = scripts
.map(({ contentSecurityPolicy }) => contentSecurityPolicy)
.filter(filterOutNullable)
.reduce((csp, policy) => merge(csp, policy), defaultCSP);
return result
.replace(/\n/g, ' ')
.replace(/\s{2,}/g, ' ')
.trim();
}