Site builder: security & data-loss hardening + unified asset picker + audit backlog #3

Merged
jknapp merged 61 commits from builder-hardening-2026-07 into main 2026-07-13 01:13:28 +00:00
2 changed files with 25 additions and 1 deletions
Showing only changes of commit 1a88baa95d - Show all commits
+17
View File
@@ -129,4 +129,21 @@ describe('regenerateTreeIds', () => {
regenerateTreeIds(input);
expect(JSON.parse(JSON.stringify(input))).toEqual(snapshot);
});
test('output node data.props is a deep clone, not shared with the input node', () => {
const input = makeTree();
input.nodes['root-1'].data.props = { alignment: 'left' };
const output = regenerateTreeIds(input);
const outputRoot = output.nodes[output.rootNodeId];
// Clone must preserve content at the time of cloning.
expect(outputRoot.data.props).toEqual({ alignment: 'left' });
// Mutating the output's props (simulating Craft.js's in-place setProp)
// must NOT affect the input node's props object.
(outputRoot.data.props as { alignment: string }).alignment = 'right';
expect(input.nodes['root-1'].data.props).toEqual({ alignment: 'left' });
});
});
+8 -1
View File
@@ -43,11 +43,18 @@ export function regenerateTreeIds(tree: NodeTree): NodeTree {
newLinkedNodes[slot] = remapId(linkedId);
}
// Deep-clone data so mutable sub-objects (props, custom, etc.) are never
// shared by reference between the original node and its regenerated
// copy. Craft.js's setProp mutates data.props in place, so a shallow
// copy here would let edits to the duplicate silently corrupt the
// original.
const clonedData = structuredClone(oldNode.data);
const newNode: Node = {
...oldNode,
id: newId,
data: {
...oldNode.data,
...clonedData,
parent: newParent,
nodes: (oldNode.data.nodes || []).map(remapId),
linkedNodes: newLinkedNodes,