{"record":{"id":"80d72df931414098","repo":"basecamp/trix","slug":"trusted-types-policy-configuration-option-must-pro-80d72d","errorCode":null,"errorMessage":"TRUSTED_TYPES_POLICY configuration option must provide a \"createScriptURL\" hook.","messagePattern":"TRUSTED_TYPES_POLICY configuration option must provide a \"createScriptURL\" hook\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"action_text-trix/app/assets/javascripts/trix.js","lineNumber":2654,"sourceCode":"      }\n      /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */\n      if (ALLOWED_TAGS.table) {\n        addToSet(ALLOWED_TAGS, ['tbody']);\n        delete FORBID_TAGS.tbody;\n      }\n      // Re-derive the active Trusted Types policy from this configuration on\n      // every parse. The active policy must never be sticky closure state that\n      // outlives the config that set it: a caller-supplied policy left in place\n      // after `clearConfig()` — or after a later call that supplied none, or\n      // `TRUSTED_TYPES_POLICY: null` — could sign a subsequent \"default\"\n      // `RETURN_TRUSTED_TYPE` result with a foreign, possibly unsafe policy.\n      // See GHSA-vxr8-fq34-vvx9.\n      if (cfg.TRUSTED_TYPES_POLICY) {\n        if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {\n          throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a \"createHTML\" hook.');\n        }\n        if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {\n          throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a \"createScriptURL\" hook.');\n        }\n        // A caller-supplied policy applies to this configuration only.\n        const previousTrustedTypesPolicy = trustedTypesPolicy;\n        trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;\n        // Sign local variables required by `sanitize`. If the supplied policy's\n        // `createHTML` is circular (i.e. it calls `DOMPurify.sanitize`), this\n        // throws via the re-entrancy guard. Restore the previous policy first so\n        // the instance is not left in a poisoned state. See #1422.\n        try {\n          emptyHTML = _createTrustedHTML('');\n        } catch (error) {\n          trustedTypesPolicy = previousTrustedTypesPolicy;\n          throw error;\n        }\n      } else if (cfg.TRUSTED_TYPES_POLICY === null) {\n        // Explicit opt-out for this call: perform no Trusted Types signing and\n        // create nothing (so a strict `trusted-types` CSP that disallows a\n        // `dompurify` policy can still call `sanitize` from inside its own","sourceCodeStart":2636,"sourceCodeEnd":2672,"githubUrl":"https://github.com/basecamp/trix/blob/470040131122bd44e269b4de0f2e9557f90ec994/action_text-trix/app/assets/javascripts/trix.js#L2636-L2672","documentation":"Companion check to the createHTML requirement: when a TRUSTED_TYPES_POLICY is supplied in the config, DOMPurify also requires a createScriptURL function. The policy is used to sign both HTML output and script URLs (e.g. for allowed src attributes), so a missing createScriptURL would leave script URL sinks unsigned or unsafe. DOMPurify throws this TypeError at configuration time, fail-closed.","triggerScenarios":"DOMPurify.sanitize(dirty, { TRUSTED_TYPES_POLICY: { createHTML: fn } }) — a policy object providing only createHTML, or a natively created policy object whose createScriptURL is absent or not a function.","commonSituations":"Copy-pasting a minimal policy example that only handles HTML; building a policy for a browser where you thought script URLs were irrelevant but DOMPurify still validates both; property name typos (createScriptUrl).","solutions":["Add a createScriptURL function to the policy object (an identity or allow-list-based signer is fine).","Verify the key spelling is exactly createScriptURL.","If you never load scripts from sanitized content, still supply a strict createScriptURL that throws or returns only known-safe URLs.","Prefer trustedTypes.createPolicy(name, {createHTML, createScriptURL}) so both methods always exist."],"exampleFix":"// before\nDOMPurify.sanitize(dirty, { TRUSTED_TYPES_POLICY: { createHTML: (s) => s } });\n// after\nDOMPurify.sanitize(dirty, {\n  TRUSTED_TYPES_POLICY: {\n    createHTML: (s) => s,\n    createScriptURL: (url) => ALLOWED_URLS.includes(url) ? url : 'about:blank'\n  }\n});","handlingStrategy":"type-guard","validationCode":"if (cfg.TRUSTED_TYPES_POLICY && typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {\n  throw new Error('Refusing to configure: TRUSTED_TYPES_POLICY.createScriptURL is missing');\n}","typeGuard":"const hasCreateScriptURL = (p) => !!p && typeof p.createScriptURL === 'function';","tryCatchPattern":"try {\n  DOMPurify.sanitize(dirty, { TRUSTED_TYPES_POLICY: policy });\n} catch (e) {\n  if (String(e.message).includes('createScriptURL')) {\n    console.error('Supplied policy lacks createScriptURL; fix policy object.');\n  } else { throw e; }\n}","preventionTips":["Supply both hooks even if scripts are irrelevant; use a strict createScriptURL that rejects unknown URLs.","Build policies via trustedTypes.createPolicy so both methods are declared together.","Check key spelling: createScriptURL (capital URL).","Add a unit test asserting the policy object satisfies both typeof checks before configuration."],"tags":["dompurify","trusted-types","configuration","validation"],"backgroundTag":"invalid-trusted-types-policy","analyzedSha":"470040131122bd44e269b4de0f2e9557f90ec994","analyzedAt":"2026-09-02T10:19:15.878Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T16:17:10.729Z"}