tosijs-styled-editor 0.5.0 → 0.5.2
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 +67 -0
- package/SECURITY.md +29 -0
- package/dist/changes.d.ts +20 -0
- package/dist/index.js +5 -5
- package/dist/module.js +54 -49
- package/dist/tosijs-styled-editor.d.ts +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.2] - 2026-09-26
|
|
11
|
+
|
|
12
|
+
### Security
|
|
13
|
+
|
|
14
|
+
- **0.5.1's fix was incomplete: `reviseWith()` could still emit live HTML.**
|
|
15
|
+
The guard that strips `<` and `>` from `changeAuthor` was applied at the four
|
|
16
|
+
write sites in the component and missed the fifth, `mark()` in
|
|
17
|
+
`src/changes.ts` — which is exactly what `reviseWith()` and the exported
|
|
18
|
+
`applyRevision()` go through. So the LLM-proofreading path, documented public
|
|
19
|
+
API, still broke a display name out of a raw-text element and put an
|
|
20
|
+
`<img onerror>` into `editor.value`. Reproduced end to end through the public
|
|
21
|
+
`value` setter and `reviseWith()`, with no host cooperation beyond wiring
|
|
22
|
+
`changeAuthor.name` to a profile name.
|
|
23
|
+
|
|
24
|
+
The guard now lives in `src/changes.ts` beside the only code that writes those
|
|
25
|
+
attributes, and every writer shares it. **A guard belongs at the layer every
|
|
26
|
+
writer shares, not at the addresses where the bug was first noticed** — which
|
|
27
|
+
is precisely the mistake 0.5.1 made.
|
|
28
|
+
|
|
29
|
+
**Upgrade from 0.5.1 as well as from 0.5.0** if you use `trackChanges` or
|
|
30
|
+
`reviseWith`. `SECURITY.md`'s statement in 0.5.1 was true of typing and
|
|
31
|
+
deletion and false of revision.
|
|
32
|
+
|
|
33
|
+
## [0.5.1] - 2026-09-26
|
|
34
|
+
|
|
35
|
+
### Security
|
|
36
|
+
|
|
37
|
+
- **An author's display name could break out of a raw-text element and become
|
|
38
|
+
live HTML.** HTML attribute serialization escapes `&` and `"` but **never
|
|
39
|
+
`<`**, and `style`, `xmp`, `title`, `textarea`, `noembed`, `noframes` and
|
|
40
|
+
`plaintext` re-parse their contents as raw text — so a `changeAuthor.name`
|
|
41
|
+
containing `</style><img src=x onerror=…>`, written into a change mark inside
|
|
42
|
+
one of those elements, terminated the element and the remainder parsed as
|
|
43
|
+
markup. `editor.value` then carried a real `<img onerror>`.
|
|
44
|
+
|
|
45
|
+
Nothing external triggers the re-parse: `value` is also the undo stack, so a
|
|
46
|
+
single undo does it in the same session, and `internals.setFormValue` hands it
|
|
47
|
+
to every other reader. Reachable without host cooperation — `tosijs-kilpi`
|
|
48
|
+
drops `style`/`script`/`iframe` but keeps `xmp`/`textarea`/`title`/`noembed`/
|
|
49
|
+
`noframes`/`plaintext`, so a collaborator can paste one — and the host need
|
|
50
|
+
only have wired `changeAuthor.name` to a profile name, which is what that
|
|
51
|
+
field is for.
|
|
52
|
+
|
|
53
|
+
`<` and `>` are now stripped from `changeAuthor.id` and `.name` at every write
|
|
54
|
+
site. Stripping rather than escaping: no escape survives attribute
|
|
55
|
+
serialization into a raw-text element. Affects 0.5.0 only, and only with
|
|
56
|
+
`trackChanges` enabled.
|
|
57
|
+
|
|
58
|
+
**Still true, and not a vulnerability:** tracked edits inside a raw-text
|
|
59
|
+
element are a bad idea regardless — a caret marker left in a `<style>` block
|
|
60
|
+
round-trips into literal CSS text with no attacker involved. Keeping the
|
|
61
|
+
editor's own marks out of those elements is tracked in `TODO.md`. See
|
|
62
|
+
`SECURITY.md`.
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
|
|
66
|
+
- The build no longer passes `--incremental` to `tsc`, so no
|
|
67
|
+
`dist/tsconfig.tsbuildinfo` is produced. An incremental build into a wiped
|
|
68
|
+
`dist/` can emit nothing at all the second time, which disqualifies a build
|
|
69
|
+
that has to reproduce for the publish workflow.
|
|
70
|
+
|
|
71
|
+
### Added
|
|
72
|
+
|
|
73
|
+
- `.github/workflows/publish.yml` — OIDC trusted publishing with npm staged
|
|
74
|
+
publishing. CI can only *stage*; the maintainer's 2FA approval on npmjs.com is
|
|
75
|
+
what publishes, and it works from a phone.
|
|
76
|
+
|
|
10
77
|
## [0.5.0] - 2026-09-21
|
|
11
78
|
|
|
12
79
|
### Added
|
package/SECURITY.md
CHANGED
|
@@ -30,3 +30,32 @@ and we will find a private channel.
|
|
|
30
30
|
content are inside your trust boundary and are not filtered.
|
|
31
31
|
- **documents stored before 0.4.4**, which may already contain a pasted payload.
|
|
32
32
|
Setting `value` does not filter, so sanitize your corpus as part of upgrading.
|
|
33
|
+
|
|
34
|
+
## `changeAuthor` is written into the document
|
|
35
|
+
|
|
36
|
+
`editor.changeAuthor` (`id` and `name`) is serialized verbatim into
|
|
37
|
+
`data-author` / `data-author-name` on every change mark, so it reaches
|
|
38
|
+
`editor.value`, the form value and every undo snapshot. It is host-supplied and
|
|
39
|
+
inside your trust boundary — but "the host wired it to a profile name" is the
|
|
40
|
+
ordinary case, so the editor does not trust it: `<` and `>` are stripped at the
|
|
41
|
+
seam (fully fixed in 0.5.2 — 0.5.0 is affected, and **0.5.1 is still affected
|
|
42
|
+
through `reviseWith()`**, whose write site the first fix missed).
|
|
43
|
+
|
|
44
|
+
They have to be stripped rather than escaped. HTML attribute serialization
|
|
45
|
+
escapes `&` and `"` and **never `<`**, and `style`, `xmp`, `title`, `textarea`,
|
|
46
|
+
`noembed`, `noframes` and `plaintext` re-parse their contents as raw text — so a
|
|
47
|
+
`</style>` inside an attribute value written into one of those terminates the
|
|
48
|
+
element and everything after it parses as markup. Nothing external triggers the
|
|
49
|
+
re-parse: `value` is also the undo stack, so a single undo does it in the same
|
|
50
|
+
session.
|
|
51
|
+
|
|
52
|
+
Two consequences worth knowing:
|
|
53
|
+
|
|
54
|
+
- **A display name is not a place to put markup**, and it comes back with its
|
|
55
|
+
angle brackets removed rather than escaped. There is no escape that survives
|
|
56
|
+
attribute serialization into a raw-text element.
|
|
57
|
+
- **Tracked edits inside a raw-text element remain a bad idea.** With no
|
|
58
|
+
attacker at all, a caret marker left inside a `<style>` block round-trips into
|
|
59
|
+
literal CSS text. Keeping the editor's own marks out of those elements is
|
|
60
|
+
tracked as an open item; until it lands, do not point this editor at documents
|
|
61
|
+
whose `<style>`/`<xmp>` content is meant to survive editing.
|
package/dist/changes.d.ts
CHANGED
|
@@ -63,6 +63,26 @@ export declare class TosiIns extends HTMLElement {
|
|
|
63
63
|
export declare class TosiDel extends HTMLElement {
|
|
64
64
|
}
|
|
65
65
|
export declare function defineChanges(): void;
|
|
66
|
+
/**
|
|
67
|
+
* An attribute value that cannot break out of the element it is written into.
|
|
68
|
+
*
|
|
69
|
+
* HTML attribute serialization escapes `&` and `"` and **never `<`**. Inside a
|
|
70
|
+
* raw-text or RCDATA element — `style`, `xmp`, `title`, `textarea`, `noembed`,
|
|
71
|
+
* `noframes`, `plaintext` — the contents re-parse as text, so a `</style>` in
|
|
72
|
+
* an attribute value terminates the element and everything after it parses as
|
|
73
|
+
* markup, and `editor.value` then carries live HTML.
|
|
74
|
+
*
|
|
75
|
+
* This lives HERE, next to the only code that writes these attributes, because
|
|
76
|
+
* the first version of the fix lived in the component and covered four of the
|
|
77
|
+
* five write sites. The fifth was `mark()` below — on the public `reviseWith()`
|
|
78
|
+
* path — so 0.5.1 shipped as the fix for this and was still exploitable
|
|
79
|
+
* through it. A guard belongs at the layer every writer shares, not at the
|
|
80
|
+
* addresses where the bug was first noticed.
|
|
81
|
+
*
|
|
82
|
+
* Stripping, not escaping: no escape survives attribute serialization into a
|
|
83
|
+
* raw-text element.
|
|
84
|
+
*/
|
|
85
|
+
export declare function safeAttributeValue(value: string): string;
|
|
66
86
|
/** Every tracked change in a root, in document order. */
|
|
67
87
|
export declare function changesIn(root: Element): TrackedChange[];
|
|
68
88
|
/**
|