Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
147 commits
Select commit Hold shift + click to select a range
b33cde3
docs: design safe rich clipboard ingestion
seonghobae Aug 5, 2026
c33d642
docs: plan safe rich clipboard implementation
seonghobae Aug 5, 2026
c9ab5f8
test(clipboard): define safe rich paste contract
seonghobae Aug 5, 2026
16272b5
feat(clipboard): implement bounded rich HTML sanitizer
seonghobae Aug 5, 2026
6e0115b
feat(clipboard): add sanitizer to shared editor kit
seonghobae Aug 5, 2026
f3f0d67
test(clipboard): cover shared kit configuration
seonghobae Aug 5, 2026
c71acb0
feat(clipboard): expose editor paste policy props
seonghobae Aug 5, 2026
d06701a
feat(clipboard): wire standalone paste policy
seonghobae Aug 5, 2026
54736e2
feat(clipboard): wire collaborative paste policy
seonghobae Aug 5, 2026
7d8d1de
test(clipboard): cover standalone editor integration
seonghobae Aug 5, 2026
23b486d
test(clipboard): cover collaborative editor integration
seonghobae Aug 5, 2026
1a6b0eb
test(clipboard): use inferred extension contract
seonghobae Aug 5, 2026
e52c39a
feat(clipboard): export safe rich paste API
seonghobae Aug 5, 2026
2faf969
docs(clipboard): add operator security guide
seonghobae Aug 5, 2026
ffb85f2
docs(doctoring): record safe rich clipboard boundary
seonghobae Aug 5, 2026
4168586
docs: add Inkspan architecture and trust boundaries
seonghobae Aug 5, 2026
34baf71
docs(changelog): record safe rich clipboard slice
seonghobae Aug 5, 2026
26cd91f
fix(clipboard): type paste transform input
seonghobae Aug 5, 2026
9d30ecb
test(clipboard): preserve untrusted configuration identity
seonghobae Aug 5, 2026
80a5233
test(clipboard): use deterministic accessor spy
seonghobae Aug 5, 2026
dc85544
test(clipboard): defer standalone config validation to paste
seonghobae Aug 5, 2026
cc4b05d
test(clipboard): defer collaborative config validation
seonghobae Aug 5, 2026
a1c952e
fix(clipboard): validate preserved host config at paste
seonghobae Aug 5, 2026
2c67c41
fix(clipboard): preserve host config until paste validation
seonghobae Aug 5, 2026
9f188f2
test(clipboard): cover hidden-style and transform-order regressions
seonghobae Aug 5, 2026
f948fdd
fix(clipboard): harden Office hiding and final transform order
seonghobae Aug 5, 2026
c34a5bb
test(clipboard): assert removed elements through parsed output
seonghobae Aug 5, 2026
e2998b9
test(clipboard): inspect unwrapped elements structurally
seonghobae Aug 5, 2026
bae6033
test(docs): require clipboard assurance boundaries
seonghobae Aug 5, 2026
0772cf1
docs(clipboard): define paste and browser assurance boundaries
seonghobae Aug 5, 2026
aa23f03
docs(doctoring): record clipboard residual assurance risk
seonghobae Aug 5, 2026
a387a09
docs(changelog): record clipboard assurance hardening
seonghobae Aug 5, 2026
41c91b3
test(ci): require immutable exact-head verification
seonghobae Aug 5, 2026
e2fff37
ci: verify immutable exact pull-request heads
seonghobae Aug 5, 2026
7e48536
test(docs): require reconciled clipboard design records
seonghobae Aug 5, 2026
94df928
test: normalize clipboard documentation contracts
seonghobae Aug 5, 2026
db038d2
docs(clipboard): reconcile reviewed design and assurance limits
seonghobae Aug 5, 2026
94ed221
docs(clipboard): reconcile implementation plan after review
seonghobae Aug 5, 2026
610318d
test: cover clipboard fail-closed branches
seonghobae Aug 5, 2026
c3615e5
test(clipboard): cover absent ambient DOM branch
seonghobae Aug 5, 2026
e15104b
test(clipboard): cover CSS-escaped hidden declarations
seonghobae Aug 5, 2026
8991804
test(clipboard): cover CSS escape decoding boundaries
seonghobae Aug 5, 2026
5fe4915
fix(clipboard): decode bounded CSS escapes for hidden content
seonghobae Aug 5, 2026
8a704b6
test(docs): require CSS escape assurance evidence
seonghobae Aug 5, 2026
0be036e
docs(clipboard): document bounded CSS escape decoding
seonghobae Aug 5, 2026
b7aa88b
docs(doctoring): record CSS escape hidden-content defense
seonghobae Aug 5, 2026
cc8797b
test(docs): bind CSS escape decision to doctoring addendum
seonghobae Aug 5, 2026
d01dceb
docs: record CSS-escaped Office hidden handling
seonghobae Aug 5, 2026
3096782
test: close residual clipboard branch coverage
seonghobae Aug 5, 2026
dac8fef
test(clipboard): cover EOF-terminated CSS comments
seonghobae Aug 5, 2026
e6d51c9
fix(clipboard): consume EOF-terminated CSS comments
seonghobae Aug 5, 2026
e12fec9
docs(clipboard): record EOF CSS comment boundary
seonghobae Aug 5, 2026
ae38958
docs(clipboard): document EOF comment handling
seonghobae Aug 5, 2026
fd3b5ae
docs(changelog): record EOF CSS comment hardening
seonghobae Aug 5, 2026
c116170
test(clipboard): reject title metadata subtrees
seonghobae Aug 5, 2026
d9fcc0f
fix(clipboard): drop title metadata subtrees
seonghobae Aug 5, 2026
eebf18a
test(clipboard): expose closed disclosure text leakage
seonghobae Aug 5, 2026
e84261e
fix(clipboard): exclude closed interactive content
seonghobae Aug 5, 2026
2e52ec5
docs(clipboard): define closed interactive content boundary
seonghobae Aug 5, 2026
f0b0e49
docs(doctoring): record closed interactive clipboard boundary
seonghobae Aug 5, 2026
111925b
docs(changelog): record closed interactive sanitizer hardening
seonghobae Aug 5, 2026
ba97291
test(docs): bind closed interactive clipboard policy
seonghobae Aug 5, 2026
6e6d48f
test: reject native-widget fallback text
seonghobae Aug 5, 2026
01683cc
fix: drop native-widget fallback subtrees
seonghobae Aug 5, 2026
2269d36
docs: record fallback-content boundary
seonghobae Aug 5, 2026
6ab2272
docs: explain fallback-content sanitization
seonghobae Aug 5, 2026
aa7a556
docs: record fallback-content change
seonghobae Aug 5, 2026
75e96eb
test: bind fallback-content documentation
seonghobae Aug 5, 2026
a48d81e
test(clipboard): expose hidden datalist fallback text
seonghobae Aug 5, 2026
ec5d602
fix(clipboard): drop hidden datalist suggestion content
seonghobae Aug 5, 2026
5a17ec2
docs(clipboard): record hidden datalist boundary
seonghobae Aug 5, 2026
80e073c
docs(clipboard): document hidden datalist suggestions
seonghobae Aug 5, 2026
491e97e
test(docs): require datalist visibility evidence
seonghobae Aug 5, 2026
83429c3
docs(changelog): record datalist visibility boundary
seonghobae Aug 5, 2026
8335871
docs(clipboard): correct interactive record date metadata
seonghobae Aug 5, 2026
d356f08
docs(clipboard): correct native fallback decision date
seonghobae Aug 5, 2026
602bcd2
docs(clipboard): synchronize hidden boundaries and memory claims
seonghobae Aug 5, 2026
f8c24f6
docs(clipboard): correct review defect count
seonghobae Aug 5, 2026
46e744e
test(clipboard): prove representative Word capacity
seonghobae Aug 5, 2026
04657df
docs(types): clarify clipboard policy lifecycle
seonghobae Aug 5, 2026
6dadce2
test(clipboard): require one safe-link policy decision
seonghobae Aug 5, 2026
00d163e
refactor(clipboard): reuse safe-link classification
seonghobae Aug 5, 2026
77ea951
test(clipboard): reject visibility-collapse content
seonghobae Aug 6, 2026
503ba7f
fix(clipboard): drop visibility-collapse subtrees
seonghobae Aug 6, 2026
7a7efd1
docs(clipboard): record visibility-collapse boundary
seonghobae Aug 6, 2026
95dc3eb
test(docs): bind visibility-collapse decision
seonghobae Aug 6, 2026
3e369cb
test(clipboard): expose content-visibility hidden gap
seonghobae Aug 6, 2026
28df0b9
fix(clipboard): drop content-visibility hidden subtrees
seonghobae Aug 6, 2026
b830f49
test(docs): bind content-visibility hidden policy
seonghobae Aug 6, 2026
f5af554
docs(clipboard): define content-visibility hidden boundary
seonghobae Aug 6, 2026
b82405f
docs(doctoring): record content-visibility hidden decision
seonghobae Aug 6, 2026
20238fb
docs(architecture): preserve integrated modular contract
seonghobae Aug 6, 2026
baeaa89
docs(changelog): reconcile architecture and clipboard evidence
seonghobae Aug 6, 2026
ca7b417
test(ci): preserve exact-head evidence contract
seonghobae Aug 6, 2026
54d8a68
docs(ci): preserve exact-head evidence record
seonghobae Aug 6, 2026
2d47306
docs(changelog): isolate pending clipboard notes from integrated history
seonghobae Aug 6, 2026
50dcde8
Merge 2d47306043581a18db7e6ba0847e8368a0e34982 into ca49a3249403be88b…
seonghobae Aug 6, 2026
d69575e
test(clipboard): expose hidden popover content
seonghobae Aug 6, 2026
ee14bdb
fix(clipboard): drop hidden popover subtrees
seonghobae Aug 6, 2026
0af1410
test(clipboard): cover invalid popover default
seonghobae Aug 6, 2026
36e0e5a
docs(clipboard): record popover hidden-content policy
seonghobae Aug 6, 2026
2d38ccb
docs(clipboard): explain popover privacy boundary
seonghobae Aug 6, 2026
639eaf0
docs(changelog): record popover hidden-content fix
seonghobae Aug 6, 2026
7f0fa36
test(docs): bind popover policy evidence
seonghobae Aug 6, 2026
486846a
test(security): expose DOM capability reflection leakage
seonghobae Aug 6, 2026
e438765
fix(security): redact DOM capability reflection failures
seonghobae Aug 6, 2026
73585e5
docs(security): record DOM reflection redaction decision
seonghobae Aug 6, 2026
1e27f14
docs(changelog): record DOM capability reflection repair
seonghobae Aug 6, 2026
5c84b73
docs(security): document DOM capability failure boundary
seonghobae Aug 6, 2026
0c63934
test(docs): bind DOM reflection security evidence
seonghobae Aug 6, 2026
2e1d634
test(clipboard): exercise actual ProseMirror paste pipeline
seonghobae Aug 7, 2026
88bdd07
fix(clipboard): install sanitizer in ProseMirror paste pipeline
seonghobae Aug 7, 2026
ce95e56
fix(clipboard): use registered TipTap v2 adapter
seonghobae Aug 7, 2026
018b55e
fix(clipboard): export registered SafeClipboard adapter
seonghobae Aug 7, 2026
8070ed0
test(clipboard): verify collaborative ProseMirror paste path
seonghobae Aug 7, 2026
9ed36e6
test(clipboard): cover TipTap v2 adapter and transform order
seonghobae Aug 7, 2026
9ba9c60
test(clipboard): bind plugin transform context
seonghobae Aug 7, 2026
88d5ebd
test(clipboard): use real ProseMirror transform ordering
seonghobae Aug 7, 2026
dc4c8d7
test(clipboard): cover preserved nested config branch
seonghobae Aug 7, 2026
a646a99
test(docs): require TipTap v2 paste adapter doctoring
seonghobae Aug 7, 2026
ea74226
docs(clipboard): record TipTap v2 paste adapter root cause
seonghobae Aug 7, 2026
f43848e
test(api): prove registered SafeClipboard exports
seonghobae Aug 7, 2026
257c494
docs(changelog): record TipTap v2 paste adapter repair
seonghobae Aug 7, 2026
f0416d0
test(docs): bind clipboard adapter changelog evidence
seonghobae Aug 7, 2026
338c095
chore(clipboard): reconcile protected release gate
seonghobae Aug 9, 2026
d5ab65d
Merge branch 'main' into feat/safe-rich-clipboard
opencode-agent[bot] Aug 9, 2026
6cb0321
test(clipboard): classify DOM factory invocation failures
seonghobae Aug 9, 2026
2a095ed
fix(clipboard): preserve DOM capability failure classification
seonghobae Aug 9, 2026
0dd5944
test(clipboard): cover nested config through ProseMirror plugin
seonghobae Aug 9, 2026
c6fa1f4
docs(clipboard): bound visibility claims to inline declarations
seonghobae Aug 9, 2026
7868195
docs(clipboard): synchronize hidden-content boundaries
seonghobae Aug 9, 2026
260935f
Merge branch 'main' into feat/safe-rich-clipboard
opencode-agent[bot] Aug 9, 2026
9f66368
test(clipboard): align DOM factory failure contract
seonghobae Aug 9, 2026
0e1e76d
test(clipboard): cover fail-closed sanitizer branches
seonghobae Aug 9, 2026
fbf8b03
Merge branch 'main' into feat/safe-rich-clipboard
opencode-agent[bot] Aug 9, 2026
71a5456
Merge branch 'main' into feat/safe-rich-clipboard
opencode-agent[bot] Aug 9, 2026
8f5ad15
fix(clipboard): reconcile transition exports from current main
seonghobae Aug 9, 2026
dd7007a
Merge branch 'main' into feat/safe-rich-clipboard
opencode-agent[bot] Aug 9, 2026
d57d300
test(clipboard): distinguish inert-document creation failures
seonghobae Aug 9, 2026
64b4616
Merge branch 'main' into feat/safe-rich-clipboard
opencode-agent[bot] Aug 9, 2026
57f3581
test(clipboard): align inert-document failure classification
seonghobae Aug 9, 2026
559f3d5
test(docs): bind inert-document failure classification
seonghobae Aug 9, 2026
d6e6e16
docs(clipboard): classify inert-document setup as DOM capability
seonghobae Aug 9, 2026
38caec2
docs(clipboard): align DOM capability error guidance
seonghobae Aug 9, 2026
289c8fd
Merge branch 'main' into feat/safe-rich-clipboard
opencode-agent[bot] Aug 9, 2026
a530a44
test(docs): bind DOM capability contract to current semantics
seonghobae Aug 9, 2026
73b3148
test(docs): ignore prose capitalization in DOM capability contract
seonghobae Aug 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Inkspan Architecture

