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>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
e8d0b13ff5
commit
0b84e7b7b1
@@ -0,0 +1,69 @@
|
||||
# P2 Phase 8 — `generate_variants` report
|
||||
|
||||
Generated: 2026-04-19T18:19:47.106Z
|
||||
Data dir: `/tmp/pascal-phase8-p2`
|
||||
Transport: stdio (`bun packages/mcp/dist/bin/pascal-mcp.js --stdio`)
|
||||
Total run time: 32 ms
|
||||
|
||||
## Setup
|
||||
|
||||
- template: `two-bedroom`
|
||||
- base nodeCount: **25**
|
||||
- base saved: **true** (id=`4a051220b1e6`)
|
||||
|
||||
## Per-mutation results
|
||||
|
||||
| # | Mutation | Status | nodeCounts | Summary |
|
||||
|---|----------|--------|------------|---------|
|
||||
| 1 | `wall-thickness` | PASS | [25, 25] | 2 variants, nodeCounts=[25,25], min=25 |
|
||||
| 2 | `wall-height` | PASS | [25, 25] | 2 variants, nodeCounts=[25,25], min=25 |
|
||||
| 3 | `zone-labels` | PASS | [25, 25] | 2 variants, nodeCounts=[25,25], min=25 |
|
||||
| 4 | `room-proportions` | PASS | [25, 25] | 2 variants, nodeCounts=[25,25], min=25 |
|
||||
| 5 | `open-plan` | FAIL | [23, 24] | variant nodeCount 23 < min 24 (base=25) |
|
||||
| 6 | `door-positions` | PASS | [25, 25] | 2 variants, nodeCounts=[25,25], min=25 |
|
||||
| 7 | `fence-style` | PASS | [25, 25] | 2 variants, nodeCounts=[25,25], min=25 |
|
||||
|
||||
### Variant descriptions
|
||||
|
||||
- **wall-thickness**: "wall thickness 0.2m", "wall thickness 0.25m"
|
||||
- **wall-height**: "wall height 2.7m", "wall height 3m"
|
||||
- **zone-labels**: "zones [Living / Kitchen, Bedroom 2, Bedroom 1, Bath]", "zones [Bath, Bedroom 1, Living / Kitchen, Bedroom 2]"
|
||||
- **room-proportions**: "room proportions nudged", "room proportions nudged"
|
||||
- **open-plan**: "open-plan", "open-plan"
|
||||
- **door-positions**: "doors repositioned", "doors repositioned"
|
||||
- **fence-style**: "no-op", "no-op"
|
||||
|
||||
## Determinism
|
||||
|
||||
- Status: **PASS**
|
||||
- Detail: 3 variant graphs identical (after ID normalization) across calls (wall-thickness, seed=1337)
|
||||
|
||||
## Save path
|
||||
|
||||
- Status: **PASS**
|
||||
- Detail: variants saved=3, list_scenes returned 4 (expected 4)
|
||||
- Variants saved in step: 3
|
||||
- `list_scenes` after save: 4
|
||||
|
||||
## Combined mutation validation
|
||||
|
||||
- Status: **PASS**
|
||||
- Detail: combined variant sceneId=18e32febadc7, valid=true, errors=0, description="wall thickness 0.1m, wall height 2.7m, zones [Bedroom 2, Living / Kitchen, Bath, Bedroom 1], room proportions nudged, open-plan, doors repositioned"
|
||||
- valid: true, errorCount: 0
|
||||
|
||||
## Error path
|
||||
|
||||
- Status: **PASS**
|
||||
- Detail: isError with text: MCP error -32602: scene_not_found
|
||||
|
||||
## Totals
|
||||
|
||||
- Total variants saved across the run: **4**
|
||||
|
||||
## Overall summary
|
||||
|
||||
**Summary (≤150 words):**
|
||||
|
||||
Per-mutation: 6/7 PASS. Determinism: PASS (identical after id normalization; `forkSceneGraph` regenerates ids so raw JSON can't match). Save path: PASS — 3 variants saved, `list_scenes` returned 4 (expected 4). Combined mutation: PASS (variant validates). Error path: PASS. Total variants saved: 4. Total variants exercised across the run: ~21.
|
||||
|
||||
Note: the only failing mutation is `open-plan` — nodeCounts [23, 24] with base=25. The spec rule `>= base - 1` assumes open-plan drops only the wall node, but `applyOpenPlan` also drops any openings (doors/windows) attached to the removed wall — so a variant may drop 2+ nodes. The mutation itself is working correctly; the spec's lower-bound rule is tighter than the implementation.
|
||||
Reference in New Issue
Block a user