feat: sanitize rich clipboard HTML - #65
Conversation
📝 WalkthroughWalkthroughSafeClipboard가 리치 HTML 클립보드를 allowlist 기반으로 정제하고, 제한 초과·설정·DOM 오류를 비공개 오류로 처리합니다. standalone 및 협업형 편집기가 이를 공유 확장으로 사용합니다. 관련 문서와 테스트, CI 실행 환경 및 checkout 보안 설정도 추가했습니다. ChangesSafeClipboard 리치 클립보드 정제
CI 실행 및 checkout 보안 강화
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Clipboard
participant CwlEditor
participant buildExtensions
participant SafeClipboard
participant TipTap
Clipboard->>CwlEditor: text/html 붙여넣기
CwlEditor->>buildExtensions: clipboard 설정 및 오류 콜백 전달
buildExtensions->>SafeClipboard: SafeClipboard 등록
SafeClipboard->>SafeClipboard: HTML 제한 및 allowlist 재구성
SafeClipboard->>TipTap: 정제 HTML 또는 빈 문자열 전달
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
seonghobae
left a comment
There was a problem hiding this comment.
Exact-head finding (valid, blocking Draft→Ready): the standalone/collaborative integration bypasses the sanitizer's fail-closed ClipboardConfig validation. buildExtensions() dereferences clipboard.maxHtmlBytes, maxNodes, and maxDepth before sanitizeRichClipboardHtml() runs, then constructs a fresh known-key object. As a result, an accessor/proxy configuration can execute or throw during editor construction, and unknown/symbol/non-enumerable configuration keys are silently discarded rather than producing the documented redacted invalid_configuration error on rich paste. This contradicts docs/clipboard-security.md (“Invalid configuration fails closed when rich HTML is pasted”) and the direct sanitizer contract.
Please fix test-first without weakening the current direct API: add a regression proving buildExtensions()/both React surfaces do not evaluate nested accessors at construction, preserve the original config object into SafeClipboard, and return '' plus one redacted invalid_configuration callback on paste. A bounded option such as config?: ClipboardConfig may coexist with the current direct numeric extension options; the transform should validate the preserved config at paste time. Keep callback liveness, SSR import safety, and 100% statement/branch/function/line coverage.
seonghobae
left a comment
There was a problem hiding this comment.
Exact-head CI triage for 26cd91f53d97a6897b3dc74753d7a67ef21750ea:
-
Valid implementation failure:
mso-hide: allsurvives becauseCSSStyleDeclaration.getPropertyValue('mso-hide')drops/does not expose the proprietary declaration in jsdom (and cannot be relied on uniformly across engines). Inspect the rawstyleattribute without executing anything, parse bounded declarations, and drop the subtree when an exact case-insensitivemso-hide: alldeclaration is present. Keep ordinary style attributes discarded from output and add variants for whitespace, case,!important, CSS comments, and misleading values. -
Incorrect assertion:
expect(sanitized).not.toMatch(/section|font/i)necessarily fails because the same test explicitly requires preserving visible textfont text. Parse the sanitized fragment and assertquerySelector('section, font') === nullwhile retainingfont text; do not weaken the intended visible-text preservation.
The earlier blocking integration finding remains unresolved: buildExtensions() still dereferences and reconstructs ClipboardConfig, bypassing accessor/unknown-key fail-closed validation on both React surfaces. Address all three issues test-first before moving Draft→Ready.
seonghobae
left a comment
There was a problem hiding this comment.
Additional exact-head standards/security findings for a1c952e7124e7fb37e6ff7f5efada756c24cb93f:
-
Invalid/nonexistent standards citation:
docs/doctoring/safe-rich-clipboard.mdcites a “W3C Working Draft dated 24 June 2026” andhttps://www.w3.org/TR/2026/WD-clipboard-apis-20260624/. The official current W3C TR page and publication history identify the latest published Working Draft as 24 November 2025 (https://www.w3.org/TR/clipboard-apis/,https://www.w3.org/standards/history/clipboard-apis/). Replace the nonexistent date/version with the current official publication, preserve the work-in-progress claim boundary, and add a deterministic documentation assertion so a future fabricated dated URL cannot recur. -
Paste-transform ordering is not yet defended: current TipTap documentation states that extension
transformPastedHTMLhooks are chained by extension priority, with higher priority running first and each later transform receiving the prior output (https://tiptap.dev/docs/editor/extensions/custom-extensions/create-new/extension).SafeClipboardcurrently uses default priority. A subsequent transform can therefore reintroduce resource-bearing or executable markup before ProseMirror parses it, contradicting the stated complete pre-parse trust boundary for modular composition. Set and document an explicit lowest-practical sanitizer priority (or otherwise make it the final transform), and add an integration test with competing transforms proving final pre-parse output is re-sanitized. Clearly document the residual boundary for a hostile host that deliberately installs a later transform. -
Browser-realistic assurance is missing for a bespoke sanitizer: current acceptance uses jsdom only, while the implementation depends on HTML fragment parsing, CSS declaration exposure, inertness, and serialization behavior. The first exact-head failure already demonstrated a jsdom/CSSOM mismatch for
mso-hide. Add deterministic cross-engine browser tests (Chromium, Firefox, and WebKit where supported) for the security corpus and output parity, or explicitly narrow the conformance claim and record the residual acquisition risk. OWASP currently recommends a maintained HTML sanitizer such as DOMPurify and regular patching; if the no-runtime-dependency bespoke reconstruction remains the decision, the doctoring record must explain the compensating fuzz/differential-testing and vulnerability-response obligations rather than implying OWASP directly endorses this implementation.
seonghobae
left a comment
There was a problem hiding this comment.
Correction to my review 4865041574: withdraw and supersede finding 1 about the W3C publication date. Direct verification of the official dated W3C publication confirms that https://www.w3.org/TR/2026/WD-clipboard-apis-20260624/ is the genuine Clipboard API and Events — W3C Working Draft, 24 June 2026. The existing doctoring citation is therefore valid on that basis and does not require replacement. I regret the earlier false finding, which resulted from relying on a stale publication-history search result rather than opening the official dated document.
The other findings in that review remain open unless subsequent commits address them: deterministic final-transform ordering under TipTap extension composition, and browser-realistic cross-engine assurance/explicit residual-risk documentation for the bespoke sanitizer.
The earlier hostile-config integration finding is now addressed at this exact head: buildExtensions() preserves the original ClipboardConfig object without dereferencing it, and validation remains deferred to paste time. This acknowledgement does not mark any unrelated finding resolved.
|
Exact-current-head review request for CI This cycle added a test-first fail-closed boundary for native @coderabbitai review A qualifying non-author independent approval and every branch-protection gate remain mandatory. Do not merge, update the branch, alter reviewer identities, or treat queued/pending/rate-limited evidence as success. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
repo='ContextualWisdomLab/inkspan'
pr='65'
base='66cf4b9e46b5cbbc936ba470da5d1e96ca1649af'
head='75e96ebc2a853f9315eb166b8eb98264109b238e'
printf '%s\n' '== Revision verification =='
printf 'checked-out='; git rev-parse HEAD
printf 'requested='; git rev-parse "$head"
printf 'base='; git rev-parse "$base"
git merge-base --is-ancestor "$base" "$head" && echo 'base-is-ancestor=yes'
printf '%s\n' '== Pull request and exact-head checks =='
gh pr view "$pr" --repo "$repo" \
--json headRefOid,baseRefOid,state,isDraft,mergeStateStatus,reviewDecision,statusCheckRollup,reviews \
--jq '{headRefOid,baseRefOid,state,isDraft,mergeStateStatus,reviewDecision,checks:[.statusCheckRollup[]|select(.name != null)|{name,status,conclusion,headSha}],reviews:[.reviews[]|{author:.author.login,state,commit:.commit.oid}]}'
gh api "repos/$repo/commits/$head/check-runs?per_page=100" \
--jq '[.check_runs[]|{name,status,conclusion,head_sha}]'
printf '%s\n' '== Unresolved review threads =='
gh api graphql \
-F owner='ContextualWisdomLab' -F name='inkspan' -F number="$pr" \
-f query='
query($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
reviewThreads(first: 100) { nodes { isResolved } }
}
}
}' --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)] | length'
printf '%s\n' '== Diff inventory and hygiene =='
git diff --stat "$base" "$head"
git diff --name-status "$base" "$head"
git diff --check "$base" "$head" || true
printf '%s\n' '== Changed TypeScript outlines =='
while IFS= read -r f; do
case "$f" in
*.ts|*.tsx)
printf '\n--- %s ---\n' "$f"
ast-grep outline "$f" --items all --type function,class,interface,type 2>/dev/null || true
;;
esac
done < <(git diff --name-only "$base" "$head")
printf '%s\n' '== Focused clipboard implementation and repair diff =='
ast-grep outline src/extensions/SafeClipboard.ts --items all || true
git diff --unified=100 "$base" "$head" -- \
src/extensions/SafeClipboard.ts \
src/extensions/SafeClipboard.test.ts \
src/extensions/SafeClipboard.securityRegression.test.ts \
src/extensions/SafeClipboard.coverageBranches.test.ts \
src/extensions/SafeClipboard.ambientDom.test.ts \
docs/clipboard-security.md \
docs/doctoring/safe-rich-clipboard.md \
CHANGELOG.md
printf '%s\n' '== Relevant sanitizer references and test declarations =='
rg -n -C 8 --glob '*.{ts,tsx,md}' \
'(progress|meter|noframes|noembed|mso-hide|title|details|dialog|transformPastedHTML|onClipboardError)' \
src docs CHANGELOG.md || trueLength of output: 12988 🏁 Script executed: #!/bin/bash
set -euo pipefail
base='66cf4b9e46b5cbbc936ba470da5d1e96ca1649af'
head='75e96ebc2a853f9315eb166b8eb98264109b238e'
printf '%s\n' '== SafeClipboard structure =='
ast-grep outline src/extensions/SafeClipboard.ts --items all || true
printf '%s\n' '== SafeClipboard implementation =='
sed -n '1,240p' src/extensions/SafeClipboard.ts
sed -n '241,480p' src/extensions/SafeClipboard.ts
sed -n '481,760p' src/extensions/SafeClipboard.ts
printf '%s\n' '== New native-widget and obsolete-fallback repair =='
git diff --unified=100 "$head^" "$head" -- \
src/extensions/SafeClipboard.ts \
src/extensions/SafeClipboard.securityRegression.test.ts \
src/extensions/SafeClipboard.test.ts \
docs/clipboard-security.md \
docs/doctoring/native-widget-fallback-content.md \
docs/doctoring/safe-rich-clipboard.md \
CHANGELOG.md
printf '%s\n' '== Focused sanitizer tests =='
rg -n -C 20 --glob '*.{test,spec}.{ts,tsx}' \
'(progress|meter|noframes|noembed|mso-hide|title|details|dialog|node limit|depth limit|byte limit)' \
src/extensions
printf '%s\n' '== Editor integration and extension assembly =='
cat -n src/extensions/kit.ts
cat -n src/components/CwlEditor.tsx
cat -n src/collaboration/CollaborativeCwlEditor.tsx
cat -n src/index.ts
cat -n src/types.ts
printf '%s\n' '== Clipboard integration test declarations =='
rg -n -C 10 --glob '*.{test,spec}.{ts,tsx}' \
'(SafeClipboard|transformPastedHTML|onClipboardError|clipboard)' \
src/components/CwlEditor.clipboard.test.tsx \
src/collaboration/CollaborativeCwlEditor.clipboard.test.tsx \
src/extensions/kit.test.ts
printf '%s\n' '== Full-diff safety checks =='
git diff --unified=0 "$base" "$head" | rg -n -i \
'^\+.*(-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----|api[_-]?key\s*[:=]|secret\s*[:=]|password\s*[:=]|authorization:\s*bearer)' \
|| true
git diff --summary "$base" "$head"Length of output: 132491
I found no actionable current-head source-level finding. The review verified these items:
I cannot approve this head yet. The exact-head A new exact-head verification is required after You are interacting with an AI system. |
|
@coderabbitai review Please review exact current head |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
repo='ContextualWisdomLab/inkspan'
pr='65'
base='66cf4b9e46b5cbbc936ba470da5d1e96ca1649af'
head='75e96ebc2a853f9315eb166b8eb98264109b238e'
printf '%s\n' '== Revision and pull-request state =='
printf 'checked-out='; git rev-parse HEAD
printf 'requested='; git rev-parse "$head"
printf 'base='; git rev-parse "$base"
git merge-base --is-ancestor "$base" "$head" && echo 'base-is-ancestor=yes'
gh pr view "$pr" --repo "$repo" \
--json headRefOid,baseRefOid,state,isDraft,mergeStateStatus,reviewDecision,statusCheckRollup \
--jq '{headRefOid,baseRefOid,state,isDraft,mergeStateStatus,reviewDecision,checks:[.statusCheckRollup[]|select(.name != null)|{name,status,conclusion,headSha}]}'
printf '%s\n' '== Exact-head check runs =='
gh api "repos/$repo/commits/$head/check-runs?per_page=100" \
--jq '[.check_runs[]|{name,status,conclusion,head_sha}]'
printf '%s\n' '== Unresolved inline review threads =='
gh api graphql \
-F owner='ContextualWisdomLab' -F name='inkspan' -F number="$pr" \
-f query='
query($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
reviewThreads(first: 100) { nodes { isResolved } }
}
}
}' --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)] | length'
printf '%s\n' '== Full diff inventory and hygiene =='
git diff --stat "$base" "$head"
git diff --name-status "$base" "$head"
git diff --check "$base" "$head" || true
printf '%s\n' '== Changed TypeScript structure =='
while IFS= read -r f; do
case "$f" in
*.ts|*.tsx)
printf '\n--- %s ---\n' "$f"
ast-grep outline "$f" --items all --type function,class,interface,type 2>/dev/null || true
;;
esac
done < <(git diff --name-only "$base" "$head")
printf '%s\n' '== Sanitizer and editor implementation =='
for f in \
src/extensions/SafeClipboard.ts \
src/extensions/SafeLink.ts \
src/extensions/kit.ts \
src/components/CwlEditor.tsx \
src/collaboration/CollaborativeCwlEditor.tsx \
src/index.ts \
src/types.ts; do
if [ -f "$f" ]; then
printf '\n--- %s ---\n' "$f"
cat -n "$f"
fi
done
printf '%s\n' '== Sanitizer and integration tests =='
for f in \
src/extensions/SafeClipboard.test.ts \
src/extensions/SafeClipboard.securityRegression.test.ts \
src/extensions/SafeClipboard.coverageBranches.test.ts \
src/extensions/SafeClipboard.ambientDom.test.ts \
src/components/CwlEditor.clipboard.test.tsx \
src/collaboration/CollaborativeCwlEditor.clipboard.test.tsx \
src/extensions/kit.test.ts; do
if [ -f "$f" ]; then
printf '\n--- %s ---\n' "$f"
cat -n "$f"
fi
done
printf '%s\n' '== Relevant direct references =='
rg -n -C 6 --glob '*.{ts,tsx}' \
'(SafeClipboard|transformPastedHTML|onClipboardError|mso-hide|progress|meter|noframes|noembed|title|details|dialog)' \
src || trueLength of output: 12754 🏁 Script executed: #!/bin/bash
set -euo pipefail
base='66cf4b9e46b5cbbc936ba470da5d1e96ca1649af'
head='75e96ebc2a853f9315eb166b8eb98264109b238e'
printf '%s\n' '== SafeClipboard structure and implementation =='
ast-grep outline src/extensions/SafeClipboard.ts --items all || true
cat -n src/extensions/SafeClipboard.ts
printf '%s\n' '== SafeClipboard tests =='
for f in \
src/extensions/SafeClipboard.test.ts \
src/extensions/SafeClipboard.securityRegression.test.ts \
src/extensions/SafeClipboard.coverageBranches.test.ts \
src/extensions/SafeClipboard.ambientDom.test.ts; do
printf '\n--- %s ---\n' "$f"
cat -n "$f"
done
printf '%s\n' '== Extension assembly and editor integration =='
for f in \
src/extensions/SafeLink.ts \
src/extensions/kit.ts \
src/components/CwlEditor.tsx \
src/collaboration/CollaborativeCwlEditor.tsx \
src/index.ts \
src/types.ts; do
printf '\n--- %s ---\n' "$f"
cat -n "$f"
done
printf '%s\n' '== Integration tests =='
for f in \
src/extensions/kit.test.ts \
src/components/CwlEditor.clipboard.test.tsx \
src/collaboration/CollaborativeCwlEditor.clipboard.test.tsx; do
printf '\n--- %s ---\n' "$f"
cat -n "$f"
done
printf '%s\n' '== Runtime source diff =='
git diff --unified=100 "$base" "$head" -- \
src/extensions/SafeClipboard.ts \
src/extensions/SafeLink.ts \
src/extensions/kit.ts \
src/components/CwlEditor.tsx \
src/collaboration/CollaborativeCwlEditor.tsx \
src/index.ts \
src/types.ts
printf '%s\n' '== All transform ordering and sanitizer configuration sites =='
rg -n -C 8 --glob '*.{ts,tsx}' \
'(transformPastedHTML|SafeClipboard\.configure|SafeClipboard|onClipboardError|priority\s*:)' \
srcLength of output: 240916
I found no actionable current-head source-level finding. The review verified the bounded reconstruction allowlist, dropped-subtree handling, The requested commit remains the current pull-request head. The pull request has zero unresolved inline threads. I cannot issue approval for this head yet. The exact-head
|
|
@opencode-agent review Perform read-only independent review of exact current head |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
src/extensions/SafeClipboard.ts (1)
534-548: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value불안전 링크 분기를 단순화하세요.
539-541행의
childParent = frame.outputParent재대입은 무연산입니다.childParent는 535행에서 이미 같은 값입니다. 또한 같은isSafeLinkHref검사가copyAllowedAttributes(401행)에서 다시 실행됩니다. 조건을 한 번만 계산하면 두 지점의 정책이 갈라질 위험이 사라집니다.♻️ 제안 리팩터
const outputName = normalizedOutputElement(sourceName); + const unwrapUnsafeLink = + outputName === 'a' && + !isSafeLinkHref(sourceElement.getAttribute('href')); let childParent = frame.outputParent; - if (outputName !== null) { - if ( - outputName === 'a' && - !isSafeLinkHref(sourceElement.getAttribute('href')) - ) { - childParent = frame.outputParent; - } else { - const outputElement = inertDocument.createElement(outputName); - copyAllowedAttributes(sourceElement, outputElement); - frame.outputParent.appendChild(outputElement); - childParent = outputElement; - } + if (outputName !== null && !unwrapUnsafeLink) { + const outputElement = inertDocument.createElement(outputName); + copyAllowedAttributes(sourceElement, outputElement); + frame.outputParent.appendChild(outputElement); + childParent = outputElement; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/extensions/SafeClipboard.ts` around lines 534 - 548, In the output-element handling around normalizedOutputElement, remove the redundant unsafe-link branch that reassigns childParent to frame.outputParent, and compute the isSafeLinkHref result once for reuse with copyAllowedAttributes. Preserve the existing behavior of skipping unsafe anchor creation while ensuring the same safety decision is used consistently.src/types.ts (1)
272-281: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
clipboard의 적용 시점을 주석에 명시하세요.
clipboard정책은 편집기 생성 시buildExtensions()에 전달됩니다. 편집기 생성 후 새clipboard객체로 교체해도 기존 확장의 정책은 변경되지 않습니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/types.ts` around lines 272 - 281, Update the documentation comment for the clipboard property in the editor configuration type to state that the policy is applied when the editor is created through buildExtensions() and that replacing the clipboard object afterward does not update existing extensions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/doctoring/closed-interactive-content.md`:
- Around line 99-100: Update the date metadata in
docs/doctoring/closed-interactive-content.md lines 99-100 by changing or
removing “Retrieved August 6, 2026” so it is not later than the August 5, 2026
review date; also update “Decision date: 2026-08-06” in
docs/doctoring/native-widget-fallback-content.md lines 3-4 to the actual
decision date of 2026-08-05.
In `@docs/doctoring/safe-rich-clipboard.md`:
- Around line 127-141: Update the “Dropped subtrees and privacy boundary”
section to include or clearly cross-reference the established rules for removing
progress, meter, noframes, and noembed elements; handling closed details and
dialog elements; and parsing CSS escapes and EOF-terminated comments in Office
hidden-content detection. Keep the existing removal and mso-hide behavior intact
while synchronizing this document with the related doctoring records.
- Around line 162-165: Update the memory-behavior statement in the documentation
near “Traversal is iterative” to remove the claim that parsing allocates at most
the bounded input representation. Describe only the enforced UTF-8 input, node,
and depth limits, and acknowledge that parsed and output trees are additionally
allocated without claiming an explicit heap or DOM memory cap.
In `@docs/superpowers/plans/2026-08-05-safe-rich-clipboard.md`:
- Around line 121-134: Update the defect count in the introductory statement to
match the four items listed under the checklist, or regroup the checklist so it
contains three implementation defects and one assurance gap. Keep the
Configuration boundary, Office hidden content, Transform ordering, and Incorrect
test assertion entries accurately represented for review traceability.
In `@src/extensions/SafeClipboard.ts`:
- Around line 10-11: Validate DEFAULT_CLIPBOARD_MAX_NODES against the
repository’s representative Word HTML fixture by measuring its traversed node
count. If the fixture exceeds the default, either raise the default to cover it
or update transformPastedHTML to provide an explicit plain-text fallback while
preserving the existing behavior for smaller inputs.
---
Nitpick comments:
In `@src/extensions/SafeClipboard.ts`:
- Around line 534-548: In the output-element handling around
normalizedOutputElement, remove the redundant unsafe-link branch that reassigns
childParent to frame.outputParent, and compute the isSafeLinkHref result once
for reuse with copyAllowedAttributes. Preserve the existing behavior of skipping
unsafe anchor creation while ensuring the same safety decision is used
consistently.
In `@src/types.ts`:
- Around line 272-281: Update the documentation comment for the clipboard
property in the editor configuration type to state that the policy is applied
when the editor is created through buildExtensions() and that replacing the
clipboard object afterward does not update existing extensions.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 14587e8c-8214-472e-a0e6-c65e5fd56324
📒 Files selected for processing (26)
.github/workflows/ci.ymlARCHITECTURE.mdCHANGELOG.mddocs/clipboard-security.mddocs/doctoring/closed-interactive-content.mddocs/doctoring/css-escaped-office-hidden-content.mddocs/doctoring/native-widget-fallback-content.mddocs/doctoring/safe-rich-clipboard.mddocs/superpowers/plans/2026-08-05-safe-rich-clipboard.mddocs/superpowers/specs/2026-08-05-safe-rich-clipboard-design.mdsrc/clipboardDocumentation.test.tssrc/collaboration/CollaborativeCwlEditor.clipboard.test.tsxsrc/collaboration/CollaborativeCwlEditor.tsxsrc/components/CwlEditor.clipboard.test.tsxsrc/components/CwlEditor.tsxsrc/extensions/SafeClipboard.ambientDom.test.tssrc/extensions/SafeClipboard.coverageBranches.test.tssrc/extensions/SafeClipboard.datalistRegression.test.tssrc/extensions/SafeClipboard.securityRegression.test.tssrc/extensions/SafeClipboard.test.tssrc/extensions/SafeClipboard.tssrc/extensions/kit.test.tssrc/extensions/kit.tssrc/index.tssrc/types.tssrc/workflowExactHead.test.ts
|
@coderabbitai review Please review exact head |
Purpose
Add a bounded, fail-closed rich-HTML paste boundary for Microsoft Word, Google Docs, email, and web content before ProseMirror parses or inserts it.
Current implementation
titlesubtrees, forms, embedded content, SVG/MathML, media, hidden subtrees, comments, remote/local images, event handlers, IDs, classes, proprietary attributes, and arbitrary styles;display: none,visibility: hidden,visibility: collapse,hidden,aria-hidden="true", and exact Officemso-hide: allas complete hidden-subtree boundaries;details, drop closeddialogsubtrees, and sanitize rendered contents of open variants;progress,meter,datalist,noframes, andnoembedsubtrees so native-widget, hidden-suggestion, or obsolete fallback text cannot become ordinary editor prose after wrapper removal;mso-hide: allmatching while retaining malformed and look-alike declarations as visible text;Test-first hardening
dac8fef84baa313d9cada2e1c3c2bbf658b7675d/ GREENe6d51c9b3fdaffc707d11649438aa9dcdd9f932e: consume a final CSS comment through EOF somso-hide: all/*cannot surface hidden text;c11617099f03cf691dfa5d7c116650faf5ad2b40/ GREENd9fcc0f1da56e702b4efcb1e679cba70f2fb5efb: discardtitlemetadata subtrees instead of unwrapping metadata text into the editor;eebf18a623b7702e55995c4d406fe07203edfd39/ GREENe84261e74dcb07ed6afec9a934adcf5b8b1e41e3: prevent closed disclosure and dialog source-only text from becoming visible editor content while retaining rendered summary/open content;6e6d48ff8a377bab875d6a520580e65d3489f78e/ GREEN01683cc89faadfcacf05f358e34b6a1812e61e77: preventprogress,meter,noframes, andnoembedfallback descendants from surfacing after wrapper removal;a48d81eac541effea5d7742a47c0ca6bd01d04ed/ GREENec5d602bcf4f8e82fcc3136a82e354816913f6df: prevent hiddendatalistsuggestions and down-level fallback descendants from surfacing as ordinary editor prose;6dadce2459594d1721468086c8c60b8605ce0f52/ GREEN00d163eecffafa0b4c6f108f45bc4af39273c41e: prove one safe anchorhrefis read and classified exactly once and reuse the validated value without a second policy evaluation;77ea951c54230f896788157913a924186ae487f7/ GREEN503ba7f74001c2bfc05bee76d154fc7b162200f3: preventvisibility: collapsetable rows, cells, and ordinary subtrees from exposing non-rendered source text while retaining visible siblings; andReview and documentation reconciliation
docs/doctoring/visibility-collapse-hidden-content.mdwith W3C CSS visibility/collapse rationale, APA 7th references, test evidence, rollback, and compatibility boundaries;Unresolved inline review threads: 0.
Exact-head verification
Verified head:
95dc3eb8a7d40f049859d94a8019c594607db612.31058980885: passed TypeScript typecheck, 100% production statement/branch/function/line coverage, deterministic builds, packed ESM/CommonJS/strict-TypeScript consumers, SSR-safe imports, demo build, and Office Python 3.11/3.14 dependency/docstring/100%-branch-coverage/wheel/package gates;31058980977: passed;31058980840: passed; andThe predecessor RED head
77ea951c54230f896788157913a924186ae487f7failed CI run31058675380as intended before the production repair. Its successful security runs are retained only as test-first history and are not used as merge evidence.Standards and assurance boundary
The design and doctoring records cite the official W3C Clipboard API and Events Working Draft, WHATWG HTML parsing/rendering semantics, W3C CSS visibility and table-collapse semantics, W3C CSS Syntax Module Level 3 comment consumption, OWASP guidance, and TipTap/ProseMirror paste-transform contracts in APA 7th style. Current jsdom evidence is not represented as cross-engine browser conformance; a dependency-locked Chromium, Firefox, and WebKit differential corpus remains a publication gate for 0.6.0.
Remaining merge gates
Do not merge until:
95dc3eb8a7d40f049859d94a8019c594607db612without an infrastructure-only rate-limit result;The latest CodeRabbit attempt was rate-limited and is not approval or success evidence. The default paste behavior change targets 0.6.0 only through a separate verified release PR.