Inkspan is a modular rich-document engine that can run as a standalone React
editor or as a provider-neutral Yjs collaboration module. The repository keeps
probabilistic, transport, persistence, identity, and tenant policy outside the
editor while supplying deterministic document, safety, accessibility, and
interoperability contracts.

```mermaid
flowchart TB
Host[Host application / naruon / CWL service]
React[Standalone CwlEditor]
Collab[CollaborativeCwlEditor]
Kit[Shared TipTap extension kit]
Clip[Safe rich clipboard boundary]
Link[SafeLink URI boundary]
Image[Base64Image binary-image boundary]
PM[TipTap / ProseMirror document]
Snap[Snapshots and versioned envelopes]
Rev[Canonical revision evidence]
Auto[Single-flight durable autosave session]
HostStore[Host transport, authorization, tenant isolation, persistence]
Office[Deterministic Office renderer]

Host --> React
Host --> Collab
React --> Kit
Collab --> Kit
Kit --> Clip
Kit --> Link
Kit --> Image
Clip --> PM
Link --> PM
Image --> PM
PM --> Snap
Snap --> Rev
Rev --> Auto
Auto --> HostStore
Snap --> Office
```

## Module boundaries

### Interactive editor graph

- `src/components/` owns standalone React lifecycle, form integration,
accessibility attributes, editor callbacks, and imperative handles.
- `src/collaboration/` owns the provider-neutral Yjs editor and public presence
projection. Hosts own network/provider lifecycle and authorization.
- `src/extensions/` owns shared ProseMirror ingress and transaction policies.

