Files
editor/packages/mcp/test-reports/phase8/p6-casa-sol-save.md
T
Adrian PerezandClaude Opus 4.7 0b84e7b7b1 fix(mcp,editor): close URL-validation bypasses surfaced by Phase 8 P4
Phase 8 parallel validation flagged two boundaries where malicious URLs
(javascript:, file:, external http:, data:text/html, ...) could be
persisted despite the AssetUrl allowlist added in Phase 7 A7:

1. `save_scene({ includeCurrentScene: false, graph })` — the graph arg
   was treated as opaque (`z.record(z.string(), z.unknown())`) and
   written to the store without re-running AnyNode.safeParse.

2. `POST /api/scenes { graph }` in the editor API — same issue; the
   Zod `graphSchema` accepted anything object-shaped.

Fixes:
- `save-scene.ts`: when `includeCurrentScene === false`, iterate every
  node and run `AnyNode.safeParse`; collect issues and throw
  `McpError(InvalidParams, 'graph_invalid', { errors })` on any
  failure.
- `app/api/scenes/route.ts`: replace `graphSchema` with a structured
  `z.object({ nodes, rootNodeIds, collections? })` + `superRefine`
  that runs `AnyNode.safeParse` on every node. Invalid → 400 with
  detailed issue paths.

Tests:
- Added `save_scene` regression test for the P4 attack
  (item.asset.src = 'javascript:alert(1)') — expected error.
- Fixed the existing `includeCurrentScene=false` test to use a
  schema-compliant site node id (the prior `id: 'root'` now fails
  the AnyNode parse, which is the desired strict behaviour).
- Full suite: 294 pass / 0 fail.

Also adds Phase 8 test-reports/phase8/** (10 agents, ~15 scripts +
markdown reports) documenting the validation run, plus minor biome
cleanups to the Phase 5/7 test artefacts (removed stale
`// biome-ignore` suppression comments that now resolve to the
already-off `noConsole` rule).

Phase 8 result summary (10 parallel agents, stdio MCP transport with
isolated data dirs):
- P1 templates: 18/18 PASS
- P2 variants: 6/7 mutations + determinism + save + combined + error
- P3 locking: 12/12 PASS (MCP + editor HTTP If-Match)
- P4 URL hardening: fixed 2 bypasses (see above)
- P5 photo-to-scene: 6/6 PASS
- P6 Casa del Sol via save_scene: 13/13 PASS
- P7 editor HTTP API: 18/18 PASS
- P8 concurrency: 4/5 PASS, flagged 2 real filesystem-store races
  (expectedVersion CAS gap + .index.json drift under parallel writes)
- P9 edge cases: 13/13 PASS (size cap, slug safety, bad inputs)
- P10 full sweep: 37/37 PASS (30 tools + 4 resources + 3 prompts)

Known follow-ups:
- FilesystemSceneStore needs a proper lockfile / atomic CAS to fix
  the P8 concurrency bugs (low priority: single-writer MCP is the
  typical case).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 20:26:26 +02:00

2.7 KiB

Phase 8 P6 — Casa del Sol via save_scene

Generated: 2026-04-19T18:20:19.643Z Transport: stdio (spawned bun /Users/adrian/Desktop/editor/.worktrees/mcp-server/packages/mcp/dist/bin/pascal-mcp.js --stdio) PASCAL_DATA_DIR: /tmp/pascal-phase8 Editor URL: http://localhost:3002

Result summary

  • Steps passed: 13/13
  • Initial node count: 3
  • Final node count: 39 (threshold ≥ 30)
  • doors=6, windows=6, zones=9, walls=9, fences=5, slabs=1
  • All validate_scene calls valid=true: true
  • Saved scene id: 6f87c59c1535
  • Scene file: /tmp/pascal-phase8/scenes/6f87c59c1535.json

Open in browser: http://localhost:3002/scene/6f87c59c1535

Per-step results

# Step Status Duration Summary
1 discover PASS 2ms building=building_16mw8oy88f952is9, level=level_3jbpcuma0wfwclex
2 perimeter walls PASS 2ms 4 walls
3 interior walls PASS 0ms 5 walls
4 zones PASS 2ms 7 zones
5 openings PASS 3ms 6 doors, 6 windows, 0 failures
6 pool zone + slab PASS 1ms zone=zone_lnki9rxnoyq72rwi, slab=slab_rfyzzvcxlkbgif1w
7 privacy fences PASS 1ms 5 fences
8 garden zone PASS 0ms zone=zone_6l4ls8mr4rtfvsye
9 save_scene PASS 6ms id=6f87c59c1535, nodeCount=39, size=29677B, url=/scene/6f87c59c1535
10 file on disk PASS 0ms /tmp/pascal-phase8/scenes/6f87c59c1535.json (29677B)
11 GET /api/scenes/:id PASS 9ms 200 OK, 39 nodes
12 GET /scene/:id (HTML) PASS 237ms 200 OK, text/html; charset=utf-8, 72918B
13 write v2 scene.json PASS 1ms 26761B -> /Users/adrian/Desktop/editor/.worktrees/mcp-server/packages/mcp/test-reports/phase8/casa-sol-v2.json

Validation history

Phase valid errors
initial true 0
afterWalls true 0
afterZones true 0
afterOpenings true 0
afterPool true 0
afterFences true 0
afterGarden true 0
final true 0

save_scene response

{
  "id": "6f87c59c1535",
  "name": "Casa del Sol",
  "projectId": null,
  "thumbnailUrl": null,
  "version": 1,
  "createdAt": "2026-04-19T18:20:19.389Z",
  "updatedAt": "2026-04-19T18:20:19.389Z",
  "ownerId": null,
  "sizeBytes": 29677,
  "nodeCount": 39,
  "url": "/scene/6f87c59c1535"
}

Assertions

  • ≥30 nodes total
  • validate_scene valid:true at every phase
  • save_scene returned id=6f87c59c1535
  • file exists on disk at /tmp/pascal-phase8/scenes/6f87c59c1535.json
  • GET /api/scenes/ returned 200 with matching node count
  • GET /scene/ returned 200 HTML
  • wrote /Users/adrian/Desktop/editor/.worktrees/mcp-server/packages/mcp/test-reports/phase8/casa-sol-v2.json