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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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 preventionbugSomething isn't workingpriority: 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