### Deterministic document graph

- `documentEnvelope*` owns versioned, resource-bounded, duplicate-name-safe
structural persistence.
- `documentRevisionEvidence*` owns RFC 8785 canonical bytes and SHA-256 equality
evidence.
- `autosave/` owns bounded process-local scheduling and server-validator handoff,
not durable storage or transport.
- `office/` owns network-free JSON-to-DOCX/XLSX/PPTX rendering.

### Trust boundaries

```mermaid
flowchart LR
U[Untrusted clipboard / host input / remote collaboration update]
V[Bounded validation and semantic reconstruction]
D[Validated ProseMirror document]
E[Detached envelope and revision evidence]
H[Host-authorized durable transaction]

U --> V --> D --> E --> H
```

Untrusted content never receives authority from its text or markup. Clipboard
HTML is parsed into an inert tree and reconstructed through a positive allowlist;
unsafe links, resource-bearing HTML images, active elements, hidden content,
and unbounded structures fail closed. Direct document writes and collaboration
updates remain protected by active-schema, SafeLink, and inline-image policies.

## Ownership matrix

| Concern | Inkspan | Host or integrating service |
| --- | --- | --- |
| Editor schema and deterministic serialization | Owns | Consumes |
| Clipboard, link, and inline-image ingress policy | Owns | Chooses documented limits and UX |
| Accessibility semantics and document callbacks | Owns | Supplies labels, errors, and workflow |
| Local collaboration binding | Owns | Owns Yjs document/provider lifecycle |
| Local single-flight autosave ordering | Owns | Chooses enqueue/debounce timing |
| Network, credentials, authentication | Does not own | Owns |
| Authorization and tenant isolation | Does not own | Owns |
| Durable persistence and atomic compare/commit | Does not own | Owns |
| Migration, retention, backup, residency, audit | Does not own | Owns |
| LLM/provider selection and model-use policy | Does not own | Owns |

