Back to Plate

fix media embed advisory

docs/plans/2026-06-14-fix-media-embed-advisory.md

53.2.018.6 KB
Original Source

fix media embed advisory

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:

  • package-api (docs/plans/templates/packs/package-api.md)

Task source:

  • type: GitHub repository security advisory
  • id / link: https://github.com/udecode/plate/security/advisories/GHSA-qj6x-xx2h-8hvv
  • title: Media embed provider metadata can bypass URL sanitization and execute iframe JavaScript
  • acceptance criteria: the advisory PoC cannot produce an embed with 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.
  • Regression coverage proves the advisory PoC returns no renderable embed.
  • Valid serialized provider metadata keeps rendering through parser-owned HTTP(S) output.
  • Focused media package tests pass and release artifact is recorded.
  • Task closure is legal only when the source-of-truth acceptance criteria are satisfied or explicitly narrowed, required verification evidence is recorded, code-review and release-artifact gates are closed when applicable, tracker/PR sync is complete or marked N/A with reason, and 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 -- useMediaState
  • pnpm turbo typecheck --filter=./packages/media
  • pnpm lint:fix
  • Source audit of packages/media/src/react/media/useMediaState.ts, media parsers, and package export impact.

Constraints:

  • Preserve existing user-facing behavior outside the task scope.
  • Prefer the durable ownership boundary over caller-by-caller patches.
  • Do not create PRs, comments, commits, or pushes unless the task/user/skill requires them.
  • Do not add broad ceremony when the task is trivial or docs-only.

Boundaries:

  • Source of truth: private GHSA advisory GHSA-qj6x-xx2h-8hvv, fetched with gh api repos/udecode/plate/security-advisories/GHSA-qj6x-xx2h-8hvv.
  • Allowed edit scope: packages/media/src/react/media/useMediaState.ts, focused media tests, .changeset, and this plan.
  • Browser surface: N/A for the package fix; the vulnerable behavior is hook/parser-owned and covered by unit tests.
  • Tracker sync: N/A unless the user asks to comment or publish the advisory.
  • Non-goals: public disclosure wording, npm release execution, PR creation, broad media parser redesign.

Output budget strategy:

  • Use focused rg/sed reads and cap large command output. Avoid broad generated docs/public JSON output after initial search.

Blocked condition:

  • Block only if package tests cannot run due to install corruption after the required reinstall retry, or if GitHub advisory access is revoked before confirming remediation.

Task state:

  • task_type: security bug fix
  • task_complexity: normal
  • current_phase: closeout
  • current_phase_status: complete
  • next_phase: final response
  • goal_status: ready to complete

Current verdict:

  • verdict: valid high-severity package bug
  • confidence: high
  • next owner: task
  • reason: advisory PoC matches current useMediaState fast path and registry iframe render path.

Completion rule:

  • Do not call update_goal(status: complete) while any required checklist item remains unchecked. If an item does not apply, check it and add N/A: <reason>.
  • Do not call 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.
  • Do not create hook state for this goal. This file plus the active goal are the durable state.

Start Gates:

GateAppliesEvidence
Skill analysis before editsyesRead task, autogoal, GitHub, and changeset instructions.
Active goal checked or createdyesCreated active goal for GHSA-qj6x-xx2h-8hvv fix.
Source of truth read before editsyesFetched advisory with gh api repos/udecode/plate/security-advisories/GHSA-qj6x-xx2h-8hvv.
Tracker comments and attachments readN/AGitHub repository advisory API returned the full report and no separate comment thread was needed.
Video transcript evidence requiredN/AAdvisory contains text PoC only.
docs/solutions checked for non-trivial existing-code workyesScoped 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 fixyesAdd regression coverage for the PoC and valid serialized metadata before closeout.
Branch decision for code-changing taskN/ANo PR/branch requested; repo instruction says do not check git state proactively.
Release artifact decisionyesAdd one .changeset for @platejs/media patch behavior.
Browser tool decision for browser surfaceN/APackage hook/parser behavior has no required browser route; unit proof is the ownership point.
PR expectation decisionN/AUser asked what to do; no PR requested.
Tracker sync expectation decisionN/ANo advisory comment/publication requested.
Output budget strategy recordedyesCommand output kept scoped and capped after the first broad search.
Package/API pack selectedyesApplied package-api pack because @platejs/media runtime behavior changes.
Public surface or package boundary identifiedyesPublic package runtime: @platejs/media/react useMediaState.
Release artifact path selectedyes.changeset required for published package behavior.
changeset skill loaded when .changeset is requiredyesRead .agents/rules/changeset.mdc.
Barrel/export impact decision recordedyesNo exports or file layout change expected; pnpm brl N/A unless implementation changes that.

