@particle-academy/fancy-code 0.12.0 → 0.12.1
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 +18 -0
- package/dist/index.cjs +20 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +20 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
14
14
|
|
|
15
15
|
## [Unreleased]
|
|
16
16
|
|
|
17
|
+
## [0.12.1] - 2026-10-18
|
|
18
|
+
|
|
19
|
+
### Security
|
|
20
|
+
|
|
21
|
+
- **`<MarkdownEditor>`'s preview no longer renders a link or image whose URL
|
|
22
|
+
can run script.** The preview is built as an HTML string and inserted with
|
|
23
|
+
`innerHTML`, where React's `javascript:` guard does not apply. URLs were kept
|
|
24
|
+
inside their attribute but their scheme was never checked, so
|
|
25
|
+
`[x](javascript:...)` became a live script link in the preview, in the page's
|
|
26
|
+
own origin. A link now needs a relative URL or `http`, `https`, `mailto` or
|
|
27
|
+
`tel`; an image needs a relative URL, `http(s)`, or a raster
|
|
28
|
+
`data:image/...`. Anything else renders as its text (a link) or its alt text
|
|
29
|
+
(an image). The scheme is read the way a browser reads it: case-insensitive,
|
|
30
|
+
with leading control characters and embedded tabs and newlines ignored.
|
|
31
|
+
**Upgrade if any markdown you preview was written by someone else**, an agent
|
|
32
|
+
included. Nothing else changes. Found while auditing the viewers fancy-labs
|
|
33
|
+
uses to show agent-written documents.
|
|
34
|
+
|
|
17
35
|
## [0.12.0] - 2026-10-17
|
|
18
36
|
|
|
19
37
|
### Added
|
package/dist/index.cjs
CHANGED
|
@@ -1809,8 +1809,27 @@ function escapeHtml(s) {
|
|
|
1809
1809
|
function escapeAttr(s) {
|
|
1810
1810
|
return s.replace(/"/g, """).replace(/'/g, "'");
|
|
1811
1811
|
}
|
|
1812
|
+
var LINK_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel"]);
|
|
1813
|
+
function schemeOf(url) {
|
|
1814
|
+
let s = "";
|
|
1815
|
+
for (const ch of url) {
|
|
1816
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
1817
|
+
if (code === 9 || code === 10 || code === 13) continue;
|
|
1818
|
+
if (s === "" && code <= 32) continue;
|
|
1819
|
+
s += ch;
|
|
1820
|
+
}
|
|
1821
|
+
const m = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(s);
|
|
1822
|
+
return m ? m[1].toLowerCase() : null;
|
|
1823
|
+
}
|
|
1824
|
+
function safeUrl(url, kind) {
|
|
1825
|
+
const scheme = schemeOf(url);
|
|
1826
|
+
if (scheme === null) return true;
|
|
1827
|
+
if (scheme === "http" || scheme === "https") return true;
|
|
1828
|
+
if (kind === "link") return LINK_SCHEMES.has(scheme);
|
|
1829
|
+
return scheme === "data" && /^data:image\/(png|jpe?g|gif|webp|avif|bmp);/i.test(url.trim());
|
|
1830
|
+
}
|
|
1812
1831
|
function inline(text) {
|
|
1813
|
-
return text.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_m, alt, src, title) => `<img src="${escapeAttr(src)}" alt="${escapeAttr(alt)}"${title ? ` title="${escapeAttr(title)}"` : ""}>`).replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_m, t, href, title) => `<a href="${escapeAttr(href)}"${title ? ` title="${escapeAttr(title)}"` : ""} rel="noopener">${t}</a>`).replace(/`([^`]+)`/g, (_m, c) => `<code>${c}</code>`).replace(/(\*\*|__)(?=\S)([\s\S]*?\S)\1/g, "<strong>$2</strong>").replace(/(\*|_)(?=\S)([\s\S]*?\S)\1/g, "<em>$2</em>");
|
|
1832
|
+
return text.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_m, alt, src, title) => safeUrl(src, "image") ? `<img src="${escapeAttr(src)}" alt="${escapeAttr(alt)}"${title ? ` title="${escapeAttr(title)}"` : ""}>` : alt).replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (_m, t, href, title) => safeUrl(href, "link") ? `<a href="${escapeAttr(href)}"${title ? ` title="${escapeAttr(title)}"` : ""} rel="noopener">${t}</a>` : t).replace(/`([^`]+)`/g, (_m, c) => `<code>${c}</code>`).replace(/(\*\*|__)(?=\S)([\s\S]*?\S)\1/g, "<strong>$2</strong>").replace(/(\*|_)(?=\S)([\s\S]*?\S)\1/g, "<em>$2</em>");
|
|
1814
1833
|
}
|
|
1815
1834
|
function renderMarkdown(markdown) {
|
|
1816
1835
|
const lines = markdown.replace(/\r\n?/g, "\n").split("\n");
|