## Compatibility requirements

- Standalone operation must not require naruon or any central CWL service.
- Integration surfaces must remain narrow enough for naruon compose, `ui.panel`,
contextual-orchestrator, and other repositories to provide host policy without
forking Inkspan.
- Framework-independent package subpaths must remain free of React, TipTap UI,
ProseMirror UI, Yjs, DOM, provider SDK, network, and credential dependencies
unless their documented contract explicitly requires one.
- Database objects are host-owned. New objects must use at least two descriptive
words and prefer `snake_case`.
- Default behavior changes require a minor version and a verified release-only
pull request after feature integration.

## Quality gates

Every production change is expected to maintain:

- 100% production statement, branch, function, and line coverage;
- complete public module, type, class, method, function, and property docs;
- realistic security, interoperability, concurrency, and package-consumer tests;
- deterministic builds and package contents;
- exact-current-head CI, SAST, security, automated review, independent approval,
and branch protection;
- `CHANGELOG.md`, operator docs, and APA 7 doctoring where standards or research
materially inform the design.
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,36 @@ Historical release entries from **0.1.0 through 0.5.27** are preserved verbatim

## [Unreleased]

### Added
- Added a default `SafeClipboard` TipTap extension and `sanitizeRichClipboardHtml()` API that reconstruct browser-provided `text/html` through a strict semantic allowlist before ProseMirror parsing
- Added `clipboard` byte/node/depth limits and a live `onClipboardError` observer to standalone and provider-neutral Yjs editor surfaces
- Added root architecture, operator guidance, design, implementation-plan, and APA 7th doctoring records for the clipboard trust boundary

