@immediately-run/sdk 0.29.0 → 0.31.0

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.
Files changed (83) hide show
  1. package/dist/boot.cjs +5 -1
  2. package/dist/boot.cjs.map +1 -1
  3. package/dist/boot.js +5 -1
  4. package/dist/boot.js.map +1 -1
  5. package/dist/components/Link.cjs +25 -0
  6. package/dist/components/Link.cjs.map +1 -1
  7. package/dist/components/Link.d.cts +7 -1
  8. package/dist/components/Link.d.ts +7 -1
  9. package/dist/components/Link.js +24 -0
  10. package/dist/components/Link.js.map +1 -1
  11. package/dist/components/ScrollAfterNavigation.cjs +66 -0
  12. package/dist/components/ScrollAfterNavigation.cjs.map +1 -0
  13. package/dist/components/ScrollAfterNavigation.d.cts +24 -0
  14. package/dist/components/ScrollAfterNavigation.d.ts +24 -0
  15. package/dist/components/ScrollAfterNavigation.js +41 -0
  16. package/dist/components/ScrollAfterNavigation.js.map +1 -0
  17. package/dist/components/WikiLink.cjs +10 -2
  18. package/dist/components/WikiLink.cjs.map +1 -1
  19. package/dist/components/WikiLink.d.cts +11 -3
  20. package/dist/components/WikiLink.d.ts +11 -3
  21. package/dist/components/WikiLink.js +10 -2
  22. package/dist/components/WikiLink.js.map +1 -1
  23. package/dist/index.cjs +3 -1
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +5 -0
  26. package/dist/index.d.ts +5 -0
  27. package/dist/index.js +1 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/safeContent/SafeContent.cjs +54 -0
  30. package/dist/safeContent/SafeContent.cjs.map +1 -0
  31. package/dist/safeContent/SafeContent.d.cts +13 -0
  32. package/dist/safeContent/SafeContent.d.ts +13 -0
  33. package/dist/safeContent/SafeContent.js +30 -0
  34. package/dist/safeContent/SafeContent.js.map +1 -0
  35. package/dist/safeContent/index.cjs +43 -0
  36. package/dist/safeContent/index.cjs.map +1 -0
  37. package/dist/safeContent/index.d.cts +6 -0
  38. package/dist/safeContent/index.d.ts +6 -0
  39. package/dist/safeContent/index.js +14 -0
  40. package/dist/safeContent/index.js.map +1 -0
  41. package/dist/safeContent/parseSafeMdast.cjs +69 -0
  42. package/dist/safeContent/parseSafeMdast.cjs.map +1 -0
  43. package/dist/safeContent/parseSafeMdast.d.cts +31 -0
  44. package/dist/safeContent/parseSafeMdast.d.ts +31 -0
  45. package/dist/safeContent/parseSafeMdast.js +35 -0
  46. package/dist/safeContent/parseSafeMdast.js.map +1 -0
  47. package/dist/safeContent/renderMdast.cjs +138 -0
  48. package/dist/safeContent/renderMdast.cjs.map +1 -0
  49. package/dist/safeContent/renderMdast.d.cts +27 -0
  50. package/dist/safeContent/renderMdast.d.ts +27 -0
  51. package/dist/safeContent/renderMdast.js +114 -0
  52. package/dist/safeContent/renderMdast.js.map +1 -0
  53. package/dist/safeContent/sanitizeUrl.cjs +39 -0
  54. package/dist/safeContent/sanitizeUrl.cjs.map +1 -0
  55. package/dist/safeContent/sanitizeUrl.d.cts +9 -0
  56. package/dist/safeContent/sanitizeUrl.d.ts +9 -0
  57. package/dist/safeContent/sanitizeUrl.js +15 -0
  58. package/dist/safeContent/sanitizeUrl.js.map +1 -0
  59. package/dist/safeContent/wikilink.cjs +63 -0
  60. package/dist/safeContent/wikilink.cjs.map +1 -0
  61. package/dist/safeContent/wikilink.d.cts +21 -0
  62. package/dist/safeContent/wikilink.d.ts +21 -0
  63. package/dist/safeContent/wikilink.js +38 -0
  64. package/dist/safeContent/wikilink.js.map +1 -0
  65. package/dist/scrollToId.cjs +46 -0
  66. package/dist/scrollToId.cjs.map +1 -0
  67. package/dist/scrollToId.d.cts +12 -0
  68. package/dist/scrollToId.d.ts +12 -0
  69. package/dist/scrollToId.js +22 -0
  70. package/dist/scrollToId.js.map +1 -0
  71. package/dist/urlUtils.cjs +9 -1
  72. package/dist/urlUtils.cjs.map +1 -1
  73. package/dist/urlUtils.d.cts +10 -1
  74. package/dist/urlUtils.d.ts +10 -1
  75. package/dist/urlUtils.js +8 -1
  76. package/dist/urlUtils.js.map +1 -1
  77. package/dist/version.cjs +1 -1
  78. package/dist/version.cjs.map +1 -1
  79. package/dist/version.d.cts +1 -1
  80. package/dist/version.d.ts +1 -1
  81. package/dist/version.js +1 -1
  82. package/dist/version.js.map +1 -1
  83. package/package.json +9 -3
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var renderMdast_exports = {};
20
+ __export(renderMdast_exports, {
21
+ renderMdast: () => renderMdast
22
+ });
23
+ module.exports = __toCommonJS(renderMdast_exports);
24
+ var import_react = require("react");
25
+ var import_sanitizeUrl = require("./sanitizeUrl");
26
+ var import_wikilink = require("./wikilink");
27
+ function literalProps(attributes) {
28
+ const props = {};
29
+ for (const attr of attributes ?? []) {
30
+ if (attr.type !== "mdxJsxAttribute") continue;
31
+ if (typeof attr.name !== "string") continue;
32
+ if (typeof attr.value === "string") props[attr.name] = attr.value;
33
+ else if (attr.value === null || attr.value === void 0) props[attr.name] = "";
34
+ }
35
+ return props;
36
+ }
37
+ let keyCounter = 0;
38
+ const nextKey = () => `sc-${keyCounter++}`;
39
+ function renderWiki(token, resolve) {
40
+ const label = token.label ?? token.target;
41
+ const href = resolve?.(token.target);
42
+ const safe = href !== void 0 ? (0, import_sanitizeUrl.sanitizeUrl)(href) : void 0;
43
+ if (safe === void 0) return label;
44
+ return (0, import_react.createElement)("a", { href: safe, "data-wikilink": token.target, key: nextKey() }, label);
45
+ }
46
+ function renderText(value, opts) {
47
+ const parts = (0, import_wikilink.splitWikiLinks)(value);
48
+ if (!parts) return value;
49
+ return parts.map(
50
+ (p, i) => "text" in p ? (0, import_react.createElement)(import_react.Fragment, { key: `t${i}` }, p.text) : (0, import_react.createElement)(import_react.Fragment, { key: `w${i}` }, renderWiki(p.wiki, opts.resolveWikiLink))
51
+ );
52
+ }
53
+ function renderChildren(node, opts) {
54
+ return (node.children ?? []).map((c, i) => renderNode(c, opts, i));
55
+ }
56
+ const HEADING_TAGS = ["h1", "h2", "h3", "h4", "h5", "h6"];
57
+ function renderNode(node, opts, index = 0) {
58
+ const key = `n${index}`;
59
+ switch (node.type) {
60
+ case "root":
61
+ return (0, import_react.createElement)(import_react.Fragment, { key }, ...renderChildren(node, opts));
62
+ case "text":
63
+ return renderText(node.value ?? "", opts);
64
+ case "paragraph":
65
+ return (0, import_react.createElement)("p", { key }, ...renderChildren(node, opts));
66
+ case "heading": {
67
+ const tag = HEADING_TAGS[Math.min(Math.max((node.depth ?? 1) - 1, 0), 5)];
68
+ return (0, import_react.createElement)(tag, { key }, ...renderChildren(node, opts));
69
+ }
70
+ case "strong":
71
+ return (0, import_react.createElement)("strong", { key }, ...renderChildren(node, opts));
72
+ case "emphasis":
73
+ return (0, import_react.createElement)("em", { key }, ...renderChildren(node, opts));
74
+ case "delete":
75
+ return (0, import_react.createElement)("del", { key }, ...renderChildren(node, opts));
76
+ case "inlineCode":
77
+ return (0, import_react.createElement)("code", { key }, node.value ?? "");
78
+ case "code":
79
+ return (0, import_react.createElement)("pre", { key }, (0, import_react.createElement)("code", null, node.value ?? ""));
80
+ case "blockquote":
81
+ return (0, import_react.createElement)("blockquote", { key }, ...renderChildren(node, opts));
82
+ case "list":
83
+ return (0, import_react.createElement)(node.ordered ? "ol" : "ul", { key }, ...renderChildren(node, opts));
84
+ case "listItem":
85
+ return (0, import_react.createElement)("li", { key }, ...renderChildren(node, opts));
86
+ case "thematicBreak":
87
+ return (0, import_react.createElement)("hr", { key });
88
+ case "break":
89
+ return (0, import_react.createElement)("br", { key });
90
+ case "link": {
91
+ const href = (0, import_sanitizeUrl.sanitizeUrl)(node.url);
92
+ if (href === void 0) return (0, import_react.createElement)(import_react.Fragment, { key }, ...renderChildren(node, opts));
93
+ return (0, import_react.createElement)("a", { key, href, title: node.title ?? void 0 }, ...renderChildren(node, opts));
94
+ }
95
+ case "image": {
96
+ const src = (0, import_sanitizeUrl.sanitizeUrl)(node.url);
97
+ if (src === void 0) return node.alt ? (0, import_react.createElement)(import_react.Fragment, { key }, node.alt) : null;
98
+ return (0, import_react.createElement)("img", { key, src, alt: node.alt ?? "", title: node.title ?? void 0 });
99
+ }
100
+ // GFM tables.
101
+ case "table":
102
+ return (0, import_react.createElement)("table", { key }, (0, import_react.createElement)("tbody", null, ...renderChildren(node, opts)));
103
+ case "tableRow":
104
+ return (0, import_react.createElement)("tr", { key }, ...renderChildren(node, opts));
105
+ case "tableCell":
106
+ return (0, import_react.createElement)("td", { key }, ...renderChildren(node, opts));
107
+ // Raw HTML (`<script>`, `<div onclick>` at block level) → INERT TEXT. No
108
+ // `rehype-raw`, never `dangerouslySetInnerHTML`. The literal markup is shown, not run.
109
+ case "html":
110
+ return (0, import_react.createElement)(import_react.Fragment, { key }, node.value ?? "");
111
+ // JSX `<Component/>` syntax → registry lookup by NAME, literal props only.
112
+ case "mdxJsxFlowElement":
113
+ case "mdxJsxTextElement": {
114
+ const name = typeof node.name === "string" ? node.name : "";
115
+ const Component = name ? opts.components?.[name] : void 0;
116
+ const children = renderChildren(node, opts);
117
+ if (!Component) return (0, import_react.createElement)(import_react.Fragment, { key }, ...children);
118
+ return (0, import_react.createElement)(Component, { key, ...literalProps(node.attributes) }, ...children);
119
+ }
120
+ // Inert expression nodes (should not occur — expression extension is off — but be
121
+ // defensive if a tree from elsewhere carries them): render nothing.
122
+ case "mdxFlowExpression":
123
+ case "mdxTextExpression":
124
+ case "mdxjsEsm":
125
+ return null;
126
+ default:
127
+ return node.children ? (0, import_react.createElement)(import_react.Fragment, { key }, ...renderChildren(node, opts)) : null;
128
+ }
129
+ }
130
+ function renderMdast(tree, options = {}) {
131
+ keyCounter = 0;
132
+ return renderNode(tree, options);
133
+ }
134
+ // Annotate the CommonJS export names for ESM import in node:
135
+ 0 && (module.exports = {
136
+ renderMdast
137
+ });
138
+ //# sourceMappingURL=renderMdast.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/safeContent/renderMdast.tsx"],"sourcesContent":["import { createElement, Fragment, type ReactNode } from 'react';\nimport { sanitizeUrl } from './sanitizeUrl';\nimport { splitWikiLinks, type WikiLinkToken } from './wikilink';\nimport type { SafeMdastNode, SafeMdxAttribute } from './parseSafeMdast';\n\n// The mdast→React renderer for the safe content path (TRUST_MODES_SPEC §5.1). PURE\n// and synchronous — it only reads `node.type` off an already-parsed tree, so it\n// carries no ESM dep and is exhaustively unit-testable. Every security property lives\n// here:\n//\n// - **JSX tags resolve ONLY to a component registry, by name.** A `<Component/>`\n// node becomes `registry[name]` with **literal string props only**; an unknown tag\n// renders its children inert (a Fragment, no element) — so author-written `<div\n// onclick=…>`, `<img onerror=…>`, `<script>` never become an intrinsic element with\n// attacker attributes. (Block-level raw HTML arrives as an `html` node, below.)\n// - **Expression attributes are dropped.** `f={fetch(\"/x\")}` / `{...spread}` are\n// `mdxJsxExpressionAttribute` or object-valued `mdxJsxAttribute`s — never passed to\n// a component, never evaluated. (There is also no evaluator to reach — parse used\n// no acorn.)\n// - **Raw HTML is inert text.** `html` nodes render as their literal string via a\n// text node — never `dangerouslySetInnerHTML` (no `rehype-raw`).\n// - **URL-scheme sanitizer** on every `link`/`image` URL (`sanitizeUrl`).\n// - **Wikilinks resolve in-mount only** via an injected resolver; the renderer never\n// fetches.\n\nexport interface SafeContentComponents {\n /** Safe components an app exposes to `<Component/>` syntax (host/app-provided).\n * Looked up by the JSX tag name; anything not here renders inert. */\n [tag: string]: React.ComponentType<Record<string, string>>;\n}\n\nexport interface RenderMdastOptions {\n /** The component registry for `<Component/>` syntax. Absent ⇒ all JSX tags inert. */\n components?: SafeContentComponents;\n /**\n * Resolve a wikilink target to an href, **within the granted mount only** — a pure,\n * synchronous, mount-scoped lookup that MUST NOT fetch or reach out of the mount.\n * Return `undefined` for an unresolvable/out-of-mount target (rendered as inert\n * text). Absent ⇒ every wikilink renders as inert text.\n */\n resolveWikiLink?: (target: string) => string | undefined;\n}\n\n/** Extract the literal string props of a JSX element — `mdxJsxAttribute`s whose value\n * is a plain string. Expression attributes (object value) and spreads are DROPPED. */\nfunction literalProps(attributes: SafeMdxAttribute[] | undefined): Record<string, string> {\n const props: Record<string, string> = {};\n for (const attr of attributes ?? []) {\n if (attr.type !== 'mdxJsxAttribute') continue; // drop `{...spread}`\n if (typeof attr.name !== 'string') continue;\n // A literal attribute's value is a string (or null → boolean-ish `true`). An\n // expression value is an object (`mdxJsxAttributeValueExpression`) — DROP it: it\n // is an inert raw string, never a real value, and must never reach a component.\n if (typeof attr.value === 'string') props[attr.name] = attr.value;\n else if (attr.value === null || attr.value === undefined) props[attr.name] = '';\n // object-valued (expression) → skipped\n }\n return props;\n}\n\nlet keyCounter = 0;\nconst nextKey = () => `sc-${keyCounter++}`;\n\n/** Render a wikilink token via the in-mount resolver, or inert text when it can't\n * resolve (never a network call — resolution is the injected mount-scoped callback). */\nfunction renderWiki(token: WikiLinkToken, resolve: RenderMdastOptions['resolveWikiLink']): ReactNode {\n const label = token.label ?? token.target;\n const href = resolve?.(token.target);\n const safe = href !== undefined ? sanitizeUrl(href) : undefined;\n if (safe === undefined) return label; // inert text — unresolved or out-of-mount\n return createElement('a', { href: safe, 'data-wikilink': token.target, key: nextKey() }, label);\n}\n\n/** Render a `text` node, splitting any `[[…]]` wikilinks out of it. */\nfunction renderText(value: string, opts: RenderMdastOptions): ReactNode {\n const parts = splitWikiLinks(value);\n if (!parts) return value;\n return parts.map((p, i) =>\n 'text' in p\n ? createElement(Fragment, { key: `t${i}` }, p.text)\n : createElement(Fragment, { key: `w${i}` }, renderWiki(p.wiki, opts.resolveWikiLink)),\n );\n}\n\nfunction renderChildren(node: SafeMdastNode, opts: RenderMdastOptions): ReactNode[] {\n return (node.children ?? []).map((c, i) => renderNode(c, opts, i));\n}\n\n// Standard mdast block/inline nodes → a FIXED, safe React element. We control every\n// attribute; author input only reaches text content and (sanitized) URLs.\nconst HEADING_TAGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;\n\nfunction renderNode(node: SafeMdastNode, opts: RenderMdastOptions, index = 0): ReactNode {\n const key = `n${index}`;\n switch (node.type) {\n case 'root':\n return createElement(Fragment, { key }, ...renderChildren(node, opts));\n case 'text':\n return renderText(node.value ?? '', opts);\n case 'paragraph':\n return createElement('p', { key }, ...renderChildren(node, opts));\n case 'heading': {\n const tag = HEADING_TAGS[Math.min(Math.max((node.depth ?? 1) - 1, 0), 5)];\n return createElement(tag, { key }, ...renderChildren(node, opts));\n }\n case 'strong':\n return createElement('strong', { key }, ...renderChildren(node, opts));\n case 'emphasis':\n return createElement('em', { key }, ...renderChildren(node, opts));\n case 'delete':\n return createElement('del', { key }, ...renderChildren(node, opts));\n case 'inlineCode':\n return createElement('code', { key }, node.value ?? '');\n case 'code':\n return createElement('pre', { key }, createElement('code', null, node.value ?? ''));\n case 'blockquote':\n return createElement('blockquote', { key }, ...renderChildren(node, opts));\n case 'list':\n return createElement(node.ordered ? 'ol' : 'ul', { key }, ...renderChildren(node, opts));\n case 'listItem':\n return createElement('li', { key }, ...renderChildren(node, opts));\n case 'thematicBreak':\n return createElement('hr', { key });\n case 'break':\n return createElement('br', { key });\n case 'link': {\n const href = sanitizeUrl(node.url);\n // A rejected scheme → render the link TEXT only (inert), never an <a href>.\n if (href === undefined) return createElement(Fragment, { key }, ...renderChildren(node, opts));\n return createElement('a', { key, href, title: node.title ?? undefined }, ...renderChildren(node, opts));\n }\n case 'image': {\n const src = sanitizeUrl(node.url);\n if (src === undefined) return node.alt ? createElement(Fragment, { key }, node.alt) : null;\n return createElement('img', { key, src, alt: node.alt ?? '', title: node.title ?? undefined });\n }\n // GFM tables.\n case 'table':\n return createElement('table', { key }, createElement('tbody', null, ...renderChildren(node, opts)));\n case 'tableRow':\n return createElement('tr', { key }, ...renderChildren(node, opts));\n case 'tableCell':\n return createElement('td', { key }, ...renderChildren(node, opts));\n // Raw HTML (`<script>`, `<div onclick>` at block level) → INERT TEXT. No\n // `rehype-raw`, never `dangerouslySetInnerHTML`. The literal markup is shown, not run.\n case 'html':\n return createElement(Fragment, { key }, node.value ?? '');\n // JSX `<Component/>` syntax → registry lookup by NAME, literal props only.\n case 'mdxJsxFlowElement':\n case 'mdxJsxTextElement': {\n const name = typeof node.name === 'string' ? node.name : '';\n const Component = name ? opts.components?.[name] : undefined;\n const children = renderChildren(node, opts);\n // Unknown tag (not in the registry) OR a fragment `<>` → render children inert,\n // NO element, NO author attributes. This is what neutralizes `<script>`/`<div\n // onclick>`/`<img onerror>` written as JSX.\n if (!Component) return createElement(Fragment, { key }, ...children);\n return createElement(Component, { key, ...literalProps(node.attributes) }, ...children);\n }\n // Inert expression nodes (should not occur — expression extension is off — but be\n // defensive if a tree from elsewhere carries them): render nothing.\n case 'mdxFlowExpression':\n case 'mdxTextExpression':\n case 'mdxjsEsm':\n return null;\n default:\n // Unknown node → render its children (never its raw value as markup).\n return node.children ? createElement(Fragment, { key }, ...renderChildren(node, opts)) : null;\n }\n}\n\n/**\n * Render a safe-parsed mdast tree to React. Pure and synchronous; carries every §5.1\n * security property (registry-only JSX, dropped expressions, inert raw HTML, URL\n * sanitizing, in-mount wikilinks). Feed it a tree from {@link parseSafeMdast}.\n */\nexport function renderMdast(tree: SafeMdastNode, options: RenderMdastOptions = {}): ReactNode {\n keyCounter = 0;\n return renderNode(tree, options);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,27 @@
1
+ import { ReactNode } from 'react';
2
+ import { SafeMdastNode } from './parseSafeMdast.cjs';
3
+
4
+ interface SafeContentComponents {
5
+ /** Safe components an app exposes to `<Component/>` syntax (host/app-provided).
6
+ * Looked up by the JSX tag name; anything not here renders inert. */
7
+ [tag: string]: React.ComponentType<Record<string, string>>;
8
+ }
9
+ interface RenderMdastOptions {
10
+ /** The component registry for `<Component/>` syntax. Absent ⇒ all JSX tags inert. */
11
+ components?: SafeContentComponents;
12
+ /**
13
+ * Resolve a wikilink target to an href, **within the granted mount only** — a pure,
14
+ * synchronous, mount-scoped lookup that MUST NOT fetch or reach out of the mount.
15
+ * Return `undefined` for an unresolvable/out-of-mount target (rendered as inert
16
+ * text). Absent ⇒ every wikilink renders as inert text.
17
+ */
18
+ resolveWikiLink?: (target: string) => string | undefined;
19
+ }
20
+ /**
21
+ * Render a safe-parsed mdast tree to React. Pure and synchronous; carries every §5.1
22
+ * security property (registry-only JSX, dropped expressions, inert raw HTML, URL
23
+ * sanitizing, in-mount wikilinks). Feed it a tree from {@link parseSafeMdast}.
24
+ */
25
+ declare function renderMdast(tree: SafeMdastNode, options?: RenderMdastOptions): ReactNode;
26
+
27
+ export { type RenderMdastOptions, type SafeContentComponents, renderMdast };
@@ -0,0 +1,27 @@
1
+ import { ReactNode } from 'react';
2
+ import { SafeMdastNode } from './parseSafeMdast.js';
3
+
4
+ interface SafeContentComponents {
5
+ /** Safe components an app exposes to `<Component/>` syntax (host/app-provided).
6
+ * Looked up by the JSX tag name; anything not here renders inert. */
7
+ [tag: string]: React.ComponentType<Record<string, string>>;
8
+ }
9
+ interface RenderMdastOptions {
10
+ /** The component registry for `<Component/>` syntax. Absent ⇒ all JSX tags inert. */
11
+ components?: SafeContentComponents;
12
+ /**
13
+ * Resolve a wikilink target to an href, **within the granted mount only** — a pure,
14
+ * synchronous, mount-scoped lookup that MUST NOT fetch or reach out of the mount.
15
+ * Return `undefined` for an unresolvable/out-of-mount target (rendered as inert
16
+ * text). Absent ⇒ every wikilink renders as inert text.
17
+ */
18
+ resolveWikiLink?: (target: string) => string | undefined;
19
+ }
20
+ /**
21
+ * Render a safe-parsed mdast tree to React. Pure and synchronous; carries every §5.1
22
+ * security property (registry-only JSX, dropped expressions, inert raw HTML, URL
23
+ * sanitizing, in-mount wikilinks). Feed it a tree from {@link parseSafeMdast}.
24
+ */
25
+ declare function renderMdast(tree: SafeMdastNode, options?: RenderMdastOptions): ReactNode;
26
+
27
+ export { type RenderMdastOptions, type SafeContentComponents, renderMdast };
@@ -0,0 +1,114 @@
1
+ import { createElement, Fragment } from "react";
2
+ import { sanitizeUrl } from "./sanitizeUrl";
3
+ import { splitWikiLinks } from "./wikilink";
4
+ function literalProps(attributes) {
5
+ const props = {};
6
+ for (const attr of attributes ?? []) {
7
+ if (attr.type !== "mdxJsxAttribute") continue;
8
+ if (typeof attr.name !== "string") continue;
9
+ if (typeof attr.value === "string") props[attr.name] = attr.value;
10
+ else if (attr.value === null || attr.value === void 0) props[attr.name] = "";
11
+ }
12
+ return props;
13
+ }
14
+ let keyCounter = 0;
15
+ const nextKey = () => `sc-${keyCounter++}`;
16
+ function renderWiki(token, resolve) {
17
+ const label = token.label ?? token.target;
18
+ const href = resolve?.(token.target);
19
+ const safe = href !== void 0 ? sanitizeUrl(href) : void 0;
20
+ if (safe === void 0) return label;
21
+ return createElement("a", { href: safe, "data-wikilink": token.target, key: nextKey() }, label);
22
+ }
23
+ function renderText(value, opts) {
24
+ const parts = splitWikiLinks(value);
25
+ if (!parts) return value;
26
+ return parts.map(
27
+ (p, i) => "text" in p ? createElement(Fragment, { key: `t${i}` }, p.text) : createElement(Fragment, { key: `w${i}` }, renderWiki(p.wiki, opts.resolveWikiLink))
28
+ );
29
+ }
30
+ function renderChildren(node, opts) {
31
+ return (node.children ?? []).map((c, i) => renderNode(c, opts, i));
32
+ }
33
+ const HEADING_TAGS = ["h1", "h2", "h3", "h4", "h5", "h6"];
34
+ function renderNode(node, opts, index = 0) {
35
+ const key = `n${index}`;
36
+ switch (node.type) {
37
+ case "root":
38
+ return createElement(Fragment, { key }, ...renderChildren(node, opts));
39
+ case "text":
40
+ return renderText(node.value ?? "", opts);
41
+ case "paragraph":
42
+ return createElement("p", { key }, ...renderChildren(node, opts));
43
+ case "heading": {
44
+ const tag = HEADING_TAGS[Math.min(Math.max((node.depth ?? 1) - 1, 0), 5)];
45
+ return createElement(tag, { key }, ...renderChildren(node, opts));
46
+ }
47
+ case "strong":
48
+ return createElement("strong", { key }, ...renderChildren(node, opts));
49
+ case "emphasis":
50
+ return createElement("em", { key }, ...renderChildren(node, opts));
51
+ case "delete":
52
+ return createElement("del", { key }, ...renderChildren(node, opts));
53
+ case "inlineCode":
54
+ return createElement("code", { key }, node.value ?? "");
55
+ case "code":
56
+ return createElement("pre", { key }, createElement("code", null, node.value ?? ""));
57
+ case "blockquote":
58
+ return createElement("blockquote", { key }, ...renderChildren(node, opts));
59
+ case "list":
60
+ return createElement(node.ordered ? "ol" : "ul", { key }, ...renderChildren(node, opts));
61
+ case "listItem":
62
+ return createElement("li", { key }, ...renderChildren(node, opts));
63
+ case "thematicBreak":
64
+ return createElement("hr", { key });
65
+ case "break":
66
+ return createElement("br", { key });
67
+ case "link": {
68
+ const href = sanitizeUrl(node.url);
69
+ if (href === void 0) return createElement(Fragment, { key }, ...renderChildren(node, opts));
70
+ return createElement("a", { key, href, title: node.title ?? void 0 }, ...renderChildren(node, opts));
71
+ }
72
+ case "image": {
73
+ const src = sanitizeUrl(node.url);
74
+ if (src === void 0) return node.alt ? createElement(Fragment, { key }, node.alt) : null;
75
+ return createElement("img", { key, src, alt: node.alt ?? "", title: node.title ?? void 0 });
76
+ }
77
+ // GFM tables.
78
+ case "table":
79
+ return createElement("table", { key }, createElement("tbody", null, ...renderChildren(node, opts)));
80
+ case "tableRow":
81
+ return createElement("tr", { key }, ...renderChildren(node, opts));
82
+ case "tableCell":
83
+ return createElement("td", { key }, ...renderChildren(node, opts));
84
+ // Raw HTML (`<script>`, `<div onclick>` at block level) → INERT TEXT. No
85
+ // `rehype-raw`, never `dangerouslySetInnerHTML`. The literal markup is shown, not run.
86
+ case "html":
87
+ return createElement(Fragment, { key }, node.value ?? "");
88
+ // JSX `<Component/>` syntax → registry lookup by NAME, literal props only.
89
+ case "mdxJsxFlowElement":
90
+ case "mdxJsxTextElement": {
91
+ const name = typeof node.name === "string" ? node.name : "";
92
+ const Component = name ? opts.components?.[name] : void 0;
93
+ const children = renderChildren(node, opts);
94
+ if (!Component) return createElement(Fragment, { key }, ...children);
95
+ return createElement(Component, { key, ...literalProps(node.attributes) }, ...children);
96
+ }
97
+ // Inert expression nodes (should not occur — expression extension is off — but be
98
+ // defensive if a tree from elsewhere carries them): render nothing.
99
+ case "mdxFlowExpression":
100
+ case "mdxTextExpression":
101
+ case "mdxjsEsm":
102
+ return null;
103
+ default:
104
+ return node.children ? createElement(Fragment, { key }, ...renderChildren(node, opts)) : null;
105
+ }
106
+ }
107
+ function renderMdast(tree, options = {}) {
108
+ keyCounter = 0;
109
+ return renderNode(tree, options);
110
+ }
111
+ export {
112
+ renderMdast
113
+ };
114
+ //# sourceMappingURL=renderMdast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/safeContent/renderMdast.tsx"],"sourcesContent":["import { createElement, Fragment, type ReactNode } from 'react';\nimport { sanitizeUrl } from './sanitizeUrl';\nimport { splitWikiLinks, type WikiLinkToken } from './wikilink';\nimport type { SafeMdastNode, SafeMdxAttribute } from './parseSafeMdast';\n\n// The mdast→React renderer for the safe content path (TRUST_MODES_SPEC §5.1). PURE\n// and synchronous — it only reads `node.type` off an already-parsed tree, so it\n// carries no ESM dep and is exhaustively unit-testable. Every security property lives\n// here:\n//\n// - **JSX tags resolve ONLY to a component registry, by name.** A `<Component/>`\n// node becomes `registry[name]` with **literal string props only**; an unknown tag\n// renders its children inert (a Fragment, no element) — so author-written `<div\n// onclick=…>`, `<img onerror=…>`, `<script>` never become an intrinsic element with\n// attacker attributes. (Block-level raw HTML arrives as an `html` node, below.)\n// - **Expression attributes are dropped.** `f={fetch(\"/x\")}` / `{...spread}` are\n// `mdxJsxExpressionAttribute` or object-valued `mdxJsxAttribute`s — never passed to\n// a component, never evaluated. (There is also no evaluator to reach — parse used\n// no acorn.)\n// - **Raw HTML is inert text.** `html` nodes render as their literal string via a\n// text node — never `dangerouslySetInnerHTML` (no `rehype-raw`).\n// - **URL-scheme sanitizer** on every `link`/`image` URL (`sanitizeUrl`).\n// - **Wikilinks resolve in-mount only** via an injected resolver; the renderer never\n// fetches.\n\nexport interface SafeContentComponents {\n /** Safe components an app exposes to `<Component/>` syntax (host/app-provided).\n * Looked up by the JSX tag name; anything not here renders inert. */\n [tag: string]: React.ComponentType<Record<string, string>>;\n}\n\nexport interface RenderMdastOptions {\n /** The component registry for `<Component/>` syntax. Absent ⇒ all JSX tags inert. */\n components?: SafeContentComponents;\n /**\n * Resolve a wikilink target to an href, **within the granted mount only** — a pure,\n * synchronous, mount-scoped lookup that MUST NOT fetch or reach out of the mount.\n * Return `undefined` for an unresolvable/out-of-mount target (rendered as inert\n * text). Absent ⇒ every wikilink renders as inert text.\n */\n resolveWikiLink?: (target: string) => string | undefined;\n}\n\n/** Extract the literal string props of a JSX element — `mdxJsxAttribute`s whose value\n * is a plain string. Expression attributes (object value) and spreads are DROPPED. */\nfunction literalProps(attributes: SafeMdxAttribute[] | undefined): Record<string, string> {\n const props: Record<string, string> = {};\n for (const attr of attributes ?? []) {\n if (attr.type !== 'mdxJsxAttribute') continue; // drop `{...spread}`\n if (typeof attr.name !== 'string') continue;\n // A literal attribute's value is a string (or null → boolean-ish `true`). An\n // expression value is an object (`mdxJsxAttributeValueExpression`) — DROP it: it\n // is an inert raw string, never a real value, and must never reach a component.\n if (typeof attr.value === 'string') props[attr.name] = attr.value;\n else if (attr.value === null || attr.value === undefined) props[attr.name] = '';\n // object-valued (expression) → skipped\n }\n return props;\n}\n\nlet keyCounter = 0;\nconst nextKey = () => `sc-${keyCounter++}`;\n\n/** Render a wikilink token via the in-mount resolver, or inert text when it can't\n * resolve (never a network call — resolution is the injected mount-scoped callback). */\nfunction renderWiki(token: WikiLinkToken, resolve: RenderMdastOptions['resolveWikiLink']): ReactNode {\n const label = token.label ?? token.target;\n const href = resolve?.(token.target);\n const safe = href !== undefined ? sanitizeUrl(href) : undefined;\n if (safe === undefined) return label; // inert text — unresolved or out-of-mount\n return createElement('a', { href: safe, 'data-wikilink': token.target, key: nextKey() }, label);\n}\n\n/** Render a `text` node, splitting any `[[…]]` wikilinks out of it. */\nfunction renderText(value: string, opts: RenderMdastOptions): ReactNode {\n const parts = splitWikiLinks(value);\n if (!parts) return value;\n return parts.map((p, i) =>\n 'text' in p\n ? createElement(Fragment, { key: `t${i}` }, p.text)\n : createElement(Fragment, { key: `w${i}` }, renderWiki(p.wiki, opts.resolveWikiLink)),\n );\n}\n\nfunction renderChildren(node: SafeMdastNode, opts: RenderMdastOptions): ReactNode[] {\n return (node.children ?? []).map((c, i) => renderNode(c, opts, i));\n}\n\n// Standard mdast block/inline nodes → a FIXED, safe React element. We control every\n// attribute; author input only reaches text content and (sanitized) URLs.\nconst HEADING_TAGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;\n\nfunction renderNode(node: SafeMdastNode, opts: RenderMdastOptions, index = 0): ReactNode {\n const key = `n${index}`;\n switch (node.type) {\n case 'root':\n return createElement(Fragment, { key }, ...renderChildren(node, opts));\n case 'text':\n return renderText(node.value ?? '', opts);\n case 'paragraph':\n return createElement('p', { key }, ...renderChildren(node, opts));\n case 'heading': {\n const tag = HEADING_TAGS[Math.min(Math.max((node.depth ?? 1) - 1, 0), 5)];\n return createElement(tag, { key }, ...renderChildren(node, opts));\n }\n case 'strong':\n return createElement('strong', { key }, ...renderChildren(node, opts));\n case 'emphasis':\n return createElement('em', { key }, ...renderChildren(node, opts));\n case 'delete':\n return createElement('del', { key }, ...renderChildren(node, opts));\n case 'inlineCode':\n return createElement('code', { key }, node.value ?? '');\n case 'code':\n return createElement('pre', { key }, createElement('code', null, node.value ?? ''));\n case 'blockquote':\n return createElement('blockquote', { key }, ...renderChildren(node, opts));\n case 'list':\n return createElement(node.ordered ? 'ol' : 'ul', { key }, ...renderChildren(node, opts));\n case 'listItem':\n return createElement('li', { key }, ...renderChildren(node, opts));\n case 'thematicBreak':\n return createElement('hr', { key });\n case 'break':\n return createElement('br', { key });\n case 'link': {\n const href = sanitizeUrl(node.url);\n // A rejected scheme → render the link TEXT only (inert), never an <a href>.\n if (href === undefined) return createElement(Fragment, { key }, ...renderChildren(node, opts));\n return createElement('a', { key, href, title: node.title ?? undefined }, ...renderChildren(node, opts));\n }\n case 'image': {\n const src = sanitizeUrl(node.url);\n if (src === undefined) return node.alt ? createElement(Fragment, { key }, node.alt) : null;\n return createElement('img', { key, src, alt: node.alt ?? '', title: node.title ?? undefined });\n }\n // GFM tables.\n case 'table':\n return createElement('table', { key }, createElement('tbody', null, ...renderChildren(node, opts)));\n case 'tableRow':\n return createElement('tr', { key }, ...renderChildren(node, opts));\n case 'tableCell':\n return createElement('td', { key }, ...renderChildren(node, opts));\n // Raw HTML (`<script>`, `<div onclick>` at block level) → INERT TEXT. No\n // `rehype-raw`, never `dangerouslySetInnerHTML`. The literal markup is shown, not run.\n case 'html':\n return createElement(Fragment, { key }, node.value ?? '');\n // JSX `<Component/>` syntax → registry lookup by NAME, literal props only.\n case 'mdxJsxFlowElement':\n case 'mdxJsxTextElement': {\n const name = typeof node.name === 'string' ? node.name : '';\n const Component = name ? opts.components?.[name] : undefined;\n const children = renderChildren(node, opts);\n // Unknown tag (not in the registry) OR a fragment `<>` → render children inert,\n // NO element, NO author attributes. This is what neutralizes `<script>`/`<div\n // onclick>`/`<img onerror>` written as JSX.\n if (!Component) return createElement(Fragment, { key }, ...children);\n return createElement(Component, { key, ...literalProps(node.attributes) }, ...children);\n }\n // Inert expression nodes (should not occur — expression extension is off — but be\n // defensive if a tree from elsewhere carries them): render nothing.\n case 'mdxFlowExpression':\n case 'mdxTextExpression':\n case 'mdxjsEsm':\n return null;\n default:\n // Unknown node → render its children (never its raw value as markup).\n return node.children ? createElement(Fragment, { key }, ...renderChildren(node, opts)) : null;\n }\n}\n\n/**\n * Render a safe-parsed mdast tree to React. Pure and synchronous; carries every §5.1\n * security property (registry-only JSX, dropped expressions, inert raw HTML, URL\n * sanitizing, in-mount wikilinks). Feed it a tree from {@link parseSafeMdast}.\n */\nexport function renderMdast(tree: SafeMdastNode, options: RenderMdastOptions = {}): ReactNode {\n keyCounter = 0;\n return renderNode(tree, options);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var sanitizeUrl_exports = {};
20
+ __export(sanitizeUrl_exports, {
21
+ sanitizeUrl: () => sanitizeUrl
22
+ });
23
+ module.exports = __toCommonJS(sanitizeUrl_exports);
24
+ const SCHEME = /^([a-zA-Z][a-zA-Z0-9+.-]*):/;
25
+ const ALLOWED_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto"]);
26
+ const CONTROL_CHARS = /[\u0000-\u001F\u007F]/g;
27
+ function sanitizeUrl(url) {
28
+ if (typeof url !== "string") return void 0;
29
+ const cleaned = url.replace(CONTROL_CHARS, "").trim();
30
+ if (cleaned === "") return void 0;
31
+ const m = SCHEME.exec(cleaned);
32
+ if (!m) return cleaned;
33
+ return ALLOWED_SCHEMES.has(m[1].toLowerCase()) ? cleaned : void 0;
34
+ }
35
+ // Annotate the CommonJS export names for ESM import in node:
36
+ 0 && (module.exports = {
37
+ sanitizeUrl
38
+ });
39
+ //# sourceMappingURL=sanitizeUrl.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/safeContent/sanitizeUrl.ts"],"sourcesContent":["// URL-scheme sanitizer for the safe content renderer (TRUST_MODES_SPEC §5.1,\n// AGENT_AUTHORING §10). A §5.1 residual requirement: link/image URLs in untrusted\n// content are allowlisted to `http`/`https`/`mailto` and relative URLs; every other\n// scheme — `javascript:`, `data:`, `vbscript:`, `file:`, … — is rejected. We do NOT\n// rely on React to neutralize `javascript:` (it does not, on an `<a href>`), so this\n// is the single chokepoint every rendered `href`/`src` passes through.\n\n// A leading `scheme:` per RFC 3986 (`ALPHA *( ALPHA / DIGIT / \"+\" / \"-\" / \".\" )`).\n// Anything before the first `:` that matches this is an absolute-URI scheme; if the\n// URL has no such prefix it is relative (path/query/fragment) and allowed.\nconst SCHEME = /^([a-zA-Z][a-zA-Z0-9+.-]*):/;\nconst ALLOWED_SCHEMES = new Set(['http', 'https', 'mailto']);\n\n// ASCII control chars (0x00–0x1F, 0x7F) that browsers ignore INSIDE a scheme — the\n// `java\\tscript:` / `java\\nscript:` obfuscation — stripped anywhere before testing.\n// eslint-disable-next-line no-control-regex\nconst CONTROL_CHARS = /[\\u0000-\\u001F\\u007F]/g;\n\n/**\n * Return the URL if its scheme is allowed (or it is relative/anchor), else\n * `undefined`. Control chars are stripped first so an obfuscated scheme cannot slip\n * past. A protocol-relative `//host` URL is treated as relative-with-authority and\n * allowed (it inherits the page's https scheme).\n */\nexport function sanitizeUrl(url: unknown): string | undefined {\n if (typeof url !== 'string') return undefined;\n const cleaned = url.replace(CONTROL_CHARS, '').trim();\n if (cleaned === '') return undefined;\n const m = SCHEME.exec(cleaned);\n if (!m) return cleaned; // no scheme → relative / anchor / query — allowed\n return ALLOWED_SCHEMES.has(m[1].toLowerCase()) ? cleaned : undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,MAAM,SAAS;AACf,MAAM,kBAAkB,oBAAI,IAAI,CAAC,QAAQ,SAAS,QAAQ,CAAC;AAK3D,MAAM,gBAAgB;AAQf,SAAS,YAAY,KAAkC;AAC5D,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAM,UAAU,IAAI,QAAQ,eAAe,EAAE,EAAE,KAAK;AACpD,MAAI,YAAY,GAAI,QAAO;AAC3B,QAAM,IAAI,OAAO,KAAK,OAAO;AAC7B,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,gBAAgB,IAAI,EAAE,CAAC,EAAE,YAAY,CAAC,IAAI,UAAU;AAC7D;","names":[]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Return the URL if its scheme is allowed (or it is relative/anchor), else
3
+ * `undefined`. Control chars are stripped first so an obfuscated scheme cannot slip
4
+ * past. A protocol-relative `//host` URL is treated as relative-with-authority and
5
+ * allowed (it inherits the page's https scheme).
6
+ */
7
+ declare function sanitizeUrl(url: unknown): string | undefined;
8
+
9
+ export { sanitizeUrl };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Return the URL if its scheme is allowed (or it is relative/anchor), else
3
+ * `undefined`. Control chars are stripped first so an obfuscated scheme cannot slip
4
+ * past. A protocol-relative `//host` URL is treated as relative-with-authority and
5
+ * allowed (it inherits the page's https scheme).
6
+ */
7
+ declare function sanitizeUrl(url: unknown): string | undefined;
8
+
9
+ export { sanitizeUrl };
@@ -0,0 +1,15 @@
1
+ const SCHEME = /^([a-zA-Z][a-zA-Z0-9+.-]*):/;
2
+ const ALLOWED_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto"]);
3
+ const CONTROL_CHARS = /[\u0000-\u001F\u007F]/g;
4
+ function sanitizeUrl(url) {
5
+ if (typeof url !== "string") return void 0;
6
+ const cleaned = url.replace(CONTROL_CHARS, "").trim();
7
+ if (cleaned === "") return void 0;
8
+ const m = SCHEME.exec(cleaned);
9
+ if (!m) return cleaned;
10
+ return ALLOWED_SCHEMES.has(m[1].toLowerCase()) ? cleaned : void 0;
11
+ }
12
+ export {
13
+ sanitizeUrl
14
+ };
15
+ //# sourceMappingURL=sanitizeUrl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/safeContent/sanitizeUrl.ts"],"sourcesContent":["// URL-scheme sanitizer for the safe content renderer (TRUST_MODES_SPEC §5.1,\n// AGENT_AUTHORING §10). A §5.1 residual requirement: link/image URLs in untrusted\n// content are allowlisted to `http`/`https`/`mailto` and relative URLs; every other\n// scheme — `javascript:`, `data:`, `vbscript:`, `file:`, … — is rejected. We do NOT\n// rely on React to neutralize `javascript:` (it does not, on an `<a href>`), so this\n// is the single chokepoint every rendered `href`/`src` passes through.\n\n// A leading `scheme:` per RFC 3986 (`ALPHA *( ALPHA / DIGIT / \"+\" / \"-\" / \".\" )`).\n// Anything before the first `:` that matches this is an absolute-URI scheme; if the\n// URL has no such prefix it is relative (path/query/fragment) and allowed.\nconst SCHEME = /^([a-zA-Z][a-zA-Z0-9+.-]*):/;\nconst ALLOWED_SCHEMES = new Set(['http', 'https', 'mailto']);\n\n// ASCII control chars (0x00–0x1F, 0x7F) that browsers ignore INSIDE a scheme — the\n// `java\\tscript:` / `java\\nscript:` obfuscation — stripped anywhere before testing.\n// eslint-disable-next-line no-control-regex\nconst CONTROL_CHARS = /[\\u0000-\\u001F\\u007F]/g;\n\n/**\n * Return the URL if its scheme is allowed (or it is relative/anchor), else\n * `undefined`. Control chars are stripped first so an obfuscated scheme cannot slip\n * past. A protocol-relative `//host` URL is treated as relative-with-authority and\n * allowed (it inherits the page's https scheme).\n */\nexport function sanitizeUrl(url: unknown): string | undefined {\n if (typeof url !== 'string') return undefined;\n const cleaned = url.replace(CONTROL_CHARS, '').trim();\n if (cleaned === '') return undefined;\n const m = SCHEME.exec(cleaned);\n if (!m) return cleaned; // no scheme → relative / anchor / query — allowed\n return ALLOWED_SCHEMES.has(m[1].toLowerCase()) ? cleaned : undefined;\n}\n"],"mappings":"AAUA,MAAM,SAAS;AACf,MAAM,kBAAkB,oBAAI,IAAI,CAAC,QAAQ,SAAS,QAAQ,CAAC;AAK3D,MAAM,gBAAgB;AAQf,SAAS,YAAY,KAAkC;AAC5D,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAM,UAAU,IAAI,QAAQ,eAAe,EAAE,EAAE,KAAK;AACpD,MAAI,YAAY,GAAI,QAAO;AAC3B,QAAM,IAAI,OAAO,KAAK,OAAO;AAC7B,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,gBAAgB,IAAI,EAAE,CAAC,EAAE,YAAY,CAAC,IAAI,UAAU;AAC7D;","names":[]}
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var wikilink_exports = {};
20
+ __export(wikilink_exports, {
21
+ parseWikiInner: () => parseWikiInner,
22
+ splitWikiLinks: () => splitWikiLinks
23
+ });
24
+ module.exports = __toCommonJS(wikilink_exports);
25
+ const WIKILINK = /\[\[([^[\]]+)\]\]/g;
26
+ function parseWikiInner(inner) {
27
+ const pipe = inner.indexOf("|");
28
+ let target;
29
+ let label;
30
+ if (pipe === -1) {
31
+ target = inner.trim();
32
+ } else {
33
+ label = inner.slice(0, pipe).trim();
34
+ target = inner.slice(pipe + 1).trim();
35
+ }
36
+ if (target === "") return null;
37
+ return label ? { target, label } : { target };
38
+ }
39
+ function splitWikiLinks(value) {
40
+ if (value.indexOf("[[") === -1) return null;
41
+ const out = [];
42
+ let lastIndex = 0;
43
+ let produced = false;
44
+ WIKILINK.lastIndex = 0;
45
+ let match;
46
+ while ((match = WIKILINK.exec(value)) !== null) {
47
+ const token = parseWikiInner(match[1]);
48
+ if (!token) continue;
49
+ if (match.index > lastIndex) out.push({ text: value.slice(lastIndex, match.index) });
50
+ out.push({ wiki: token });
51
+ lastIndex = match.index + match[0].length;
52
+ produced = true;
53
+ }
54
+ if (!produced) return null;
55
+ if (lastIndex < value.length) out.push({ text: value.slice(lastIndex) });
56
+ return out;
57
+ }
58
+ // Annotate the CommonJS export names for ESM import in node:
59
+ 0 && (module.exports = {
60
+ parseWikiInner,
61
+ splitWikiLinks
62
+ });
63
+ //# sourceMappingURL=wikilink.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/safeContent/wikilink.ts"],"sourcesContent":["// Wikilink parsing for the safe content renderer (TRUST_MODES_SPEC §5.1). A §5.1\n// residual: wikilinks resolve **only within the granted mount, never a fetch**. The\n// kernel's executable pipeline compiles `[[target]]`/`[[label|target]]` to a\n// `<WikiLink>` component (transpiler `remarkWikiLinks`, MARKDOWN_SYNTAX_SPEC §13); the\n// SAFE renderer parses raw source, so it splits `[[…]]` out of text itself with the\n// same byte-local rule and hands the raw target/label to an injected, **in-mount**\n// resolver. The renderer never fetches; resolution is the consumer's mount-scoped\n// callback (or, absent one, the link renders as inert text).\n\n// Inner content up to the closing `]]`, forbidding `[`/`]` so nested brackets can't\n// be swallowed. Global — a text run may hold several.\nconst WIKILINK = /\\[\\[([^[\\]]+)\\]\\]/g;\n\nexport interface WikiLinkToken {\n /** The raw target path, verbatim (relative or absolute; resolved in-mount only). */\n target: string;\n /** The explicit label, or undefined (the component derives one). */\n label?: string;\n}\n\nexport type WikiPart = { text: string } | { wiki: WikiLinkToken };\n\n/** Parse the `[[label|target]]` / `[[target]]` inner text (label first, then target\n * — the §13.1 order), or `null` for an empty target (leave the literal `[[…]]`). */\nexport function parseWikiInner(inner: string): WikiLinkToken | null {\n const pipe = inner.indexOf('|');\n let target: string;\n let label: string | undefined;\n if (pipe === -1) {\n target = inner.trim();\n } else {\n label = inner.slice(0, pipe).trim();\n target = inner.slice(pipe + 1).trim();\n }\n if (target === '') return null;\n return label ? { target, label } : { target };\n}\n\n/**\n * Split a text string on its `[[…]]` runs into ordered text / wiki parts, or `null`\n * when there is no usable wiki-link (so the caller keeps the text node untouched).\n */\nexport function splitWikiLinks(value: string): WikiPart[] | null {\n if (value.indexOf('[[') === -1) return null;\n const out: WikiPart[] = [];\n let lastIndex = 0;\n let produced = false;\n WIKILINK.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = WIKILINK.exec(value)) !== null) {\n const token = parseWikiInner(match[1]);\n if (!token) continue; // empty target → keep the literal `[[…]]`\n if (match.index > lastIndex) out.push({ text: value.slice(lastIndex, match.index) });\n out.push({ wiki: token });\n lastIndex = match.index + match[0].length;\n produced = true;\n }\n if (!produced) return null;\n if (lastIndex < value.length) out.push({ text: value.slice(lastIndex) });\n return out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,MAAM,WAAW;AAaV,SAAS,eAAe,OAAqC;AAClE,QAAM,OAAO,MAAM,QAAQ,GAAG;AAC9B,MAAI;AACJ,MAAI;AACJ,MAAI,SAAS,IAAI;AACf,aAAS,MAAM,KAAK;AAAA,EACtB,OAAO;AACL,YAAQ,MAAM,MAAM,GAAG,IAAI,EAAE,KAAK;AAClC,aAAS,MAAM,MAAM,OAAO,CAAC,EAAE,KAAK;AAAA,EACtC;AACA,MAAI,WAAW,GAAI,QAAO;AAC1B,SAAO,QAAQ,EAAE,QAAQ,MAAM,IAAI,EAAE,OAAO;AAC9C;AAMO,SAAS,eAAe,OAAkC;AAC/D,MAAI,MAAM,QAAQ,IAAI,MAAM,GAAI,QAAO;AACvC,QAAM,MAAkB,CAAC;AACzB,MAAI,YAAY;AAChB,MAAI,WAAW;AACf,WAAS,YAAY;AACrB,MAAI;AACJ,UAAQ,QAAQ,SAAS,KAAK,KAAK,OAAO,MAAM;AAC9C,UAAM,QAAQ,eAAe,MAAM,CAAC,CAAC;AACrC,QAAI,CAAC,MAAO;AACZ,QAAI,MAAM,QAAQ,UAAW,KAAI,KAAK,EAAE,MAAM,MAAM,MAAM,WAAW,MAAM,KAAK,EAAE,CAAC;AACnF,QAAI,KAAK,EAAE,MAAM,MAAM,CAAC;AACxB,gBAAY,MAAM,QAAQ,MAAM,CAAC,EAAE;AACnC,eAAW;AAAA,EACb;AACA,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI,YAAY,MAAM,OAAQ,KAAI,KAAK,EAAE,MAAM,MAAM,MAAM,SAAS,EAAE,CAAC;AACvE,SAAO;AACT;","names":[]}
@@ -0,0 +1,21 @@
1
+ interface WikiLinkToken {
2
+ /** The raw target path, verbatim (relative or absolute; resolved in-mount only). */
3
+ target: string;
4
+ /** The explicit label, or undefined (the component derives one). */
5
+ label?: string;
6
+ }
7
+ type WikiPart = {
8
+ text: string;
9
+ } | {
10
+ wiki: WikiLinkToken;
11
+ };
12
+ /** Parse the `[[label|target]]` / `[[target]]` inner text (label first, then target
13
+ * — the §13.1 order), or `null` for an empty target (leave the literal `[[…]]`). */
14
+ declare function parseWikiInner(inner: string): WikiLinkToken | null;
15
+ /**
16
+ * Split a text string on its `[[…]]` runs into ordered text / wiki parts, or `null`
17
+ * when there is no usable wiki-link (so the caller keeps the text node untouched).
18
+ */
19
+ declare function splitWikiLinks(value: string): WikiPart[] | null;
20
+
21
+ export { type WikiLinkToken, type WikiPart, parseWikiInner, splitWikiLinks };
@@ -0,0 +1,21 @@
1
+ interface WikiLinkToken {
2
+ /** The raw target path, verbatim (relative or absolute; resolved in-mount only). */
3
+ target: string;
4
+ /** The explicit label, or undefined (the component derives one). */
5
+ label?: string;
6
+ }
7
+ type WikiPart = {
8
+ text: string;
9
+ } | {
10
+ wiki: WikiLinkToken;
11
+ };
12
+ /** Parse the `[[label|target]]` / `[[target]]` inner text (label first, then target
13
+ * — the §13.1 order), or `null` for an empty target (leave the literal `[[…]]`). */
14
+ declare function parseWikiInner(inner: string): WikiLinkToken | null;
15
+ /**
16
+ * Split a text string on its `[[…]]` runs into ordered text / wiki parts, or `null`
17
+ * when there is no usable wiki-link (so the caller keeps the text node untouched).
18
+ */
19
+ declare function splitWikiLinks(value: string): WikiPart[] | null;
20
+
21
+ export { type WikiLinkToken, type WikiPart, parseWikiInner, splitWikiLinks };
@@ -0,0 +1,38 @@
1
+ const WIKILINK = /\[\[([^[\]]+)\]\]/g;
2
+ function parseWikiInner(inner) {
3
+ const pipe = inner.indexOf("|");
4
+ let target;
5
+ let label;
6
+ if (pipe === -1) {
7
+ target = inner.trim();
8
+ } else {
9
+ label = inner.slice(0, pipe).trim();
10
+ target = inner.slice(pipe + 1).trim();
11
+ }
12
+ if (target === "") return null;
13
+ return label ? { target, label } : { target };
14
+ }
15
+ function splitWikiLinks(value) {
16
+ if (value.indexOf("[[") === -1) return null;
17
+ const out = [];
18
+ let lastIndex = 0;
19
+ let produced = false;
20
+ WIKILINK.lastIndex = 0;
21
+ let match;
22
+ while ((match = WIKILINK.exec(value)) !== null) {
23
+ const token = parseWikiInner(match[1]);
24
+ if (!token) continue;
25
+ if (match.index > lastIndex) out.push({ text: value.slice(lastIndex, match.index) });
26
+ out.push({ wiki: token });
27
+ lastIndex = match.index + match[0].length;
28
+ produced = true;
29
+ }
30
+ if (!produced) return null;
31
+ if (lastIndex < value.length) out.push({ text: value.slice(lastIndex) });
32
+ return out;
33
+ }
34
+ export {
35
+ parseWikiInner,
36
+ splitWikiLinks
37
+ };
38
+ //# sourceMappingURL=wikilink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/safeContent/wikilink.ts"],"sourcesContent":["// Wikilink parsing for the safe content renderer (TRUST_MODES_SPEC §5.1). A §5.1\n// residual: wikilinks resolve **only within the granted mount, never a fetch**. The\n// kernel's executable pipeline compiles `[[target]]`/`[[label|target]]` to a\n// `<WikiLink>` component (transpiler `remarkWikiLinks`, MARKDOWN_SYNTAX_SPEC §13); the\n// SAFE renderer parses raw source, so it splits `[[…]]` out of text itself with the\n// same byte-local rule and hands the raw target/label to an injected, **in-mount**\n// resolver. The renderer never fetches; resolution is the consumer's mount-scoped\n// callback (or, absent one, the link renders as inert text).\n\n// Inner content up to the closing `]]`, forbidding `[`/`]` so nested brackets can't\n// be swallowed. Global — a text run may hold several.\nconst WIKILINK = /\\[\\[([^[\\]]+)\\]\\]/g;\n\nexport interface WikiLinkToken {\n /** The raw target path, verbatim (relative or absolute; resolved in-mount only). */\n target: string;\n /** The explicit label, or undefined (the component derives one). */\n label?: string;\n}\n\nexport type WikiPart = { text: string } | { wiki: WikiLinkToken };\n\n/** Parse the `[[label|target]]` / `[[target]]` inner text (label first, then target\n * — the §13.1 order), or `null` for an empty target (leave the literal `[[…]]`). */\nexport function parseWikiInner(inner: string): WikiLinkToken | null {\n const pipe = inner.indexOf('|');\n let target: string;\n let label: string | undefined;\n if (pipe === -1) {\n target = inner.trim();\n } else {\n label = inner.slice(0, pipe).trim();\n target = inner.slice(pipe + 1).trim();\n }\n if (target === '') return null;\n return label ? { target, label } : { target };\n}\n\n/**\n * Split a text string on its `[[…]]` runs into ordered text / wiki parts, or `null`\n * when there is no usable wiki-link (so the caller keeps the text node untouched).\n */\nexport function splitWikiLinks(value: string): WikiPart[] | null {\n if (value.indexOf('[[') === -1) return null;\n const out: WikiPart[] = [];\n let lastIndex = 0;\n let produced = false;\n WIKILINK.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = WIKILINK.exec(value)) !== null) {\n const token = parseWikiInner(match[1]);\n if (!token) continue; // empty target → keep the literal `[[…]]`\n if (match.index > lastIndex) out.push({ text: value.slice(lastIndex, match.index) });\n out.push({ wiki: token });\n lastIndex = match.index + match[0].length;\n produced = true;\n }\n if (!produced) return null;\n if (lastIndex < value.length) out.push({ text: value.slice(lastIndex) });\n return out;\n}\n"],"mappings":"AAWA,MAAM,WAAW;AAaV,SAAS,eAAe,OAAqC;AAClE,QAAM,OAAO,MAAM,QAAQ,GAAG;AAC9B,MAAI;AACJ,MAAI;AACJ,MAAI,SAAS,IAAI;AACf,aAAS,MAAM,KAAK;AAAA,EACtB,OAAO;AACL,YAAQ,MAAM,MAAM,GAAG,IAAI,EAAE,KAAK;AAClC,aAAS,MAAM,MAAM,OAAO,CAAC,EAAE,KAAK;AAAA,EACtC;AACA,MAAI,WAAW,GAAI,QAAO;AAC1B,SAAO,QAAQ,EAAE,QAAQ,MAAM,IAAI,EAAE,OAAO;AAC9C;AAMO,SAAS,eAAe,OAAkC;AAC/D,MAAI,MAAM,QAAQ,IAAI,MAAM,GAAI,QAAO;AACvC,QAAM,MAAkB,CAAC;AACzB,MAAI,YAAY;AAChB,MAAI,WAAW;AACf,WAAS,YAAY;AACrB,MAAI;AACJ,UAAQ,QAAQ,SAAS,KAAK,KAAK,OAAO,MAAM;AAC9C,UAAM,QAAQ,eAAe,MAAM,CAAC,CAAC;AACrC,QAAI,CAAC,MAAO;AACZ,QAAI,MAAM,QAAQ,UAAW,KAAI,KAAK,EAAE,MAAM,MAAM,MAAM,WAAW,MAAM,KAAK,EAAE,CAAC;AACnF,QAAI,KAAK,EAAE,MAAM,MAAM,CAAC;AACxB,gBAAY,MAAM,QAAQ,MAAM,CAAC,EAAE;AACnC,eAAW;AAAA,EACb;AACA,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI,YAAY,MAAM,OAAQ,KAAI,KAAK,EAAE,MAAM,MAAM,MAAM,SAAS,EAAE,CAAC;AACvE,SAAO;AACT;","names":[]}