#5953·node-red

cloneMessage does not isolate Buffer payloads when a message fans out

Author: dimitriehCreated Sep 14, 2026Updated Sep 14, 2026

Current Behavior

When a node output is wired to more than one destination, the first destination receives the original message and each later one receives redUtil.cloneMessage(msg). That clone is what keeps the branches independent.

cloneMessage deep-clones with lodash.clonedeep, which clones a Buffer using buffer.slice(). buffer.slice() returns a view over the same memory rather than a copy, so the clone shares its bytes with the original. Two branches that mutate a Buffer payload in place therefore corrupt each other, silently and with no error raised.

Only Buffer is affected. Uint8Array, Float64Array, ArrayBuffer and DataView are all copied correctly by lodash.

The clone itself:

javascript
const clonedeep = require("lodash.clonedeep")
const orig = { payload: Buffer.from([1, 2, 3, 4]) }
const copy = clonedeep(orig)
copy.payload[0] = 99
orig.payload[0]                               // 99
orig.payload.buffer === copy.payload.buffer   // true

Expected Behavior

A cloned message owns its own bytes. Mutating a Buffer payload in one branch leaves the other branch untouched.

Steps To Reproduce

  1. Import the flow below and deploy.
  2. It injects once on start, builds Buffer.from([1,2,3,4]), and fans that message out to two function nodes.
  3. Branch A does msg.payload[0] = 99. Branch B only reads its own copy.

Branch B logs B SEES 99. It should log B SEES 1, since B holds a clone.

Example flow

[{"id":"t1","type":"tab","label":"buffer fan-out repro"},{"id":"inj","type":"inject","z":"t1","name":"start","props":[{"p":"payload"}],"payload":"","payloadType":"date","once":true,"onceDelay":0.2,"x":110,"y":80,"wires":[["mk"]]},{"id":"mk","type":"function","z":"t1","name":"make buffer","func":"msg.payload = Buffer.from([1,2,3,4]);\nreturn msg;","outputs":1,"x":260,"y":80,"wires":[["mutate","read"]]},{"id":"mutate","type":"function","z":"t1","name":"A mutates in place","func":"msg.payload[0] = 99;\nreturn msg;","outputs":1,"x":460,"y":50,"wires":[[]]},{"id":"read","type":"function","z":"t1","name":"B reads its own copy","func":"node.warn('B SEES ' + msg.payload[0] + ' (expected 1)');\nreturn msg;","outputs":1,"x":470,"y":110,"wires":[[]]}]

Observed on current main:

[warn] [function:B reads its own copy] B SEES 99 (expected 1)

Environment

  • Node-RED version: main at d9644c41b (5.0.7)
  • Node.js version: v22.22.1 and v26.0.0, same result on both
  • npm version: 10.9.8
  • Platform/OS: Linux container and macOS
  • Browser: not applicable, this is runtime side

Note on disclosure

Raising this in public rather than through the security process: triggering it requires flow-authoring rights, which already allow arbitrary code in a function node, so no privilege boundary is crossed. The impact is integrity only, within a single trust domain, and an external sender cannot steer the corruption. If the project reads it differently, it can move to [email protected].