The load-bearing defense is render-time URL policy (urlPolicy, rule
SEC1), not the HTML sanitizer. A host that reads this file and stops
sanitizing its own input has removed a control Pen does not replace.
Pen has never been published. There are no git tags, no CHANGELOG.md
files, and nothing on the public npm registry. Reports are accepted
against the development line on the default branch.
- Pen does not encrypt content, authenticate peers, or decide who may write. The host and the transport own access. Pen's job is to render arbitrary written state without turning stored strings into live script.
- Custom block renderers are host code. A renderer that injects HTML is outside this boundary.
- The
pen.readOnlyfacet setsaria-readonlyonly. It does not decline typing, does not stopeditor.apply, and does not stop the wire. Thereadonlyprop onEditorRoot,PenEditor, ormountEditoris what declines local typing and pointer activation. Neither the facet nor the prop is a write gate. That split is an open owner decision, not a finished design.
External content reaches the live DOM through many ingresses. Only
two of them call sanitizeHTML (DOMPurify via isomorphic-dompurify,
owned by @input/pen-interop/html):
- paste
text/html(viahtmlImporter.parse/htmlImporter.import) - the HTML import API (
htmlImporter.import/parseHtmlToBlocks)
These ingresses do not pass the sanitizer. That is by design:
- Pen-blocks JSON (
application/x-pen-blocks+json, the legacyapplication/x-pen-blocksflavor, and the HTMLdata-pen-blocksembed) - plain-text paste
- Markdown paste and the Markdown import API
- the JSON import API
- the XML import API
- file drag-and-drop and image transfer
- AI, stream, and tools writes
- remote collaborator Yjs updates
- asset upload and resolve
- the host's own initial document
Schema validation and ingest bounds are not HTML sanitization. A
javascript: href written through any of those paths never meets
DOMPurify. Render-time urlPolicy is what keeps it off the live DOM.
Do not treat a successful HTML import as evidence that Markdown paste, JSON import, or a collaborator update was sanitized. They were not.
urlPolicy lives in @input/pen-core. Default admission is http:,
https:, mailto:, tel:, and relative URLs. data:image/ of
png, jpeg, gif, webp, or avif is allowed in image context
only. javascript:, vbscript:, file:, data:text/html, and
non-strings resolve to null. A blocked URL renders without the
URL-bearing attribute and with data-pen-blocked-url="". The raw
URL is not echoed.
Hosts that need extra schemes wrap the default through
urlPolicyExtension (pen.urlPolicy). Not every sink reads the
host facet: the HTML and XML exporters call the default policy
directly. A wrap that denies a URL can still emit it from those
two exporters.
- Library rendering builds DOM through
createElement/textContent/ attribute setters. Parsing untrusted HTML usesDOMParserin@input/pen-interop/html; that tree enters the document as data. @input/pen-toolsvalidates tool payloads before building ops. Invalid payloads emit a diagnostic and are not applied. Tool authority is a live boundary, not a claim that every write path is closed.@input/pen-searchdefaultsregex: false, caps query length at 1,024 characters, and budgets regex execution.
Do not open a public GitHub issue for a suspected vulnerability.
Prefer GitHub private vulnerability reporting on this repository.
The fallback is email: support@input.so with Pen security in
the subject line. That mailbox is the same contact published in
CLA.md.
Include:
- a description of the issue
- impact and affected packages or commit
- reproduction steps or a proof of concept
- any suggested remediation if you have one
We will acknowledge reports as quickly as we can.
The default coordinated-disclosure window is 90 days from the first maintainer acknowledgement. We may ask to extend that window for a complex fix. We will not publish before a fix is available unless the reporter and maintainers agree, or the issue is already public.
Once packages are on the public registry, we will publish GitHub Security Advisories, and npm advisories for those packages, for vulnerabilities reachable through the SEC1–SEC6 surfaces:
- SEC1: render-time URL policy (hostile URL-bearing document state)
- SEC2: HTML injection sinks in library code
- SEC3: the HTML sanitizer used for import and paste
- SEC4: structured clipboard and JSON ingestion
- SEC5: exporter and serializer escaping
- SEC6: tool and document-op payload validation
Host-owned code, custom renderers, and trusted host configuration are outside Pen's advisory boundary.
Until the first release train is published, only the default-branch development line is accepted.
| Version | Supported |
|---|---|
| Default branch | Yes |
| Published 2.x | none yet |
| Older lines / tags | n/a |
After 2.0 is published, the intended policy is: latest 2.x minor only. That table is not in force today.
This policy applies to the Pen repository, its packages once published, and the repository playground/docs apps when the issue affects shipped package behavior.