basecamp/trix · error · TypeError
dirty is not a string, aborting
Error message
dirty is not a string, aborting
What it means
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.
Source
Thrown at action_text-trix/app/assets/javascripts/trix.js:3959
// eslint-disable-next-line complexity
DOMPurify.sanitize = function (dirty) {
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
let body = null;
let importedNode = null;
let currentNode = null;
let returnNode = null;
/* Make sure we have a string to sanitize.
DO NOT return early, as this will return the wrong type if
the user has requested a DOM object rather than a string */
IS_EMPTY_INPUT = !dirty;
if (IS_EMPTY_INPUT) {
dirty = '<!-->';
}
/* Stringify, in case dirty is an object */
if (typeof dirty !== 'string' && !_isNode(dirty)) {
dirty = stringifyValue(dirty);
if (typeof dirty !== 'string') {
throw typeErrorCreate('dirty is not a string, aborting');
}
}
/* Return dirty HTML if DOMPurify cannot run */
if (!DOMPurify.isSupported) {
return dirty;
}
/* Assign config vars */
if (SET_CONFIG) {
/* Persistent setConfig() path: _parseConfig is skipped, so the sets are
* not re-derived per call. Restore them from the pristine bindings
* captured at setConfig() time so a previous call's hook clone (mutated
* below) does not carry over. */
ALLOWED_TAGS = SET_CONFIG_ALLOWED_TAGS;
ALLOWED_ATTR = SET_CONFIG_ALLOWED_ATTR;
} else {
_parseConfig(cfg);
}
/* Clone the hook-mutable allowlists before the walk whenever anView on GitHub (pinned to 4700401311)
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.
Example fix
// before
const clean = DOMPurify.sanitize(userInput); // userInput may be undefined
// after
if (typeof userInput === 'string') {
const clean = DOMPurify.sanitize(userInput);
} else if (userInput instanceof Node) {
const clean = DOMPurify.sanitize(userInput, { RETURN_DOM: true });
} Defensive patterns
Strategy: type-guard
Validate before calling
if (typeof dirty !== 'string' && !(dirty instanceof Node)) {
throw new TypeError('sanitize expects an HTML string or a DOM Node; got ' + typeof dirty);
} Type guard
const isSanitizableInput = (v) => typeof v === 'string' || (v !== null && typeof v === 'object' && v instanceof Node);
Try / catch
let clean;
try {
clean = DOMPurify.sanitize(dirty);
} catch (e) {
if (String(e.message).includes('dirty is not a string')) {
clean = '';
} else { throw e; }
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- TrustedTypes policy ' + policyName + ' could not be created.
- A configured TRUSTED_TYPES_POLICY callback (createHTML or cr
- TRUSTED_TYPES_POLICY configuration option must provide a "cr
- TRUSTED_TYPES_POLICY configuration option must provide a "cr
- a node selected for removal could not be detached from its t
AI-assisted analysis of basecamp/trix@4700401311 (2026-09-02).
Data as JSON: /api/errors/edef35b1bbb3e591.
Report an issue: GitHub.