Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 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
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
17 changes: 13 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
build-and-test:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
# pnpm version comes from the package.json "packageManager" field
# (pnpm 10+), which is required to read `overrides` from
# pnpm-workspace.yaml consistently with the committed lockfile.
Expand All @@ -40,7 +46,7 @@ jobs:

office:
name: Office / Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
Expand All @@ -51,7 +57,10 @@ jobs:
env:
PYTHONPATH: src
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ matrix.python-version }}
Expand Down
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.
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,53 @@ 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()`
- Nested clipboard policy objects are preserved without accessor evaluation during editor construction and use accessor-safe paste-time configuration validation at the exact rich-paste boundary
- 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
- Closed `details` elements preserve only their first rendered summary, closed `dialog` subtrees are removed, and open variants are unwrapped through the ordinary sanitizer so source-only interactive content cannot become visible editor text
- Native `progress` and `meter` widget subtrees and obsolete `noframes` and `noembed` fallback subtrees are removed so stripping their wrappers cannot promote source fallback text into ordinary visible editor prose
- Hidden `datalist` suggestion and down-level fallback subtrees are removed so stripping the suggestion-source wrapper cannot promote non-rendered descendants into ordinary visible editor prose
- Raw `mso-hide` declarations are parsed with exact case-insensitive property/value matching, closed and EOF-terminated CSS-comment removal, optional terminal `!important`, and false-positive guards instead of relying on browser CSSOM support for the proprietary Office property
- CSS-escaped property and keyword forms of `mso-hide: all` are decoded for exact comparison, while invalid code points, escaped newlines, prefixes, and longer look-alike values remain visible instead of producing false-positive subtree removal
- SafeClipboard uses the lowest-practical extension priority as the final ordinary TipTap paste transform, with an integration regression proving a prior host transform cannot reintroduce scripts or tracking images before parsing
- A host-installed lower-priority transform or post-parse mutation is explicitly outside the supported safety contract and requires an independently verified equivalent validation boundary
- 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 raw Office hidden-style variants including EOF-terminated CSS comments, false-positive cases, structural removed-element assertions, real TipTap transform-chain ordering, and standalone/Yjs regressions proving configuration accessors are not evaluated before paste
- Added a test-first closed/open `details` and `dialog` regression that proves hidden additional or dialog content does not enter the sanitized fragment while rendered content remains
- Added a test-first native-widget and obsolete-fallback regression proving ordinary visible text remains while `progress`, `meter`, `noframes`, and `noembed` descendants cannot surface after wrapper removal
- Added a test-first `datalist` regression proving ordinary neighboring content remains while hidden suggestion and down-level fallback descendants cannot surface after wrapper removal
- 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
- Recorded that current jsdom results are not cross-engine browser evidence; version-pinned Chromium, Firefox, and WebKit differential fixtures are a publication gate for 0.6.0

### Documentation
- Documented preserved and removed clipboard content, Base64Image handoff, SSR behavior, error codes, modular ownership, performance bounds, rollback, and buyer integration
- Documented closed interactive content against the WHATWG HTML Living Standard, including the source-rendering, accessibility, host-ownership, rollback, and cross-engine uncertainty boundaries
- Documented native progress/gauge widgets and obsolete fallback elements against the WHATWG HTML Living Standard, including the fail-closed conversion decision, test-first evidence, residual risk, and rollback boundary
- Documented hidden `datalist` suggestion and down-level fallback content against the WHATWG HTML Living Standard, including the linked-control, accessibility, host-ownership, rollback, and cross-engine uncertainty boundaries
- Documented OWASP's DOMPurify recommendation, the bespoke sanitizer's vulnerability-response obligation, final-transform composition limits, and the current browser-assurance boundary
- Added Mermaid architecture and trust-boundary diagrams to `ARCHITECTURE.md`

## [0.5.29] — 2026-08-05

### Added
Expand Down
Loading
Loading