{"record":{"id":"c87b89a54e677b80","repo":"withastro/astro","slug":"a-resource-was-added-to-specific-but-gene","errorCode":null,"errorMessage":"A resource was added to `${specific}`, but `${general}` also defines custom resources (${defaultResources.join(' ')}). Because `${specific}` overrides `${general}` for its scope (browsers do not fall back), those resources will not apply there. Add them to `${specific}` as well if needed.","messagePattern":"A resource was added to `(.+?)`, but `(.+?)` also defines custom resources \\((.+?)\\)\\. Because `(.+?)` overrides `(.+?)` for its scope \\(browsers do not fall back\\), those resources will not apply there\\. Add them to `(.+?)` as well if needed\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/astro/src/core/fetch/fetch-state.ts","lineNumber":696,"sourceCode":"\t\t\t\tfamily === 'script' ? state.result.scriptDirective : state.result.styleDirective;\n\t\t\t// Astro's element hashes are folded into the `-elem` directive automatically, so the\n\t\t\t// footgun is specifically user-provided `default`-kind resources on the general directive,\n\t\t\t// which do NOT carry over to the more specific directive.\n\t\t\tconst defaultResources = directive.resources\n\t\t\t\t.map(normalizeCspResourceEntry)\n\t\t\t\t.filter((entry) => entry.kind === 'default')\n\t\t\t\t.map((entry) => entry.resource);\n\t\t\tif (defaultResources.length === 0) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst key = `${family}:${kind}`;\n\t\t\tif (warnedFallback.has(key)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\twarnedFallback.add(key);\n\t\t\tconst general = `${family}-src`;\n\t\t\tconst specific = `${general}-${kind === 'element' ? 'elem' : 'attr'}`;\n\t\t\tstate.logger.warn(\n\t\t\t\t'csp',\n\t\t\t\t`A resource was added to \\`${specific}\\`, but \\`${general}\\` also defines custom resources (${defaultResources.join(\n\t\t\t\t\t' ',\n\t\t\t\t)}). Because \\`${specific}\\` overrides \\`${general}\\` for its scope (browsers do not fall back), those resources will not apply there. Add them to \\`${specific}\\` as well if needed.`,\n\t\t\t);\n\t\t};\n\t\treturn {\n\t\t\tinsertDirective(payload) {\n\t\t\t\tif (state.result) {\n\t\t\t\t\tstate.result.directives = pushDirective(state.result.directives, payload);\n\t\t\t\t}\n\t\t\t},\n\t\t\tinsertScriptResource(payload) {\n\t\t\t\tif (!state.result) return;\n\t\t\t\twarnFallback('script', normalizeCspResourceEntry(payload).kind);\n\t\t\t\tstate.result.scriptDirective.resources.push(payload);\n\t\t\t},\n\t\t\tinsertStyleResource(payload) {","sourceCodeStart":678,"sourceCodeEnd":714,"githubUrl":"https://github.com/withastro/astro/blob/e294953aa8aadd98d5be92e60a03037b05dbdfd4/packages/astro/src/core/fetch/fetch-state.ts#L678-L714","documentation":"In Content-Security-Policy, specific directives (script-src-elem/-attr, style-src-elem/-attr) fully replace the general directive (script-src/style-src) for their scope — browsers do not merge or fall back. When a resource is added to a specific directive while the general one also lists custom default resources, Astro warns (deduped once per family+kind per request via the warnedFallback set) that the general resources will not apply in that scope.","triggerScenarios":"security.csp configures custom resources on the general script-src/style-src directive (kind 'default'), and route code or config also adds resources with kind 'elem' or 'attr' — the specific directive then overrides the general one with no fallback.","commonSituations":"Mixing directive levels when allowing third-party scripts; configs that grew defaults first and per-element/per-attribute additions later; migrating policies copied from other frameworks.","solutions":["Add the general directive's resources to the specific directive as well (repeat them for the elem/attr kinds)","Or drop the custom defaults from the general directive so only the specific one carries resources","Inspect the rendered Content-Security-Policy header to confirm the effective policy after the change"],"exampleFix":"// before — defaults on script-src get shadowed by script-src-elem additions\nsecurity: {\n  csp: {\n    scriptDirective: {\n      resources: [\n        { value: \"'self'\" },                         // kind 'default'\n        { value: 'https://cdn.example.com', kind: 'elem' },\n      ],\n    },\n  },\n}\n\n// after — repeat the defaults for the elem scope\nsecurity: {\n  csp: {\n    scriptDirective: {\n      resources: [\n        { value: \"'self'\" },\n        { value: \"'self'\", kind: 'elem' },\n        { value: 'https://cdn.example.com', kind: 'elem' },\n      ],\n    },\n  },\n}","handlingStrategy":"validation","validationCode":"// mirror the browser's no-fallback rule before shipping CSP config\nfunction checkCspShadowing(cfg) {\n  for (const [dir, family] of [[cfg.scriptDirective, 'script'], [cfg.styleDirective, 'style']]) {\n    const defaults = dir?.resources?.filter((r) => r.kind === undefined || r.kind === 'default') ?? [];\n    const specifics = dir?.resources?.filter((r) => r.kind === 'elem' || r.kind === 'attr') ?? [];\n    if (defaults.length && specifics.length) {\n      console.warn(`${family}-src resources (${defaults.map((r) => r.value).join(' ')}) are shadowed by ${family}-src-elem/-attr`);\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pick one directive level per family: either the general directive or the elem/attr ones, not both","When both levels exist, repeat the general resources on the specific directive","Validate the final Content-Security-Policy header with a CSP evaluator in your test suite"],"tags":["csp","security","directives"],"backgroundTag":"csp-directive-fallback-shadowing","analyzedSha":"e294953aa8aadd98d5be92e60a03037b05dbdfd4","analyzedAt":"2026-08-18T18:48:03.901Z","contentChangedAt":"2026-08-18T18:48:03.901Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}