### Changed
- Rich HTML pasted from Word, Google Docs, email, and web pages now keeps supported structure and narrowly mapped bold/italic/underline/strike semantics while discarding arbitrary source styling and proprietary metadata
- Standalone and collaborative editors now use exactly one shared clipboard policy through `buildExtensions()`
- Because default rich-HTML paste behavior changes, the integrated feature targets the next minor release, **0.6.0**, only after a separate verified release PR

### Security
- Active, embedded, form, metadata, media, SVG/MathML, template, resource-bearing, hidden, and HTML-image subtrees are removed before insertion
- Unsafe and credential-bearing links are unwrapped while visible text remains; SafeLink-approved links retain only exact `href` and fixed `noopener noreferrer nofollow`
- IDs, classes, styles, event handlers, `data-*`, arbitrary ARIA, `contenteditable`, remote resources, local-file references, and Office/Google attributes never reach the output fragment
- UTF-8 bytes, traversed nodes, and source depth are bounded; configuration accessors, symbols, unknown fields, invalid numbers, and reflection failures fail closed with static redacted error codes
- Host error observers cannot weaken the rejection result and no rejected source HTML, URL, attribute, document text, tenant identifier, or parser exception enters public errors

### Performance
- Accepted clipboard traversal is iterative and linear in the bounded source tree; defaults are 1 MiB, 10,000 nodes, and 64 levels with documented hard ceilings
- The feature adds no runtime dependency and performs no network, storage, clipboard-permission, model, provider, credential, or database operation

### Tests
- Added realistic Word-like and Google-Docs-like fixtures, Office conditional comments, style-to-semantic conversion, tables and lists, malformed HTML, active/embedded/resource content, hidden data, remote images, unsafe links, UTF-8 byte limits, breadth/depth limits, hostile configuration, DOM-unavailable execution, callback failure, and error-redaction cases
- Added standalone and Yjs collaborative integration tests proving identical sanitizer behavior and latest-callback routing without editor or provider recreation
- Kept repository-wide 100% production statement, branch, function, and line coverage as the merge gate

