{"record":{"id":"edef35b1bbb3e591","repo":"basecamp/trix","slug":"dirty-is-not-a-string-aborting","errorCode":null,"errorMessage":"dirty is not a string, aborting","messagePattern":"dirty is not a string, aborting","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"action_text-trix/app/assets/javascripts/trix.js","lineNumber":3959,"sourceCode":"    // eslint-disable-next-line complexity\n    DOMPurify.sanitize = function (dirty) {\n      let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n      let body = null;\n      let importedNode = null;\n      let currentNode = null;\n      let returnNode = null;\n      /* Make sure we have a string to sanitize.\n        DO NOT return early, as this will return the wrong type if\n        the user has requested a DOM object rather than a string */\n      IS_EMPTY_INPUT = !dirty;\n      if (IS_EMPTY_INPUT) {\n        dirty = '<!-->';\n      }\n      /* Stringify, in case dirty is an object */\n      if (typeof dirty !== 'string' && !_isNode(dirty)) {\n        dirty = stringifyValue(dirty);\n        if (typeof dirty !== 'string') {\n          throw typeErrorCreate('dirty is not a string, aborting');\n        }\n      }\n      /* Return dirty HTML if DOMPurify cannot run */\n      if (!DOMPurify.isSupported) {\n        return dirty;\n      }\n      /* Assign config vars */\n      if (SET_CONFIG) {\n        /* Persistent setConfig() path: _parseConfig is skipped, so the sets are\n         * not re-derived per call. Restore them from the pristine bindings\n         * captured at setConfig() time so a previous call's hook clone (mutated\n         * below) does not carry over. */\n        ALLOWED_TAGS = SET_CONFIG_ALLOWED_TAGS;\n        ALLOWED_ATTR = SET_CONFIG_ALLOWED_ATTR;\n      } else {\n        _parseConfig(cfg);\n      }\n      /* Clone the hook-mutable allowlists before the walk whenever an","sourceCodeStart":3941,"sourceCodeEnd":3977,"githubUrl":"https://github.com/basecamp/trix/blob/470040131122bd44e269b4de0f2e9557f90ec994/action_text-trix/app/assets/javascripts/trix.js#L3941-L3977","documentation":"DOMPurify.sanitize accepts a string or a DOM Node. If the input is neither, the library stringifies it (stringifyValue) and, when the result is still not a string, throws this TypeError and aborts. It refuses to guess at non-stringifiable input rather than sanitizing something undefined.","triggerScenarios":"Calling DOMPurify.sanitize(undefined), sanitize(null), sanitize({ foo: 1 }) when the object has no useful string representation, or sanitize(someFunction) / sanitize(Symbol()) — anything where typeof dirty !== 'string' && !_isNode(dirty) and stringification does not yield a string.","commonSituations":"Passing a variable that is unexpectedly undefined because an async fetch or form field came back empty; passing a jQuery/React wrapper object instead of a raw node or HTML string; passing a number/boolean and expecting DOMPurify to coerce it.","solutions":["Check the input before calling: ensure it is a string or a Node; coerce with String(dirty) if you intentionally want scalar values sanitized.","Fix the upstream source so the variable is defined before sanitize (guard against undefined/null from fetch, state, or DOM reads).","If passing a DOM node, pass the raw Node, not a wrapper (unwrap jQuery with [0], React refs with .current).","Wrap the call in try/catch if input types are dynamic, and handle the abort path explicitly."],"exampleFix":"// before\nconst clean = DOMPurify.sanitize(userInput); // userInput may be undefined\n// after\nif (typeof userInput === 'string') {\n  const clean = DOMPurify.sanitize(userInput);\n} else if (userInput instanceof Node) {\n  const clean = DOMPurify.sanitize(userInput, { RETURN_DOM: true });\n}","handlingStrategy":"type-guard","validationCode":"if (typeof dirty !== 'string' && !(dirty instanceof Node)) {\n  throw new TypeError('sanitize expects an HTML string or a DOM Node; got ' + typeof dirty);\n}","typeGuard":"const isSanitizableInput = (v) => typeof v === 'string' || (v !== null && typeof v === 'object' && v instanceof Node);","tryCatchPattern":"let clean;\ntry {\n  clean = DOMPurify.sanitize(dirty);\n} catch (e) {\n  if (String(e.message).includes('dirty is not a string')) {\n    clean = '';\n  } else { throw e; }\n}","preventionTips":["Coerce scalars explicitly: DOMPurify.sanitize(String(value ?? '')).","Unwrap framework wrappers (jquery[0], ref.current) before passing nodes.","Validate upstream data (fetch/form results) is defined before sanitizing.","Add a boundary function around DOMPurify that enforces string|Node input."],"tags":["dompurify","input-validation","typeerror"],"backgroundTag":"invalid-sanitize-input-type","analyzedSha":"470040131122bd44e269b4de0f2e9557f90ec994","analyzedAt":"2026-09-02T10:19:15.878Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T16:17:10.729Z"}