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 +36 -0
- package/NOTICE +16 -0
- package/README.md +36 -24
- package/SECURITY.md +32 -0
- package/dist/dom-utils.d.ts +9 -20
- package/dist/index.js +4 -4
- package/dist/module.js +2 -94
- package/dist/version.d.ts +1 -1
- package/package.json +7 -2
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
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
```
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
**
|
|
169
|
-
|
|
170
|
-
|
|
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.
|
package/dist/dom-utils.d.ts
CHANGED
|
@@ -84,26 +84,15 @@ export declare function caretGeometryAt(marker: Element, root: Element): {
|
|
|
84
84
|
height: number;
|
|
85
85
|
} | null;
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
87
|
+
* Sanitization lives in `tosijs-kilpi` — the same code, extracted so it is not
|
|
88
|
+
* maintained in two places.
|
|
88
89
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
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
|
-
*
|
|
96
|
-
*
|
|
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
|
|
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	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';
|