docs/plans/2026-06-14-fix-media-embed-advisory.md
Objective: Fix GHSA-qj6x-xx2h-8hvv by blocking unsafe serialized media embed iframe URLs while preserving valid provider embeds.
Goal plan: docs/plans/2026-06-14-fix-media-embed-advisory.md
Template: docs/plans/templates/task.md
Primary template: docs/plans/templates/task.md
Applied packs:
Task source:
javascript: iframe src; valid serialized YouTube/Vimeo/Twitter metadata still resolves through useMediaState; @platejs/media gets a patch changeset.Completion threshold:
useMediaState recomputes or validates rendered embed URLs instead of trusting serialized provider / sourceUrl metadata.node .agents/skills/autogoal/scripts/check-complete.mjs docs/plans/2026-06-14-fix-media-embed-advisory.md passes.Verification surface:
pnpm --filter @platejs/media test -- useMediaStatepnpm turbo typecheck --filter=./packages/mediapnpm lint:fixpackages/media/src/react/media/useMediaState.ts, media parsers, and package export impact.Constraints:
Boundaries:
GHSA-qj6x-xx2h-8hvv, fetched with gh api repos/udecode/plate/security-advisories/GHSA-qj6x-xx2h-8hvv.packages/media/src/react/media/useMediaState.ts, focused media tests, .changeset, and this plan.Output budget strategy:
rg/sed reads and cap large command output. Avoid broad generated docs/public JSON output after initial search.Blocked condition:
Task state:
Current verdict:
useMediaState fast path and registry iframe render path.Completion rule:
update_goal(status: complete) while any required checklist item
remains unchecked. If an item does not apply, check it and add N/A: <reason>.update_goal(status: complete) until every completion threshold
above is satisfied, final handoff evidence is recorded, and
node .agents/skills/autogoal/scripts/check-complete.mjs docs/plans/2026-06-14-fix-media-embed-advisory.md passes.Start Gates:
| Gate | Applies | Evidence |
|---|---|---|
| Skill analysis before edits | yes | Read task, autogoal, GitHub, and changeset instructions. |
| Active goal checked or created | yes | Created active goal for GHSA-qj6x-xx2h-8hvv fix. |
| Source of truth read before edits | yes | Fetched advisory with gh api repos/udecode/plate/security-advisories/GHSA-qj6x-xx2h-8hvv. |
| Tracker comments and attachments read | N/A | GitHub repository advisory API returned the full report and no separate comment thread was needed. |
| Video transcript evidence required | N/A | Advisory contains text PoC only. |
docs/solutions checked for non-trivial existing-code work | yes | Scoped rg over docs/solutions, .agents/rules, and docs/plans; no prior fix for this media sanitizer bug found. |
| TDD decision before behavior change or bug fix | yes | Add regression coverage for the PoC and valid serialized metadata before closeout. |
| Branch decision for code-changing task | N/A | No PR/branch requested; repo instruction says do not check git state proactively. |
| Release artifact decision | yes | Add one .changeset for @platejs/media patch behavior. |
| Browser tool decision for browser surface | N/A | Package hook/parser behavior has no required browser route; unit proof is the ownership point. |
| PR expectation decision | N/A | User asked what to do; no PR requested. |
| Tracker sync expectation decision | N/A | No advisory comment/publication requested. |
| Output budget strategy recorded | yes | Command output kept scoped and capped after the first broad search. |
| Package/API pack selected | yes | Applied package-api pack because @platejs/media runtime behavior changes. |
| Public surface or package boundary identified | yes | Public package runtime: @platejs/media/react useMediaState. |
| Release artifact path selected | yes | .changeset required for published package behavior. |
changeset skill loaded when .changeset is required | yes | Read .agents/rules/changeset.mdc. |
| Barrel/export impact decision recorded | yes | No exports or file layout change expected; pnpm brl N/A unless implementation changes that. |
Work Checklist:
<video-transcripts> XML, or marked N/A with reason..agents/**, .claude/**,
.codex/**, skills, hooks, commands, prompts, or user-action tooling..changeset, registry changelog, or explicit no-artifact reason..changeset work loads changeset and follows its package/version/prose rules.tooling/data/plate-ui-changelog.mdx and generated /registry/changelog/* JSON instead of adding a package changeset.main.Completion Gates:
| Gate | Applies | Required action | Evidence |
|---|---|---|---|
| Named verification threshold | yes | Run named verification commands and source audit | pnpm --filter @platejs/media test -- useMediaState, pnpm turbo typecheck --filter=./packages/media, pnpm lint:fix, and source diff review passed. |
| Bug reproduced before fix | yes | Record failing test/repro or reason | Regression test added for the advisory PoC path; it asserts unsafe serialized provider metadata produces no embed. |
| Targeted behavior verification | yes | Run focused test/proof for changed behavior | pnpm --filter @platejs/media test -- useMediaState passed, 95 tests. |
| TypeScript or typed config changed | yes | Run relevant typecheck | pnpm turbo typecheck --filter=./packages/media passed. |
| Package exports or file layout changed | N/A | Run pnpm brl if exports or exported layout changed | No exports or file layout changed. |
| Package manifests, lockfile, or install graph changed | N/A | Run install if package graph changed | No manifest or lockfile changed. |
| Agent rules or skills changed | N/A | Run sync if agent files changed | No agent rules or skills changed. |
| Workspace authority proof | yes | Run verification in owning workspace | All commands ran in /Users/zbeyens/git/plate, owning repo/package. |
| Browser surface changed | N/A | Capture Browser Use proof or record waiver | No route or UI fixture changed; hook/parser package behavior is covered by unit tests. |
| Browser final proof | N/A | Attach screenshot or caveat when browser proof applies | No browser proof required for this package-only sanitizer fix. |
| CI-controlled template output changed | N/A | Restore generated output or justify | No template output changed. |
| Package behavior or public API changed | yes | Add a changeset | Added .changeset/media-embed-url-sanitization.md. |
| Registry-only component work changed | N/A | Update registry changelog when registry-only | No apps/www/src/registry/** files changed. |
| Docs or content changed | N/A | Verify docs/content if changed | No user-facing docs/content changed; goal scratchpad only. |
| High-risk mini gate | yes | Record risk, proof, and boundary | Risk: custom renderers trusting serialized metadata. Boundary: useMediaState now derives embed state from parsed render URL. Proof: regression test plus media package tests/typecheck. |
| Agent-native review for agent/tooling changes | N/A | Load agent-native reviewer if agent/tooling changed | No agent/tooling surface changed. |
| Local install corruption suspected | N/A | Reinstall once only if failure smells like install rot | No suspicious install/runtime failure occurred. |
| Autoreview for non-trivial implementation changes | yes | Run structured review until clean | .agents/skills/autoreview/scripts/autoreview --mode local passed clean with no accepted/actionable findings. |
| PR create or update | N/A | Run check before PR work | No PR requested. |
| Task-style PR body verified | N/A | Verify PR body if PR exists | No PR created or updated. |
| PR proof image hosting | N/A | Host proof images if PR body needs them | No PR proof images needed. |
| Tracker sync-back | N/A | Post sync after PR if requested | No advisory comment requested. |
| Final handoff contract | yes | Fill final handoff fields | Final handoff fields below are filled. |
| Final lint | yes | Run lint | pnpm lint:fix passed, no fixes applied. |
| Output budget discipline | yes | Verify command output stayed scoped | Broad output was capped; verification commands used focused scopes. |
| Goal plan complete | yes | Run autogoal checker | Check planned after this update. |
| Public API / package boundary proof | yes | Source-audit package boundary | Runtime behavior of public useMediaState changed; no export/API signature changed. |
| Release artifact classification | yes | Classify artifact need | Published package runtime behavior change in @platejs/media; patch changeset required. |
| Published package changeset | yes | Add one package changeset and avoid forbidden minor | .changeset/media-embed-url-sanitization.md uses @platejs/media: patch; no forbidden minor. |
| Registry changelog | N/A | Update registry changelog if registry-only | Not registry-only. |
| No release artifact | N/A | Record no-artifact reason if none | Release artifact exists. |
| Package typecheck/build/test | yes | Run package checks | Media tests and scoped typecheck passed. |
| Barrel/export generation | N/A | Run pnpm brl if exports or file layout changed | No barrel/export impact. |
Phase / pass table:
| Phase | Status | Evidence | Next |
|---|---|---|---|
| Intake and source read | complete | Advisory and local implementation path inspected | implementation |
| Implementation | complete | Removed serialized metadata fast path in useMediaState | verification |
| Verification | complete | Focused media tests, typecheck, lint, and autoreview passed | closeout |
| PR / tracker sync | N/A | No PR/comment requested | final response |
| Closeout | complete | Plan evidence recorded | final response |
Findings:
provider / sourceUrl metadata could make useMediaState skip parseMediaUrl and hand an unsafe render URL to registry iframe consumers.Decisions and tradeoffs:
url; keep serialized metadata out of render trust decisions.Implementation notes:
element.provider || element.sourceUrl fast path from packages/media/src/react/media/useMediaState.ts..changeset/media-embed-url-sanitization.md.Review fixes:
Error attempts:
| Error / failed attempt | Count | Next different move | Resolution |
|---|---|---|---|
| None | 0 | N/A | N/A |
Verification evidence:
pnpm --filter @platejs/media test -- useMediaState passed, 95 tests.pnpm turbo typecheck --filter=./packages/media passed.pnpm lint:fix passed, no fixes applied..agents/skills/autoreview/scripts/autoreview --mode local passed clean with no accepted/actionable findings.Final handoff contract:
useMediaState, the shared render-state owner.Task-style PR body contract:
<!-- auto-release:start --> block. If a changeset is
part of the diff and repo policy expects auto release, include that block.๐ Fixes #123 or ๐ Fixes โ N/A, then
an emoji confidence line like ๐ข 95-100% confidence.| Phase | ๐งช Tests | ๐ Browser |.Reproduced and Verified rows. Mark passing proof with ๐ข, repro or
failing proof with ๐ด, and non-applicable cells with โ N/A.**โ
Outcome**, **โ ๏ธ Caveat**,
**๐๏ธ Design**, and **๐งช Verified**.Summary / Verification PR body, an
adaptive prose body from a git helper skill, plain ## Outcome sections, or
an unrelated generated badge footer unless the caller or repo template
explicitly asks for it.gh pr view --json body output or a concise source-backed summary
of that output.Final handoff / sync:
Timeline:
useMediaState.@platejs/media patch changeset.pnpm --filter @platejs/media test -- useMediaState passed.pnpm turbo typecheck --filter=./packages/media passed.pnpm lint:fix passed..agents/skills/autoreview/scripts/autoreview --mode local passed clean.Reboot status:
| Question | Answer |
|---|---|
| Where am I? | Closeout complete |
| Where am I going? | Final response |
| What is the goal? | Fix GHSA-qj6x-xx2h-8hvv with regression coverage and focused verification |
| What have I learned? | The vulnerable path was serialized media metadata bypassing URL parsing in useMediaState |
| What have I done? | Removed the bypass, added tests, added changeset, and verified |
Open risks: