{"record":{"id":"bc32767aa82bb2a9","repo":"basecamp/trix","slug":"trusted-types-policy-configuration-option-must-pro","errorCode":null,"errorMessage":"TRUSTED_TYPES_POLICY configuration option must provide a \"createHTML\" hook.","messagePattern":"TRUSTED_TYPES_POLICY configuration option must provide a \"createHTML\" hook\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"action_text-trix/app/assets/javascripts/trix.js","lineNumber":2651,"sourceCode":"      /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */\n      if (WHOLE_DOCUMENT) {\n        addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);\n      }\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) {","sourceCodeStart":2633,"sourceCodeEnd":2669,"githubUrl":"https://github.com/basecamp/trix/blob/470040131122bd44e269b4de0f2e9557f90ec994/action_text-trix/app/assets/javascripts/trix.js#L2633-L2669","documentation":"DOMPurify validates a caller-supplied TRUSTED_TYPES_POLICY at _parseConfig time: the object must expose both a createHTML and a createScriptURL function. If TRUSTED_TYPES_POLICY is provided but createHTML is missing or not a function, DOMPurify throws this TypeError rather than silently producing unsigned/unsafe output. This fail-closed check also prevents a stale foreign policy from signing later 'default' results (see GHSA-vxr8-fq34-vvx9).","triggerScenarios":"Calling DOMPurify.addHook-free configuration such as DOMPurify.sanitize(dirty, { TRUSTED_TYPES_POLICY: someObject }) where someObject has no createHTML function (e.g. only createScriptURL, or a TrustedTypes policy object that is null/partially constructed), or after clearConfig() followed by a config supplying only one hook.","commonSituations":"Hand-rolling a policy object and forgetting one of the two required methods; passing a native TrustedTypes policy created with only createHTML; typos in property names (createHtml vs createHTML); passing the wrong variable (a config object instead of a policy).","solutions":["Add a createHTML function to the object passed as TRUSTED_TYPES_POLICY.","Check spelling/casing: the key must be exactly createHTML (and createScriptURL), not createHtml.","If you only need one direction, still provide both methods (unused ones can be identity functions).","Use trustedTypes.createPolicy(...) to build the object, then verify both methods exist before passing it."],"exampleFix":"// before\nDOMPurify.sanitize(dirty, { TRUSTED_TYPES_POLICY: { createScriptURL: (s) => s } });\n// after\nDOMPurify.sanitize(dirty, {\n  TRUSTED_TYPES_POLICY: {\n    createHTML: (s) => s,\n    createScriptURL: (s) => s\n  }\n});","handlingStrategy":"type-guard","validationCode":"if (cfg.TRUSTED_TYPES_POLICY && typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {\n  throw new Error('Refusing to configure: TRUSTED_TYPES_POLICY.createHTML is missing');\n}","typeGuard":"const hasCreateHTML = (p) => !!p && typeof p.createHTML === 'function';","tryCatchPattern":"try {\n  DOMPurify.sanitize(dirty, { TRUSTED_TYPES_POLICY: policy });\n} catch (e) {\n  if (String(e.message).includes('createHTML')) {\n    console.error('Supplied policy lacks createHTML; fix policy object.');\n  } else { throw e; }\n}","preventionTips":["Always build policies with trustedTypes.createPolicy(name, { createHTML, createScriptURL }).","Match exact casing: createHTML and createScriptURL.","Validate the policy object in a wrapper around all DOMPurify.sanitize calls.","Re-verify the policy after clearConfig() or config swaps."],"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"}