From 92a271695e77a59cf69d22beef109ed4d0cdd72e Mon Sep 17 00:00:00 2001 From: Wenjix Date: Mon, 26 Oct 2026 23:07:41 -0700 Subject: [PATCH] fix(composition): reject markup terminators and CSS escapes in style values Style declarations are serialized into a raw-text " }] }], + }], + }); + assert.throws( + () => assertVisualTrackIdentity(hostile, timeline), + /unsafe style value/, + ); +}); + +test("VisualTrack style values cannot hide resource functions behind CSS escapes", () => { + const { timeline, visual } = fixture(); + const first = visual.presents[0]!.elements[0]!; + const escaped = sealVisualTrack({...visual, + presents: [{ + ...visual.presents[0]!, + elements: [{ ...first, style: [{ name: "background", value: "u\\72 l(https://invalid.example/)" }] }], + }], + }); + assert.throws( + () => assertVisualTrackIdentity(escaped, timeline), + /unsafe style value/, + ); +}); + +test("VisualTrack animation keyframes share the same unsafe-value checks", () => { + const { timeline, visual } = fixture(); + const first = visual.presents[0]!.elements[0]!; + const hostile = sealVisualTrack({...visual, + presents: [{ + ...visual.presents[0]!, + elements: [{ ...first, animation: { keyframes: [ + { atFrame: 0, style: [{ name: "opacity", value: 1 }] }, + { atFrame: 30, style: [{ name: "opacity", value: "0" }] }, + ] } }], + }], + }); + assert.throws( + () => assertVisualTrackIdentity(hostile, timeline), + /unsafe style value/, + ); +}); + test("VisualTrack cannot silently extend the versioned public Visual IR", () => { const { timeline, visual } = fixture(); const first = visual.presents[0]!.elements[0]!;