### Documentation
- Documented preserved and removed clipboard content, Base64Image handoff, SSR behavior, error codes, modular ownership, performance bounds, rollback, and buyer integration
- Added Mermaid architecture and trust-boundary diagrams to `ARCHITECTURE.md`

## [0.5.29] — 2026-08-05

### Added
Expand Down
124 changes: 124 additions & 0 deletions docs/clipboard-security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Safe rich clipboard ingestion

Inkspan sanitizes `text/html` clipboard content before TipTap/ProseMirror parses
or inserts it. The same policy is active in `CwlEditor` and
`CollaborativeCwlEditor`, so a host does not have to maintain separate paste
rules for local and Yjs-backed documents.

## Default behavior

The default limits are:

| Limit | Default |
| --- | ---: |
| UTF-8 HTML bytes | 1,048,576 |
| Source nodes | 10,000 |
| Source depth | 64 |

```tsx
<CwlEditor
clipboard={{
maxHtmlBytes: 1_048_576,
maxNodes: 10_000,
maxDepth: 64,
}}
onClipboardError={(error) => {
// Safe for bounded telemetry or an accessible host notification.
console.warn(error.code);
}}
/>
```

Configuration is read when the editor instance is created. Error observers are
live: replacing `onClipboardError` does not recreate the editor or Yjs binding.
Invalid configuration fails closed when rich HTML is pasted.

## Preserved structure

Inkspan reconstructs a new fragment containing only:

- paragraphs, divisions, headings, blockquotes, preformatted text, code, line
breaks, and horizontal rules;
- bold/strong, italic/emphasis, underline, strike, superscript, and subscript;
- ordered and unordered lists;
- tables, table sections, rows, header cells, and data cells;
- hyperlinks that pass Inkspan's existing SafeLink URI policy.

Word and Google Docs often represent visible semantics only through inline
styles. Inkspan converts four narrowly defined styles before discarding all
style attributes:

- bold font weight → `<strong>`;
- italic or oblique font style → `<em>`;
- underline → `<u>`;
- line-through → `<s>`.

No colors, fonts, sizes, backgrounds, positioning, hidden content, direction
overrides, or layout styles survive.

## Removed content

The sanitizer discards complete active, embedded, form, metadata,
resource-fetching, media, SVG, MathML, canvas, template, and hidden subtrees.
It also removes comments, Office conditional metadata, event handlers, IDs,
classes, arbitrary ARIA, proprietary Office/Google attributes, `data-*`,
`contenteditable`, and unapproved link attributes.

Rich HTML `<img>` elements are always removed. This prevents remote tracking
pixels, local-file references, and unvalidated data URIs. A binary image copied
through the clipboard remains supported by the existing Base64Image pipeline,
which applies file type, byte, decode, dimension, and inline-source policies.

Unsafe links are unwrapped while their visible text is retained. Safe links keep
only the exact `href` and receive
`rel="noopener noreferrer nofollow"`. Inkspan never trims or repairs an
untrusted link into a different browser interpretation.

## Errors

`ClipboardSanitizationError.code` is one of:

```text
dom_unavailable
input_too_large
node_limit_exceeded
depth_limit_exceeded
invalid_configuration
invalid_html
```

Messages are static and do not contain the source HTML, URLs, clipboard text,
attribute values, private parser exceptions, tenant identifiers, or document
content. On rejection, the rich fragment becomes empty; Inkspan never falls back
to unsanitized HTML.

## Direct sanitizer API

Hosts can apply the exact same policy outside the React component:

```ts
import { sanitizeRichClipboardHtml } from '@contextualwisdomlab/cwl-editor';

const safeHtml = sanitizeRichClipboardHtml(untrustedHtml);
```

The function needs a DOM-capable document at call time but does not touch DOM
globals at module import time. SSR can import Inkspan safely; invoke this API in
a browser, jsdom-like controlled environment, or pass an explicit `Document` as
the third argument.

## Ownership boundary

Inkspan owns clipboard HTML validation, semantic reconstruction, shared editor
integration, bounded errors, and deterministic tests. The host still owns:

- clipboard permissions or custom clipboard APIs;
- user notification and recovery UX;
- authentication, authorization, and tenant isolation;
- persistence, retention, audit storage, and data residency;
- downstream HTML rendering and Content Security Policy;
- model or AI use of pasted content;
- legal, privacy, and information-governance policy.

The feature introduces no network request, storage adapter, credential,
database object, model call, or provider dependency.
Loading
Loading