CSP Header Builder With Nonces
Assemble a Content-Security-Policy header from safe defaults, merge custom directives and inject a per-request base64 nonce.
Code
export type DirectiveValue = readonly string[];
export interface CspOptions {
readonly directives?: Readonly<Record<string, DirectiveValue>>;
readonly reportOnly?: boolean;
readonly nonce?: string;
}
const BASE_DIRECTIVES: Record<string, readonly string[]> = {
"default-src": ["'self'"],
"script-src": ["'self'"],
"style-src": ["'self'"],
"img-src": ["'self'", "data:"],
"object-src": ["'none'"],
"base-uri": ["'self'"],
"frame-ancestors": ["'none'"],
};
export function createNonce(): string {
const bytes = new Uint8Array(16);
globalThis.crypto.getRandomValues(bytes);
let binary = "";
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary);
}
export function buildCsp(options: CspOptions = {}): { name: string; value: string } {
const directives: Record<string, readonly string[]> = { ...BASE_DIRECTIVES };
if (options.nonce !== undefined) {
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(options.nonce)) throw new Error("nonce must be base64");
directives["script-src"] = [...(directives["script-src"] ?? []), `'nonce-${options.nonce}'`];
}
for (const [name, values] of Object.entries(options.directives ?? {})) {
if (!/^[a-z][a-z0-9-]*$/.test(name)) throw new Error(`invalid directive name "${name}"`);
for (const value of values) {
if (typeof value !== "string" || /[;\s]/.test(value)) {
throw new Error(`invalid value for "${name}"`);
}
}
directives[name] = values;
}
const serialized = Object.entries(directives)
.filter(([, values]) => values.length > 0)
.map(([name, values]) => `${name} ${values.join(" ")}`)
.join("; ");
return {
name: options.reportOnly === true ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy",
value: serialized,
};
}
const header = buildCsp({
nonce: createNonce(),
directives: {
"connect-src": ["'self'", "https://api.example.com"],
"img-src": ["'self'", "data:", "https://cdn.example.com"],
},
});
console.log(header.name);
console.log(header.value);
console.log(buildCsp({ reportOnly: true }).name);Report a problem with this exampleOpens a pre-filled issue on GitHub.