Work Checklist:

  • Short objective plus outcome, completion threshold, verification surface, constraints, boundaries, and blocked condition are concrete.
  • Task source classified with source type, id/link, title, task type, acceptance criteria, caveats, likely files/routes/packages, browser surface, and root-cause layer.
  • Required video or screen-recording evidence is cached/read as normalized <video-transcripts> XML, or marked N/A with reason.
  • Nearby repo instructions and implementation patterns read before edits.
  • Implementation fixes the right ownership boundary, or the narrower choice is recorded with reason.
  • Release artifact requirement recorded: changeset, registry changelog, or N/A with reason.
  • Final handoff shape decided: bug/feature/testing/batch/review/tracker requirements, PR body sync, and issue/Linear sync when applicable.
  • Branch handling recorded for code-changing work: dedicated branch used, new branch needed, or N/A with reason.
  • Local-env-rot retry policy recorded for any surprising repo-wide failure: reinstall/rerun evidence or N/A with reason.
  • Workspace authority recorded: every proof command names the cwd/tool that owns the changed behavior.
  • High-risk note recorded for public API, runtime, package-boundary, browser behavior, agent-action, or command-contract changes, or marked N/A with reason.
  • Review/autoreview target selected from actual diff state for non-trivial implementation work, or marked N/A with reason.
  • Agent-native review decision recorded for .agents/**, .claude/**, .codex/**, skills, hooks, commands, prompts, or user-action tooling.
  • Output budget discipline recorded and followed: broad searches are scoped, capped, counted, or artifacted instead of streamed into goal context.
  • Package/API pack: public API, package boundary, export, and release-artifact impact are recorded.
  • Package/API pack: release artifact matrix is applied: .changeset, registry changelog, or explicit no-artifact reason.
  • Package/API pack: .changeset work loads changeset and follows its package/version/prose rules.
  • Package/API pack: registry-only work updates tooling/data/plate-ui-changelog.mdx and generated /registry/changelog/* JSON instead of adding a package changeset.
  • Package/API pack: no-artifact decisions state why the diff has no published package user-visible delta from main.
  • Package/API pack: compatibility, migration, or hard-cut decision is explicit when public shape changes.
  • Package/API pack: package-owned typecheck/build/test proof is recorded or marked N/A with reason.
  • Package/API pack: generated barrels or release notes are updated when required.

Completion Gates:

GateAppliesRequired actionEvidence
Named verification thresholdyesRun named verification commands and source auditpnpm --filter @platejs/media test -- useMediaState, pnpm turbo typecheck --filter=./packages/media, pnpm lint:fix, and source diff review passed.
Bug reproduced before fixyesRecord failing test/repro or reasonRegression test added for the advisory PoC path; it asserts unsafe serialized provider metadata produces no embed.
Targeted behavior verificationyesRun focused test/proof for changed behaviorpnpm --filter @platejs/media test -- useMediaState passed, 95 tests.
TypeScript or typed config changedyesRun relevant typecheckpnpm turbo typecheck --filter=./packages/media passed.
Package exports or file layout changedN/ARun pnpm brl if exports or exported layout changedNo exports or file layout changed.
Package manifests, lockfile, or install graph changedN/ARun install if package graph changedNo manifest or lockfile changed.
Agent rules or skills changedN/ARun sync if agent files changedNo agent rules or skills changed.
Workspace authority proofyesRun verification in owning workspaceAll commands ran in /Users/zbeyens/git/plate, owning repo/package.
Browser surface changedN/ACapture Browser Use proof or record waiverNo route or UI fixture changed; hook/parser package behavior is covered by unit tests.
Browser final proofN/AAttach screenshot or caveat when browser proof appliesNo browser proof required for this package-only sanitizer fix.
CI-controlled template output changedN/ARestore generated output or justifyNo template output changed.
Package behavior or public API changedyesAdd a changesetAdded .changeset/media-embed-url-sanitization.md.
Registry-only component work changedN/AUpdate registry changelog when registry-onlyNo apps/www/src/registry/** files changed.
Docs or content changedN/AVerify docs/content if changedNo user-facing docs/content changed; goal scratchpad only.
High-risk mini gateyesRecord risk, proof, and boundaryRisk: 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 changesN/ALoad agent-native reviewer if agent/tooling changedNo agent/tooling surface changed.
Local install corruption suspectedN/AReinstall once only if failure smells like install rotNo suspicious install/runtime failure occurred.
Autoreview for non-trivial implementation changesyesRun structured review until clean.agents/skills/autoreview/scripts/autoreview --mode local passed clean with no accepted/actionable findings.
PR create or updateN/ARun check before PR workNo PR requested.
Task-style PR body verifiedN/AVerify PR body if PR existsNo PR created or updated.
PR proof image hostingN/AHost proof images if PR body needs themNo PR proof images needed.
Tracker sync-backN/APost sync after PR if requestedNo advisory comment requested.
Final handoff contractyesFill final handoff fieldsFinal handoff fields below are filled.
Final lintyesRun lintpnpm lint:fix passed, no fixes applied.
Output budget disciplineyesVerify command output stayed scopedBroad output was capped; verification commands used focused scopes.
Goal plan completeyesRun autogoal checkerCheck planned after this update.
Public API / package boundary proofyesSource-audit package boundaryRuntime behavior of public useMediaState changed; no export/API signature changed.
Release artifact classificationyesClassify artifact needPublished package runtime behavior change in @platejs/media; patch changeset required.
Published package changesetyesAdd one package changeset and avoid forbidden minor.changeset/media-embed-url-sanitization.md uses @platejs/media: patch; no forbidden minor.
Registry changelogN/AUpdate registry changelog if registry-onlyNot registry-only.
No release artifactN/ARecord no-artifact reason if noneRelease artifact exists.
Package typecheck/build/testyesRun package checksMedia tests and scoped typecheck passed.
Barrel/export generationN/ARun pnpm brl if exports or file layout changedNo barrel/export impact.

Phase / pass table:

PhaseStatusEvidenceNext
Intake and source readcompleteAdvisory and local implementation path inspectedimplementation
ImplementationcompleteRemoved serialized metadata fast path in useMediaStateverification
VerificationcompleteFocused media tests, typecheck, lint, and autoreview passedcloseout
PR / tracker syncN/ANo PR/comment requestedfinal response
CloseoutcompletePlan evidence recordedfinal response

Findings:

  • GHSA is valid: serialized provider / sourceUrl metadata could make useMediaState skip parseMediaUrl and hand an unsafe render URL to registry iframe consumers.

Decisions and tradeoffs:

  • Fix the package hook owner instead of patching registry iframe callers.
  • Derive embed state from the render url; keep serialized metadata out of render trust decisions.
  • Browser proof is N/A because no route fixture changed and unit coverage directly exercises the hook behavior.

Implementation notes:

  • Removed the element.provider || element.sourceUrl fast path from packages/media/src/react/media/useMediaState.ts.
  • Added hook-level regression tests for unsafe serialized metadata and provider recomputation from render URL.
  • Added .changeset/media-embed-url-sanitization.md.

Review fixes:

  • Autoreview reported no accepted/actionable findings.

Error attempts:

Error / failed attemptCountNext different moveResolution
None0N/AN/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:

  • PR line: N/A, no PR requested.
  • Issue / tracker line: GHSA-qj6x-xx2h-8hvv fixed locally; no advisory comment requested.
  • Confidence line: high.
  • Flow table:
    • Reproduced: test covers advisory PoC; browser N/A.
    • Verified: media tests, typecheck, lint, and autoreview passed; browser N/A.
  • Browser check: N/A, package hook sanitizer behavior has focused unit proof.
  • Outcome: unsafe serialized media embed metadata no longer bypasses URL parsing.
  • Caveat: release/publish and advisory publication are not performed.
  • Design:
    • Chosen boundary: useMediaState, the shared render-state owner.
    • Why not quick patch: registry iframe callers are not the only consumers of the hook.
    • Why not broader change: parser contracts already own supported media URL validation; no API redesign needed.
  • Verified: media tests, scoped typecheck, lint, autoreview.
  • PR body verified: N/A, no PR.

Task-style PR body contract:

  • Preserve any existing <!-- auto-release:start --> block. If a changeset is part of the diff and repo policy expects auto release, include that block.
  • Use the accepted kitcn PR #270 visual format. The body starts with an emoji issue/tracker/fix line, for example ๐Ÿ› Fixes #123 or ๐Ÿ› Fixes โž– N/A, then an emoji confidence line like ๐ŸŸข 95-100% confidence.
  • Use this exact table header: | Phase | ๐Ÿงช Tests | ๐ŸŒ Browser |.
  • Use Reproduced and Verified rows. Mark passing proof with ๐ŸŸข, repro or failing proof with ๐Ÿ”ด, and non-applicable cells with โž– N/A.
  • Use bold emoji section headings: **โœ… Outcome**, **โš ๏ธ Caveat**, **๐Ÿ—๏ธ Design**, and **๐Ÿงช Verified**.
  • Never include a line that links to the current PR itself. The current PR URL belongs in the final response, not in its own description.
  • Do not replace this with a generic 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.
  • Proof is gh pr view --json body output or a concise source-backed summary of that output.

Final handoff / sync:

  • PR: N/A.
  • Issue / tracker: N/A.
  • Browser proof: N/A, package hook unit proof used.
  • Caveats: release/publish and advisory publication not performed.

Timeline:

  • 2026-06-14T11:14:10.441Z Task goal plan created.
  • 2026-06-14: Removed unsafe serialized metadata fast path from useMediaState.
  • 2026-06-14: Added GHSA regression tests and @platejs/media patch changeset.
  • 2026-06-14: pnpm --filter @platejs/media test -- useMediaState passed.
  • 2026-06-14: pnpm turbo typecheck --filter=./packages/media passed.
  • 2026-06-14: pnpm lint:fix passed.
  • 2026-06-14: .agents/skills/autoreview/scripts/autoreview --mode local passed clean.

Reboot status:

QuestionAnswer
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:

  • Release/publish and advisory publication remain separate maintainer actions.