{"record":{"id":"4996439b4751ecde","repo":"ianstormtaylor/slate","slug":"index-must-be-number","errorCode":null,"errorMessage":"Index must be number","messagePattern":"Index must be number","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/slate/src/interfaces/transforms/general.ts","lineNumber":105,"sourceCode":"        transformSelection = true\n        break\n      }\n\n      case 'merge_node': {\n        const { path } = op\n        const index = path[path.length - 1]\n        const prevPath = Path.previous(path)\n        const prevIndex = prevPath[prevPath.length - 1]\n\n        if (path.length === 0) {\n          throw new Error(\n            `Cannot apply a \"merge_node\" operation at path [${path}] because the root node cannot be merged.`\n          )\n        }\n\n        // Defend against malicious paths containing strings\n        if (typeof index !== 'number' || typeof prevIndex !== 'number')\n          throw new Error('Index must be number')\n\n        modifyChildren(editor, Path.parent(path), children => {\n          const node = children[index]\n          const prev = children[prevIndex]\n          let newNode: Descendant\n\n          if (Node.isText(node) && Node.isText(prev)) {\n            newNode = { ...prev, text: prev.text + node.text }\n          } else if (Node.isElement(node) && Node.isElement(prev)) {\n            newNode = { ...prev, children: prev.children.concat(node.children) }\n          } else {\n            throw new Error(\n              `Cannot apply a \"merge_node\" operation at path [${path}] to nodes of different interfaces: ${Scrubber.stringify(\n                node\n              )} ${Scrubber.stringify(prev)}`\n            )\n          }\n","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/ianstormtaylor/slate/blob/72a37c701e5da4bb13a305d416d9c070d19d3a45/packages/slate/src/interfaces/transforms/general.ts#L87-L123","documentation":"A defensive check inside merge_node: the final index of the path (and of the previous sibling's path) must be a number. It throws when the operation's path contains a string segment, as can happen with deserialized or maliciously crafted operations.","triggerScenarios":"Applying a merge_node whose path contains non-numeric segments, e.g. path: ['a', 1]; typically from JSON.parse'd operations from untrusted sources, deserialized drafts, or a bug building paths from template strings.","commonSituations":"Loading operation logs from localStorage/backend where a path got stringified; accepting operations from a collaborative peer without validation; constructing paths with template literals ('0' instead of 0).","solutions":["Validate/normalize operation paths before applying: ensure every segment is a number.","Sanitize deserialized operations with a schema check (e.g. zod) that coerces or rejects string segments.","Fix the code building the path — avoid template literals that yield strings."],"exampleFix":"// before\nconst op = JSON.parse(saved) // path segments may be strings\nEditor.apply(editor, op)\n\n// after\nconst op = JSON.parse(saved)\nif (!op.path?.every((n: unknown) => typeof n === 'number')) {\n  throw new Error('Invalid operation path')\n}\nEditor.apply(editor, op)","handlingStrategy":"validation","validationCode":"const valid = op.path.every((n: unknown) => typeof n === 'number')\nif (valid) Editor.apply(editor, op)","typeGuard":"function isNumericPath(path: unknown[]): path is Path {\n  return path.every(n => typeof n === 'number' && Number.isInteger(n) && n >= 0)\n}","tryCatchPattern":"try {\n  Editor.apply(editor, op)\n} catch (e) {\n  if (e instanceof Error && e.message === 'Index must be number') {\n    // reject/sanitize the incoming operation\n  } else throw e\n}","preventionTips":["Schema-validate deserialized operations (e.g. zod) before applying.","Never build paths with template literals; keep segments as numbers.","Treat peer-supplied operations as untrusted input."],"tags":["slate","merge-node","validation","untrusted-input"],"backgroundTag":"operation-validation-failed","analyzedSha":"72a37c701e5da4bb13a305d416d9c070d19d3a45","analyzedAt":"2026-08-27T23:04:51.145Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}