Skip to content

fix(security): fail closed on hostile clipboard sanitizer throw values #349

Description

@seonghobae

Buyer-visible clipboard fail-closed defect — canonical Draft #351

Protected shipped truth is exact main@3b38ead2d00f44eb578d0689087b9293b3dabe1e. Draft PR #351 / branch fix/clipboard-hostile-throw-349 is the canonical single writer for this hostile-thrown-value containment boundary. Historical pre-synchronization head 3460f2d4233177a29607dd9b181ff4fad3d363bf and historical branch-base metadata e8109ec2a17de8bd6594487aa12c8c8a93cb2c03 are predecessor state. Current exact Draft head is 61936f2d9e26373115fed1dee1ce93387ebcfc12, synchronized non-destructively with protected main; GitHub reports the Draft mechanically mergeable.

Protected production classified sanitizer/configuration failures with instanceof ClipboardSanitizationError. A hostile host option/config getter could throw a Proxy whose getPrototypeOf trap throws a private sentinel; prototype-based classification inside the catch path could then leak that second exception instead of returning the stable empty paste result and payload-redacted invalid_html observer error.

Inkspan owns this deterministic local clipboard error-containment boundary. No transport, persistence, auth, tenancy, credentials, model/provider, network, deployment, retention, durable audit, or clipboard-exfiltration authority is added.

TDD and repair lineage

  • RED 758c36c05f104be0cc6b608acd5c753ea5b9b709: hosted CI 31901402934 reached the real ProseMirror transformPastedHTML regression after immutable setup/typecheck and failed because the hostile thrown Proxy's prototype trap escaped.
  • GREEN source commits ce8223071aa658d70edfde6b70df936eb7e77e77 and 3f963bfe0cb0e96366ad3eb42ed4546c197cfeaf: module-created ClipboardSanitizationError values are tracked with a side-effect-free module-owned WeakSet identity brand; every other unknown thrown value normalizes directly to stable redacted failure without prototype/property/string coercion.
  • e54c7769feecffe62734a7e18bd5a3f440d98358 preserves stale fix(reliability): bound rich clipboard resource preflight #164's remaining unique direct-sanitizer hostile-configuration regression before fix(reliability): bound rich clipboard resource preflight #164 was closed/superseded.
  • Pre-synchronization head 3460f2d4233177a29607dd9b181ff4fad3d363bf also exercises a host config getter that throws a primitive string; the real adapter returns '' and emits exactly one stable invalid_html observation without leaking the primitive.
  • Current synchronization merge 61936f2d9e26373115fed1dee1ce93387ebcfc12 incorporates exact protected main@3b38ead2d00f44eb578d0689087b9293b3dabe1e without changing the intended SafeClipboard ownership boundary. Predecessor workflow/review evidence does not transfer after this head movement.

Review contradiction resolved

A formal Cursor CHANGES_REQUESTED review on this lane asserted that WeakSet.prototype.has(primitive) throws. That premise is factually inconsistent with the ECMAScript weak-collection contract; the branch additionally guards primitives before the set lookup and proves the primitive-string path through the real adapter regression. The factually invalid review is dismissed and its only inline thread is resolved/outdated. Dismissal is not approval.

Unknown thrown values must still never be stringified, enumerated, coerced or prototype-inspected merely to classify them.

Current exact-head evidence

For unchanged exact head 61936f2d9e26373115fed1dee1ce93387ebcfc12 against protected main@3b38ead2d00f44eb578d0689087b9293b3dabe1e at the fresh pre-write refetch:

  • CI 32438908748: completed / success;
  • Security Scan 32438908692: completed / success;
  • SAST Semgrep 32438908699: completed / success;
  • effective formal CHANGES_REQUESTED: 0 after dismissal of the factually invalid predecessor review;
  • unresolved inline review threads: 0;
  • qualifying approving reviews: 0;
  • GitHub reports the Draft mechanically mergeable.

The older successful 31960596587 / 31960596547 / 31960596548 generations belong to predecessor head 3460f2d4233177a29607dd9b181ff4fad3d363bf and are non-passing for the current head. Repository-local exact-head success is technical evidence only; it does not replace applicable central required workflows, qualifying independent approval, fresh live-base reconciliation, or then-live governance. Pending/queued/skipped/cancelled/absent/stale/predecessor/status-only/model-only evidence remains non-passing.

Ownership / integration boundary

#351 owns hostile thrown-value containment. Draft #359 separately owns SafeClipboard resource preflight (#163/#327), and #176 owns the overlapping SafeLink/Markdown policy surface. Do not create a competing writer or reintroduce unrelated SafeLink/release-control changes here.

Keep #351 Draft/unmerged while #118 owns exact protected v0.6.0 publication/provenance/digest operational acceptance. Before any lifecycle transition refetch exact head/live protected tip, ancestry/mergeability, formal reviews/threads, live governance and every applicable repository/central workflow. Do not self-approve, transfer predecessor evidence, weaken gates, move protected main or fabricate release identity.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: apiAPI, protocol, event, or external contractarea: authAuthentication, authorization, identity, or tenant isolationarea: ci-cdCI, GitHub Actions, checks, release, or supply chainarea: securitySecurity boundary, hardening, or vulnerability preventionpriority: mediumNormal-priority or P2 workstatus: triagedOpen issue has an organization taxonomy assignmenttype: bugDefect or incorrect behavior

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions