fix(editor): accept plugin node kinds in the scene save API#490
fix(editor): accept plugin node kinds in the scene save API#490konevenkatesh wants to merge 1 commit into
Conversation
Nodes from registered plugins (namespaced type, e.g. trees:tree) failed apiGraphSchema because the static AnyNode union doesn't know them, and LevelNode.children only accepts builtin typed-id patterns — so placing any Nature item made every scene autosave fail with 400. Plugin kinds now validate against the BaseNode envelope plus a deep scan rejecting dangerous URL schemes (javascript:, file:, data:text/…), preserving the SSRF/script-URL posture without importing renderer code into the API route. Builtin containers are validated with plugin child ids filtered from a copy; the stored graph is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit dbbfb0d. Configure here.
| path: (string | number)[], | ||
| ): (string | number)[] | null { | ||
| if (typeof value === 'string') { | ||
| return DANGEROUS_STRING.test(value) ? path : null |
There was a problem hiding this comment.
Plugin URL scan misses SSRF
Medium Severity
DANGEROUS_STRING only denylists javascript/vbscript/file/ftp and non-image data:, so plugin nodes can persist non-loopback http:// (and ws:) URLs that AssetUrl rejects on builtin fields. Combined with .passthrough(), crafted fields like config.textures[].src reopen the Phase 3 client-side beacon/SSRF class the comments claim this path preserves.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit dbbfb0d. Configure here.


Problem
Placing any plugin node (e.g.
trees:treefrom the first-party Nature plugin) in a saved scene makes every subsequent autosave fail with 400.Two validation gaps in
apiGraphSchema(apps/editor/lib/graph-schema.ts):AnyNodeunion, which doesn't include namespaced plugin kinds — the plugin node itself is rejected (Invalid enum value. Expected 'wall' | …, received 'trees:tree').LevelNode.childrenonly accepts builtin typed-id patterns, so a level containing atree_…child id fails too.Repro: run
apps/editor, create a saved scene, place a tree from the Nature panel, watchPUT /api/scenes/[id]return 400 (the editor shows a persistent save error and no further changes persist).Fix
typematches a namespaced plugin kind (ns:kind) validate against theBaseNodeenvelope plus a deep scan that rejects dangerous URL schemes (javascript:,vbscript:,file:,ftp:, non-imagedata:) anywhere in the node. This preserves the Phase 3 SSRF / script-URL posture without importing plugin/renderer code into the API route (plugin schemas live in packages that pull in UI code).AnyNodewith plugin child ids filtered from a copy — the stored graph is unchanged (a regression test pins this).Verification
apps/editor/lib/graph-schema.test.ts(5 of 8 fail without the fix): plugin node accepted, level-with-plugin-child accepted, parsed graph keeps plugin child ids, bad envelope rejected, dangerous URL schemes rejected (withdata:image/…still allowed), invalid builtin nodes and non-namespaced unknown types still rejected.PUT /api/scenes/[id]returns 200 and the stored graph contains thetrees:treenode. Replaying the identical payload against the pre-fix schema returns 400.bun test apps/editor/lib/16/16 pass;biome checkclean; no new type errors.🤖 Generated with Claude Code
Note
Medium Risk
Changes untrusted graph validation on POST/PUT scene APIs; the new plugin path relies on envelope + heuristic URL scanning instead of full
AnyNode/AssetUrlrules, so correctness of that scan matters for SSRF/script-URL posture.Overview
Fixes 400 autosave failures when saved scenes contain plugin nodes (e.g.
trees:tree) by extendingapiGraphSchemaso namespaced plugin kinds are no longer rejected by the staticAnyNodeunion.Plugin nodes now validate against a
BaseNodeenvelope plus a recursive scan that blocks dangerous URL schemes (javascript:,file:, non-imagedata:, etc.) without importing full plugin schemas into API routes. Builtin nodes still useAnyNode, but plugin child ids are removed only on a validation copy so levels with plugin children pass while the stored graph keeps those child ids.Adds regression tests for acceptance, child-id preservation, URL allow/deny rules, and unchanged rejection of invalid builtins and non-namespaced unknown types.
Reviewed by Cursor Bugbot for commit dbbfb0d. Bugbot is set up for automated code reviews on this repo. Configure here.