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.

  1. 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; null throws (avoid the null/undefined footgun where a conditional silently disables defaults).
  2. extend — sources to append per directive, layered left to right. Values append (and deduplicate) to the result of replace_defaults and 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.
  3. overrides — final-pass per-directive replace or remove. Highest precedence. Pass null to 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, or overrides throw with the offending name.
  • Extending a directive whose current value is ['none'] throws — opt in via replace_defaults or overrides instead.
  • null for replace_defaults (top-level or per-key) throws — omit the option for library defaults, pass {} to start blank, or use overrides to remove a specific directive.
  • null per-key in extend throws with a pointer to overridesextend only appends, so removal lives on overrides.
  • undefined per-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 native TypeError.
  • 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 to default-src, widening the policy.
  • Source arrays are validated to contain only strings — non-string elements (slipped through via as any) would render as undefined or [object Object] in the emitted header.

Directive specs
#

The exported csp_directive_specs has JSON data about the CSP directives. Fuz omits deprecated directives.

directivefallbackfallback of
default-srcscript-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-srcdefault-srcscript-src-elem, script-src-attr, worker-src
script-src-elemscript-src, default-src
script-src-attrscript-src, default-src
style-srcdefault-srcstyle-src-elem, style-src-attr
style-src-elemstyle-src, default-src
style-src-attrstyle-src, default-src
img-srcdefault-src
media-srcdefault-src
font-srcdefault-src
manifest-srcdefault-src
child-srcdefault-srcframe-src, worker-src
connect-srcdefault-src
frame-srcchild-src
frame-ancestors
form-action
worker-srcchild-src, script-src, default-src
object-srcdefault-src
base-uri
upgrade-insecure-requests
report-to
require-trusted-types-for
trusted-types
sandbox