tosijs-styled-editor 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.5] - 2026-09-17
11
+
12
+ ### Added
13
+
14
+ - **`SECURITY.md`**, with the one thing it needs to say: a sanitizer bypass
15
+ belongs to [`tosijs-kilpi`](https://github.com/tonioloewald/kilpi/issues),
16
+ because that is where the code lives. The README's security section now points
17
+ at kilpi's policy as authoritative rather than restating it — a copy of a
18
+ policy drifts from the policy, which is the same failure the extraction
19
+ removed from the code.
20
+ - **`NOTICE`**, for the three Apache-2.0 works the drop-in `dist/index.js`
21
+ bundles.
22
+
23
+ ### Changed
24
+
25
+ - **Sanitization moved to [`tosijs-kilpi`](https://github.com/tonioloewald/kilpi)**,
26
+ the same code extracted as a standalone library so it is not maintained in two
27
+ places. No API change: `sanitizeInPlace` and `isSafeNavigationUrl` are still
28
+ exported from this package, `editor.sanitize` still works the same way, and
29
+ behaviour is identical.
30
+
31
+ The reason it matters is not tidiness. When the sanitizer briefly existed
32
+ twice, a URL-normalization fix reached one copy and not the other — recorded
33
+ as M1 in the 0.4.4 review. Across two repositories that drift would not even
34
+ appear in a diff. kilpi carries DOMPurify's published 223-fixture corpus as a
35
+ hard publish gate, which this package could not run on its own.
36
+
37
+ `tosijs-kilpi` is a real dependency (this package's first — tosijs and
38
+ tosijs-ui remain peers), at `^1.0.0`. kilpi went 1.0.0 for that reason alone:
39
+ `^0.1.0` resolves to `>=0.1.0 <0.2.0`, so a 0.2.0 security fix would have
40
+ reached no installed consumer, and for a dependency that *is* the XSS defence
41
+ a range that blocks propagation is a defect in itself. It is external in `dist/module.js`, so a consumer who
42
+ also depends on it directly gets one copy, and bundled into `dist/index.js`,
43
+ which assumes no installs.
44
+
45
+
10
46
  ## [0.4.4] - 2026-09-16
11
47
 
12
48
  First release since 0.4.3 to reach npm. 0.4.4 and 0.4.5 were versioned in the
package/NOTICE ADDED
@@ -0,0 +1,16 @@
1
+ tosijs-styled-editor
2
+ Copyright 2026 Tonio Loewald
3
+
4
+ The drop-in build (dist/index.js) bundles the following Apache-2.0 works:
5
+
6
+ tosijs — Copyright 2026 Tonio Loewald
7
+ tosijs-ui — Copyright 2026 Tonio Loewald
8
+ tosijs-kilpi — Copyright 2026 Tonio Loewald
9
+ https://github.com/tonioloewald/kilpi
10
+
11
+ dist/module.js leaves all three external and bundles none of them.
12
+
13
+ tosijs-kilpi's verification corpus is vendored from DOMPurify
14
+ (Copyright (c) 2015 Mario Heiderich, MPL-2.0 OR Apache-2.0). That corpus is a
15
+ test fixture in kilpi's repository and is not distributed in kilpi's package or
16
+ in this one, so no DOMPurify material is redistributed here.
package/README.md CHANGED
@@ -63,7 +63,13 @@ Live site and docs: <https://editor.tosijs.net>
63
63
  npm install tosijs-styled-editor
64
64
  ```
65
65
 
66
- Peer dependencies: `tosijs`, `tosijs-ui`
66
+ **Peer dependencies** (you install these): `tosijs`, `tosijs-ui`
67
+
68
+ **Runtime dependency** (installed automatically):
69
+ [`tosijs-kilpi`](https://github.com/tonioloewald/kilpi) — the sanitizer applied
70
+ to pasted and dropped content. It has no dependencies of its own and is about
71
+ 0.8 kB gzipped. The drop-in `dist/index.js` build bundles it; the ESM build
72
+ leaves it external so you get one copy if you also depend on it directly.
67
73
 
68
74
  ## Usage
69
75
 
@@ -121,32 +127,39 @@ The caret is an `<input>` element, so mobile browsers show their keyboard automa
121
127
 
122
128
  ## Security: what is sanitized, and what is not
123
129
 
124
- Replacing `contentEditable` also means replacing the sanitization the browser
125
- was doing on your behalf. As of 0.4.4:
130
+ Replacing `contentEditable` also means replacing the sanitization the browser was
131
+ doing on your behalf.
126
132
 
127
133
  **Sanitized** — content arriving from outside the document, which is the path an
128
- attacker controls:
129
-
130
- - **paste** and **drop** (both go through one shared choke point)
131
- - inline event handlers (`onerror`, `onload`, …) are removed
132
- - `script`, `iframe`, `object`, `embed`, `link`, `meta`, `base`, `style`,
133
- `form` and the SVG animation elements are removed — in **any** namespace, so
134
- `<svg><script>` and `<svg><style>` are caught too
135
- - `href`/`src`/`xlink:href` are scheme-checked: `http(s)`, `mailto`, `tel` and
136
- relative URLs are kept, `javascript:` is dropped, and `data:` is allowed only
137
- for raster images (never for a link, never `data:image/svg+xml`)
138
- - ordinary formatting and **unregistered custom elements survive** — plugin
139
- markup is content, not a threat
134
+ attacker controls: **paste** and **drop**, both through one shared choke point.
135
+
136
+ The filtering itself is [`tosijs-kilpi`](https://github.com/tonioloewald/kilpi),
137
+ and **[its SECURITY.md is the authoritative policy](https://github.com/tonioloewald/kilpi/blob/main/SECURITY.md)** —
138
+ read it before relying on this. It is deliberately not restated here, because a
139
+ copy of a policy drifts from the policy. The one thing worth repeating, because
140
+ it is a trade rather than a detail:
141
+
142
+ > kilpi is a **denylist** for elements and attributes and an **allowlist** for URL
143
+ > schemes. That is why unknown elements survive — your plugin markup round-trips
144
+ > intact — and it is also why an element that becomes dangerous in a future
145
+ > browser, and that kilpi has never heard of, would pass through. If protection
146
+ > from the not-yet-known matters more to you than preserving unknown markup, use
147
+ > DOMPurify instead (see the hook below).
140
148
 
141
149
  **NOT sanitized** — content you supply, which is inside your own trust boundary:
142
150
 
143
151
  - `editor.value = html`
144
152
  - initial light-DOM content
145
153
 
154
+ **If you are upgrading from 0.4.3 or earlier:** documents your users created
155
+ before 0.4.4 may already contain a payload that was pasted in, and this component
156
+ cannot fix that for you — setting `value` does not filter. Sanitize your stored
157
+ corpus as part of the upgrade.
158
+
146
159
  **Using a different sanitizer.** `editor.sanitize` is the hook — it receives a
147
160
  detached element and mutates it:
148
161
 
149
- ```js
162
+ ```javascript
150
163
  editor.sanitize = (root) => {
151
164
  DOMPurify.sanitize(root, {
152
165
  IN_PLACE: true,
@@ -161,14 +174,13 @@ editor.sanitize = (root) => {
161
174
 
162
175
  It takes an element rather than an HTML string on purpose: a string signature
163
176
  would force a serialize-and-reparse round trip, and that round trip is where
164
- mutation XSS lives. Note the `CUSTOM_ELEMENT_HANDLING` block — DOMPurify
165
- unwraps unknown custom elements by default, which would discard plugin markup
166
- the built-in sanitizer preserves.
167
-
168
- **If you are upgrading from 0.4.3 or earlier, read this:** documents your users
169
- created before 0.4.4 may already contain a payload that was pasted in, and the
170
- component cannot fix that for you — setting `value` does not filter. Sanitize
171
- your stored corpus as part of the upgrade.
177
+ mutation XSS lives. Note the `CUSTOM_ELEMENT_HANDLING` block — DOMPurify unwraps
178
+ unknown custom elements by default, which would discard plugin markup kilpi
179
+ preserves.
180
+
181
+ **Reporting a vulnerability.** If it is in the sanitizer, file it against
182
+ [kilpi](https://github.com/tonioloewald/kilpi/issues) — that is where the code
183
+ lives. Anything else, [this repository](https://github.com/tonioloewald/tosijs-editor/issues).
172
184
 
173
185
  ## Keyboard Behavior
174
186
 
package/SECURITY.md ADDED
@@ -0,0 +1,32 @@
1
+ # Security
2
+
3
+ ## Reporting
4
+
5
+ - **A sanitizer bypass** — pasted or dropped content that reaches the document
6
+ still able to execute — belongs to
7
+ [`tosijs-kilpi`](https://github.com/tonioloewald/kilpi/issues). That is where
8
+ the filtering code lives; this package only calls it.
9
+ - **Anything else** — [this repository's
10
+ issues](https://github.com/tonioloewald/tosijs-editor/issues).
11
+
12
+ If you would rather not disclose publicly first, open an issue with no details
13
+ and we will find a private channel.
14
+
15
+ ## What this package is responsible for
16
+
17
+ - applying sanitization at the **paste and drop choke point**, before any node
18
+ enters the document (`insertTransfer`)
19
+ - the `editor.sanitize` hook, so a host can substitute its own sanitizer
20
+ - the two URL guards this package owns: `setLink`, and following a link on
21
+ Ctrl/Cmd-click
22
+
23
+ ## What it is NOT responsible for
24
+
25
+ - **the sanitization policy itself** — that is kilpi's, and
26
+ [kilpi's SECURITY.md](https://github.com/tonioloewald/kilpi/blob/main/SECURITY.md)
27
+ is authoritative. It is deliberately not restated here, because a copy of a
28
+ policy drifts from the policy.
29
+ - **content the host supplies**: `editor.value = html` and initial light-DOM
30
+ content are inside your trust boundary and are not filtered.
31
+ - **documents stored before 0.4.4**, which may already contain a pasted payload.
32
+ Setting `value` does not filter, so sanitize your corpus as part of upgrading.
@@ -84,26 +84,15 @@ export declare function caretGeometryAt(marker: Element, root: Element): {
84
84
  height: number;
85
85
  } | null;
86
86
  /**
87
- * Strip executable content from a subtree, IN PLACE.
87
+ * Sanitization lives in `tosijs-kilpi` — the same code, extracted so it is not
88
+ * maintained in two places.
88
89
  *
89
- * The editor replaced `contentEditable` but not the sanitization the browser
90
- * was doing on its behalf: pasted and dropped HTML is written into the live
91
- * document, and from there into `value`, `internals.setFormValue` and every
92
- * undo snapshot — so an unsanitized payload is stored, re-served, and re-fired
93
- * on undo. Must run BEFORE any node enters the document.
90
+ * It was duplicated briefly, and that is exactly the shape that produced review
91
+ * finding M1 of the 0.4.4 cycle: one URL-normalization defect fixed in one of
92
+ * two copies, silently leaving the other. Across two repositories that drift
93
+ * would not even be visible in a diff.
94
94
  *
95
- * This is deliberately a denylist for elements and an allowlist for URL
96
- * schemes: unknown ELEMENTS are content (including a plugin's custom elements,
97
- * which must survive — see EXTENSIBILITY.md), whereas unknown SCHEMES are not.
95
+ * Re-exported here so the editor's public API is unchanged and every call site
96
+ * keeps importing from `./dom-utils`.
98
97
  */
99
- export declare function sanitizeInPlace(root: Element | DocumentFragment): void;
100
- /**
101
- * Is this URL safe to NAVIGATE to, or to write into an href?
102
- *
103
- * Stricter than `isSafeUrl`: that one allows raster `data:image/*` because an
104
- * `<img src>` may legitimately carry one, while a link must never — so this
105
- * rejects every `data:` URL. Both must normalize identically, or the stricter
106
- * check is the one that gets bypassed: `da&#9;ta:image/png;…` passed here while
107
- * `data:image/png;…` was correctly rejected.
108
- */
109
- export declare function isSafeNavigationUrl(value: string): boolean;
98
+ export { sanitizeInPlace, isSafeNavigationUrl } from 'tosijs-kilpi';