csp #
Fuz supports SvelteKit's config for Content Security Policies with the create_csp_directives helper. Fuz also provides related helpers, types, and CSP data.
The API is designed to read as an audit log: every user-added source is named at exactly one
site in the source code. There's no implicit promotion of sources across directives — if you
want a domain on script-src, you write script-src. Library defaults
are inherited unless you opt out via replace_defaults.
Example usage:
import {create_csp_directives, type CspDirectives} from '@fuzdev/fuz_ui/csp.js';
// Default CSP — restrictive defaults from `csp_directive_value_defaults`.
const csp = create_csp_directives();
// Use in svelte.config.js:
// export default {kit: {csp: {directives: csp}}}
// Layer in your own sources per directive:
const csp_with_sources = create_csp_directives({
extend: [
{
'img-src': ['https://*.my.domain/'],
'connect-src': ['https://api.my.domain/'],
// Putting a source on script-src requires naming script-src here.
'script-src': ['https://cdn.my.domain/'],
},
],
});
// Compose multiple "shared lib" objects (e.g. a vendor's directive map plus your own):
import {csp_directives_of_fuzdev} from '@fuzdev/fuz_ui/csp_of_fuzdev.js';
const csp_composed = create_csp_directives({
extend: [
csp_directives_of_fuzdev,
{'connect-src': ['https://api.my.domain/']},
],
});
// Replace a directive wholesale via the final-pass `overrides`:
const csp_replaced = create_csp_directives({
extend: [{'connect-src': ['https://api.my.domain/']}],
overrides: {
// Wins over extend; `null` removes a directive entirely.
'frame-src': ['none'],
'report-to': null,
},
});
// Start from your own defaults instead of the library defaults:
const csp_custom_defaults = create_csp_directives({
replace_defaults: {
'default-src': ['none'],
'script-src': ['self'],
'connect-src': ['self', 'https://api.my.domain/'],
},
// `extend` and `overrides` still layer on top.
});
// Start blank — fully declarative, no library defaults at all:
const csp_blank = create_csp_directives({
replace_defaults: {},
overrides: {
'script-src': ['self'],
'img-src': ['self', 'data:'],
},
});Pipeline #
Three stages run in order. Each is independent — use the one that matches your intent.
- CreateCspDirectivesOptions
replace_defaults— the starting state. Omitted, it's csp_directive_value_defaults. Provided, it replaces the library defaults wholesale — exactly the directives you list, nothing inherited.{}starts blank;nullthrows (avoid the null/undefined footgun where a conditional silently disables defaults). extend— sources to append per directive, layered left to right. Values append (and deduplicate) to the result ofreplace_defaultsand prior entries. Boolean directives (e.g.upgrade-insecure-requests) are excluded by the type — only array-typed directives can be extended. Compose multiple shared maps in one array.overrides— final-pass per-directive replace or remove. Highest precedence. Passnullto drop a directive from the output entirely.
Adding sources via extend #
extend is the common path: take a starting state and add per-directive sources. Sources
land only on the directives you name — there's no cross-directive promotion.
create_csp_directives({
extend: [
{
'img-src': ['https://cdn.example.com/'],
'connect-src': ['https://api.example.com/'],
},
],
}); Multiple entries compose left to right, deduplicating across layers. Useful for combining a "shared lib" object with app-specific extras:
import {csp_directives_of_fuzdev} from '@fuzdev/fuz_ui/csp_of_fuzdev.js';
create_csp_directives({
extend: [
csp_directives_of_fuzdev,
{
'connect-src': ['https://api.example.com/'],
'img-src': ['https://media.example.com/'],
},
],
}); Default-deny directives (those whose default value is ['none'] — including default-src, object-src, base-uri, script-src-attr, and child-src) cannot be extended. Attempting to extend them throws — opting in must go through replace_defaults or overrides so the opt-in is visible at the call site. Note that overrides cannot rescue an extend for a default-deny directive in
the same call: extend runs first and throws before overrides would replace the
value. Move the sources into overrides directly, or opt in via replace_defaults and then extend.
Replacing values via overrides #
The final-pass overrides option replaces a directive's value or removes it
entirely. Highest precedence — wins over replace_defaults and extend.
create_csp_directives({
extend: [{'connect-src': ['https://api.example.com/']}],
overrides: {
// Wholesale replace — drops the connect-src extend output above.
'connect-src': ['self'],
// Remove a directive entirely from the output.
'report-to': null,
// Set a default-deny directive's value explicitly.
'object-src': ['none'],
// Boolean directives are supported.
'upgrade-insecure-requests': false,
},
});Custom defaults via replace_defaults #
replace_defaults sets the starting state. The default is the library's curated csp_directive_value_defaults. To use your own foundation, pass a
complete map. Anything you don't list is absent from the starting state —
including security defaults like default-src: ['none']. Use extend and overrides for per-directive tweaks while keeping the library defaults.
// Fully declarative — no library defaults at all.
const csp = create_csp_directives({
replace_defaults: {
'default-src': ['none'],
'script-src': ['self'],
'connect-src': ['self'],
},
});
// assert.deepEqual(csp, {
// 'default-src': ['none'],
// 'script-src': ['self'],
// 'connect-src': ['self'],
// });
// Same shape as a hand-written directives map — still gets input and output validation.
create_csp_directives({replace_defaults: {}, overrides: {/* ... */}}); Use overrides for tweaks (replace one directive while keeping the library
defaults), and replace_defaults for full ownership of the starting state. null is rejected (top-level or per-key) — omit the option for library defaults,
pass {} to start blank, or use overrides to remove a specific directive.
Validation #
create_csp_directives validates inputs and outputs at build time. Misconfigurations throw rather than producing a silently broken policy.
- Unknown directive keys in any of
replace_defaults,extend, oroverridesthrow with the offending name. - Extending a directive whose current value is
['none']throws — opt in viareplace_defaultsoroverridesinstead. nullforreplace_defaults(top-level or per-key) throws — omit the option for library defaults, pass{}to start blank, or useoverridesto remove a specific directive.nullper-key inextendthrows with a pointer tooverrides—extendonly appends, so removal lives onoverrides.undefinedper-key in any of the three stages is treated as omitted (no-op). This lets conditional patterns like{'connect-src': is_prod ? [API_URL] : undefined}work naturally.- Non-object entries in
extend(e.g.extend: [undefined]) throw a library error pointing at the option, instead of a cryptic nativeTypeError. - The output is validated to ensure
'none'never appears alongside other tokens (an invalid CSP that browsers reject). - The output is validated to ensure no directive ends up with an empty array — use
['none']to forbid all sources, or omit the directive entirely. Empty arrays can be silently dropped or fall back todefault-src, widening the policy. - Source arrays are validated to contain only strings — non-string elements (slipped through
via
as any) would render asundefinedor[object Object]in the emitted header.
Directive specs #
The exported csp_directive_specs has JSON data about the CSP directives. Fuz omits deprecated directives.
| directive | fallback | fallback of |
|---|---|---|
| default-src | script-src, script-src-elem, script-src-attr, style-src, style-src-elem, style-src-attr, img-src, media-src, font-src, manifest-src, child-src, connect-src, worker-src, object-src | |
| script-src | default-src | script-src-elem, script-src-attr, worker-src |
| script-src-elem | script-src, default-src | |
| script-src-attr | script-src, default-src | |
| style-src | default-src | style-src-elem, style-src-attr |
| style-src-elem | style-src, default-src | |
| style-src-attr | style-src, default-src | |
| img-src | default-src | |
| media-src | default-src | |
| font-src | default-src | |
| manifest-src | default-src | |
| child-src | default-src | frame-src, worker-src |
| connect-src | default-src | |
| frame-src | child-src | |
| frame-ancestors | ||
| form-action | ||
| worker-src | child-src, script-src, default-src | |
| object-src | default-src | |
| base-uri | ||
| upgrade-insecure-requests | ||
| report-to | ||
| require-trusted-types-for | ||
| trusted-types | ||
| sandbox |