@render-policy/a2ui 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 render-policy contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,85 @@
1
- # Temporary Holding Version
1
+ # @render-policy/a2ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The render policy for [A2UI](https://a2ui.org) surfaces (specification v0.9 and v0.9.1). An A2UI
4
+ agent sends no HTML: it describes components from a catalog, and your client renders them with its
5
+ own widgets. Sanitization is no longer the question. Where the agent's data may flow still is.
6
+
7
+ In the basic catalog these places matter:
8
+
9
+ | Place | What happens without a policy |
10
+ | --- | --- |
11
+ | `Image.url`, `Video.url`, `AudioPlayer.url`, `theme.iconUrl` | the browser requests the URL as soon as it renders, without a click: whatever the agent put in the URL leaves |
12
+ | `openUrl` (button actions) | navigation; the catalog requires http and https only and `noopener,noreferrer` |
13
+ | `Text.text` | Markdown; the catalog says "without HTML, images, or links", but that is guidance to the agent, not a guarantee |
14
+
15
+ Nearly every property is dynamic: a literal, a `{ "path": "/…" }` binding into the data model, or a
16
+ function call such as `formatString` (`"https://cdn.example/${/user/token}.png"`). The value that
17
+ matters exists only after binding and changes with every `updateDataModel`. Checking the message JSON
18
+ is not enough: the check has to run on the resolved value, when the renderer uses it.
19
+
20
+ ```ts
21
+ import { createA2uiGuard } from '@render-policy/a2ui';
22
+
23
+ const guard = createA2uiGuard({
24
+ policy: { images: { hosts: ['cdn.example.com'] } },
25
+ onDecision: (d) => audit.push(d),
26
+ });
27
+
28
+ // In your Image component, after the binding resolved:
29
+ const result = guard.url('image', resolvedUrl, { surfaceId, componentId });
30
+ if (result.allowed) img.src = result.value; // use result.value: the query string may be stripped, a proxy applied
31
+ else showPlaceholder(result.decision.reason);
32
+
33
+ // The basic catalog's openUrl function:
34
+ guard.openUrl(resolvedArgs.url); // opens with noopener,noreferrer only when allowed
35
+
36
+ // Text: inert fragment, no HTML, images or links
37
+ guard.renderText(element, resolvedText, { componentId });
38
+ ```
39
+
40
+ What the guard does:
41
+
42
+ - `url(kind, value)` for `image`, `video`, `audio`, `icon` and `openUrl` runs the URL policy of
43
+ [`@render-policy/core`](../core) on the resolved value: scheme allowlist, sink denylist,
44
+ `urls.decide`, and for everything fetched without a click the image policy (host allowlist, query
45
+ string handling, length and entropy heuristics, `images.rewriteUrl`). Video and audio players
46
+ request on their own, so they follow the image rules. A value that is not a non-empty string (an
47
+ unresolved binding, a number, an object) is refused with `a2ui-not-a-url`.
48
+ - `openUrl(value)` allows only http and https (the catalog's requirement, on top of the policy),
49
+ resolves relative targets against the page and opens with `noopener,noreferrer`. Pass `open` to
50
+ route it through your own navigation.
51
+ - `text(value)` / `renderText(target, value)` hold `Text` to the catalog contract: raw HTML is shown
52
+ as text (`a2ui-text-html`), an image becomes its alt text (`a2ui-text-image`), a link its text
53
+ (`a2ui-text-link`). Nothing the agent wrote disappears silently, and nothing is fetched or opened.
54
+ The result is sanitized and inserted with `replaceChildren()`. `text: 'policy'` renders `Text` as
55
+ ordinary Markdown under the policy instead, for a catalog of your own that allows more.
56
+ - Every decision is a journal entry with the core's codes plus the A2UI place (`a2ui.kind`,
57
+ `surfaceId`, `componentId`). Application hooks that throw become entries; the guard never throws.
58
+
59
+ ## The A2UI renderers' Markdown plug-in
60
+
61
+ The A2UI web renderers (React, Lit, Angular) take Markdown through a plug-in,
62
+ `(markdown, options) => Promise<string>`, and insert the returned string themselves.
63
+ `createA2uiMarkdownRenderer()` fits that contract with the strict `Text` mode, `tagClassMap` and
64
+ `renderMode: 'inline'`:
65
+
66
+ ```ts
67
+ import { createA2uiMarkdownRenderer } from '@render-policy/a2ui';
68
+
69
+ const markdown = createA2uiMarkdownRenderer({ guard }); // share the guard with the rest of the surface
70
+ // React: <MarkdownContext.Provider value={markdown}>; Angular: provideMarkdownRenderer(markdown)
71
+ ```
72
+
73
+ This is the compatibility path, and it is weaker than a fragment: the HTML is serialized here and
74
+ parsed again by the renderer. Where you render `Text` yourself, use `guard.renderText()`.
75
+
76
+ ## Proofs
77
+
78
+ `test/` checks the guard in jsdom. `e2e/run.mjs` runs an A2UI v0.9 surface in Chromium: image,
79
+ video and icon URLs arrive literally, through `updateDataModel` and through `formatString`; `Text`
80
+ carries Markdown images, raw HTML and links; a button calls `openUrl('javascript:…')`. With the guard,
81
+ no request reaches a host outside the allowlist, the token assembled by `formatString` never leaves,
82
+ and nothing opens. The same messages without the guard are the control: they make those requests.
83
+
84
+ Not covered yet: a static scan of A2UI messages for literal hazards (planned as `mcp-app-lint`
85
+ rules), and A2UI cases in the evil-Markdown corpus. A2UI v1.0 follows when that version is stable.
@@ -0,0 +1,89 @@
1
+ import { type RenderDecision, type RenderMode, type RenderPolicyOverrides, type RenderTarget, type RenderWindow, type SinkDenylist } from '@render-policy/core';
2
+ /**
3
+ * Where an A2UI value is used, by the catalog's names:
4
+ *
5
+ * - `image`: `Image.url`
6
+ * - `video`: `Video.url`
7
+ * - `audio`: `AudioPlayer.url`
8
+ * - `icon`: `theme.iconUrl` in `createSurface` (an image next to the surface)
9
+ * - `openUrl`: `args.url` of the `openUrl` function (navigation)
10
+ *
11
+ * The first four are fetched without a click and go through the image policy; video and audio
12
+ * players request metadata on their own, so they are held to the same rule as images.
13
+ */
14
+ export type A2uiUrlKind = 'image' | 'video' | 'audio' | 'icon' | 'openUrl';
15
+ /** Optional location of a value, copied into its journal entries. */
16
+ export interface A2uiWhere {
17
+ readonly surfaceId?: string;
18
+ readonly componentId?: string;
19
+ }
20
+ /** A journal entry with the A2UI place it came from. */
21
+ export interface A2uiDecision extends RenderDecision {
22
+ readonly a2ui: A2uiWhere & {
23
+ readonly kind: A2uiUrlKind | 'text';
24
+ };
25
+ }
26
+ export type A2uiUrlResult = {
27
+ readonly allowed: true;
28
+ readonly value: string;
29
+ readonly decisions: readonly A2uiDecision[];
30
+ } | {
31
+ readonly allowed: false;
32
+ readonly decision: A2uiDecision;
33
+ readonly decisions: readonly A2uiDecision[];
34
+ };
35
+ export interface A2uiTextResult {
36
+ readonly fragment: DocumentFragment;
37
+ readonly decisions: readonly A2uiDecision[];
38
+ }
39
+ export interface A2uiTextOptions extends A2uiWhere {
40
+ /** Render without block wrappers (no `<p>`), for a label. Default false. */
41
+ readonly inline?: boolean;
42
+ }
43
+ export interface A2uiGuardOptions {
44
+ /** The window to render with and resolve relative URLs against. Defaults to the global window. */
45
+ readonly window?: RenderWindow;
46
+ /** Policy preset. Default 'balanced': no remote image host until you list it. */
47
+ readonly mode?: RenderMode;
48
+ /** Overrides applied on top of the preset, as in `createRenderer`. */
49
+ readonly policy?: RenderPolicyOverrides;
50
+ /** Replace the bundled sink denylist. */
51
+ readonly sinkDenylist?: SinkDenylist;
52
+ /**
53
+ * `strict` (default) holds `Text` to the basic catalog's contract: Markdown without HTML, images or
54
+ * links. `policy` renders `Text` like any other Markdown under the policy, for a catalog of your own
55
+ * that allows more.
56
+ */
57
+ readonly text?: 'strict' | 'policy';
58
+ /** Opens an allowed `openUrl` target. Default: `window.open(url, '_blank', 'noopener,noreferrer')`. */
59
+ readonly open?: (url: string) => void;
60
+ /** Receive every decision as it is made. */
61
+ readonly onDecision?: (decision: A2uiDecision) => void;
62
+ }
63
+ export interface A2uiGuard {
64
+ /**
65
+ * Check a resolved value right before the renderer uses it: after data binding and function calls
66
+ * (`{ "path": "/img" }`, `formatString`), on every data-model update. Use the returned `value`, not
67
+ * the one passed in: it may be rewritten (query string stripped, image proxy). Never throws.
68
+ */
69
+ url(kind: A2uiUrlKind, value: unknown, where?: A2uiWhere): A2uiUrlResult;
70
+ /**
71
+ * The basic catalog's `openUrl`: checks the target (http and https only, as the catalog requires,
72
+ * plus the policy) and opens it with `noopener,noreferrer` only when it is allowed.
73
+ */
74
+ openUrl(value: unknown, where?: A2uiWhere): A2uiUrlResult;
75
+ /** `Text.text` as an inert fragment. Non-string values are rendered as their string form. */
76
+ text(value: unknown, options?: A2uiTextOptions): A2uiTextResult;
77
+ /** `Text.text` inserted into `target` with `replaceChildren()`. */
78
+ renderText(target: RenderTarget, value: unknown, options?: A2uiTextOptions): {
79
+ readonly decisions: readonly A2uiDecision[];
80
+ };
81
+ }
82
+ /**
83
+ * The render policy for an A2UI surface. A2UI sends no HTML, so sanitization is no longer the
84
+ * question; where the agent's data may flow still is. Every URL a component property or function
85
+ * argument carries is checked on its resolved value, with the same policy, sink denylist and journal
86
+ * codes as `@render-policy/core`, and `Text` is held to the catalog's "no HTML, images or links".
87
+ */
88
+ export declare function createA2uiGuard(options?: A2uiGuardOptions): A2uiGuard;
89
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,YAAY,EAEjB,KAAK,YAAY,EAGlB,MAAM,qBAAqB,CAAC;AAG7B;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAE3E,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,wDAAwD;AACxD,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG;QAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAAA;KAAE,CAAC;CACpE;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,YAAY,EAAE,CAAA;CAAE,GAC/F;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,YAAY,EAAE,CAAA;CAAE,CAAC;AAE9G,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,SAAS,YAAY,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,kGAAkG;IAClG,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IACxC,yCAAyC;IACzC,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACpC,uGAAuG;IACvG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,4CAA4C;IAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;CACxD;AAED,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;IACzE;;;OAGG;IACH,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;IAC1D,6FAA6F;IAC7F,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,cAAc,CAAC;IAChE,mEAAmE;IACnE,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG;QAAE,QAAQ,CAAC,SAAS,EAAE,SAAS,YAAY,EAAE,CAAA;KAAE,CAAC;CAC9H;AAuBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS,CAqFzE"}
package/dist/guard.js ADDED
@@ -0,0 +1,115 @@
1
+ import { createRenderer, createUrlGuard, resolvePolicy, resolveWindow, } from '@render-policy/core';
2
+ import { createStrictMarkdown } from './text.js';
3
+ const REQUESTS = {
4
+ image: { subject: 'image', tag: 'img', attribute: 'src' },
5
+ icon: { subject: 'image', tag: 'img', attribute: 'src' },
6
+ video: { subject: 'image', tag: 'video', attribute: 'src' },
7
+ audio: { subject: 'image', tag: 'audio', attribute: 'src' },
8
+ openUrl: { subject: 'link', tag: 'a', attribute: 'href' },
9
+ };
10
+ /** The catalog: "Strictly validate that the resolved URL protocol/scheme is either https: or http:". */
11
+ const OPEN_URL_SCHEMES = ['http', 'https'];
12
+ /** No form controls, SVG, media, images or links in `Text`, whatever the policy allows elsewhere. */
13
+ const strictContent = (content) => ({
14
+ ...content,
15
+ allowImages: false,
16
+ allowForms: false,
17
+ allowSvg: false,
18
+ allowMedia: false,
19
+ forbidTags: [...(content?.forbidTags ?? []), 'a', 'img'],
20
+ });
21
+ /**
22
+ * The render policy for an A2UI surface. A2UI sends no HTML, so sanitization is no longer the
23
+ * question; where the agent's data may flow still is. Every URL a component property or function
24
+ * argument carries is checked on its resolved value, with the same policy, sink denylist and journal
25
+ * codes as `@render-policy/core`, and `Text` is held to the catalog's "no HTML, images or links".
26
+ */
27
+ export function createA2uiGuard(options = {}) {
28
+ const win = resolveWindow(options.window, '@render-policy/a2ui');
29
+ const { mode, policy = {}, sinkDenylist } = options;
30
+ const onDecision = options.onDecision;
31
+ const urlGuard = createUrlGuard({ window: win, mode, policy, sinkDenylist });
32
+ const openUrlSchemes = resolvePolicy(mode, policy).urls.allowedSchemes.filter((s) => OPEN_URL_SCHEMES.includes(s));
33
+ const openUrlGuard = createUrlGuard({ window: win, mode, policy: { ...policy, urls: { ...policy.urls, allowedSchemes: openUrlSchemes } }, sinkDenylist });
34
+ const open = options.open ??
35
+ ((url) => {
36
+ const opener = win.open;
37
+ opener?.call(win, url, '_blank', 'noopener,noreferrer');
38
+ });
39
+ const located = (decisions, kind, where) => decisions.map((d) => ({ ...d, a2ui: { ...where, kind } }));
40
+ const emit = (decisions) => {
41
+ if (onDecision)
42
+ for (const d of decisions)
43
+ onDecision(d);
44
+ };
45
+ const check = (guard, kind, value, where) => {
46
+ const request = REQUESTS[kind];
47
+ if (typeof value !== 'string' || value.trim() === '') {
48
+ // A binding that resolved to nothing usable: there is no URL to check, so there is none to use.
49
+ const [decision] = located([{ kind: 'blocked', subject: request.subject, code: 'a2ui-not-a-url', reason: `expected a non-empty string, got ${describe(value)}`, tag: request.tag, attribute: request.attribute }], kind, where);
50
+ emit([decision]);
51
+ return { allowed: false, decision, decisions: [] };
52
+ }
53
+ const result = guard(value, request);
54
+ const decisions = located(result.decisions, kind, where);
55
+ if (result.allowed) {
56
+ emit(decisions);
57
+ return { allowed: true, value: result.value, decisions };
58
+ }
59
+ const [decision] = located([result.decision], kind, where);
60
+ emit([...decisions, decision]);
61
+ return { allowed: false, decision, decisions };
62
+ };
63
+ // One renderer per text shape, created on first use.
64
+ const textRenderers = new Map();
65
+ const textRenderer = (inline) => {
66
+ let entry = textRenderers.get(inline);
67
+ if (!entry) {
68
+ if (options.text === 'policy') {
69
+ entry = { renderer: createRenderer({ window: win, mode, policy, sinkDenylist }), markdown: null };
70
+ }
71
+ else {
72
+ const markdown = createStrictMarkdown(inline);
73
+ const renderer = createRenderer({ window: win, mode, policy: { ...policy, content: strictContent(policy.content) }, sinkDenylist, markdown });
74
+ entry = { renderer, markdown };
75
+ }
76
+ textRenderers.set(inline, entry);
77
+ }
78
+ return entry;
79
+ };
80
+ const text = (value, textOptions = {}) => {
81
+ const { inline = false, ...where } = textOptions;
82
+ const source = typeof value === 'string' ? value : value === null || value === undefined ? '' : String(value);
83
+ const { renderer, markdown } = textRenderer(inline);
84
+ const { fragment, decisions: rendered } = renderer.markdownToFragment(source);
85
+ // Markdown-level decisions come first: they happened first.
86
+ const decisions = located([...(markdown?.decisions ?? []), ...rendered], 'text', where);
87
+ emit(decisions);
88
+ return { fragment, decisions };
89
+ };
90
+ return {
91
+ url: (kind, value, where) => check(kind === 'openUrl' ? openUrlGuard : urlGuard, kind, value, where),
92
+ openUrl(value, where) {
93
+ const result = check(openUrlGuard, 'openUrl', value, where);
94
+ if (result.allowed)
95
+ open(result.value);
96
+ return result;
97
+ },
98
+ text,
99
+ renderText(target, value, textOptions) {
100
+ const { fragment, decisions } = text(value, textOptions);
101
+ target.replaceChildren(fragment);
102
+ return { decisions };
103
+ },
104
+ };
105
+ }
106
+ function describe(value) {
107
+ if (value === null)
108
+ return 'null';
109
+ if (Array.isArray(value))
110
+ return 'an array';
111
+ if (typeof value === 'string')
112
+ return 'an empty string';
113
+ return typeof value === 'object' ? 'an object' : `a ${typeof value}`;
114
+ }
115
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { createA2uiGuard } from './guard.js';
2
+ export type { A2uiDecision, A2uiGuard, A2uiGuardOptions, A2uiTextOptions, A2uiTextResult, A2uiUrlKind, A2uiUrlResult, A2uiWhere } from './guard.js';
3
+ export { createA2uiMarkdownRenderer } from './markdown-plugin.js';
4
+ export type { A2uiMarkdownOptions, A2uiMarkdownRenderer, A2uiMarkdownRendererOptions } from './markdown-plugin.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEpJ,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { createA2uiGuard } from './guard.js';
2
+ export { createA2uiMarkdownRenderer } from './markdown-plugin.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAG7C,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { type A2uiGuard, type A2uiGuardOptions } from './guard.js';
2
+ /** The options the A2UI web renderers pass to a Markdown plug-in (`MarkdownRendererOptions` in their web core). */
3
+ export interface A2uiMarkdownOptions {
4
+ /** Class names to put on rendered elements, by tag name. */
5
+ readonly tagClassMap?: Readonly<Record<string, readonly string[]>>;
6
+ /** `inline` renders without block wrappers. */
7
+ readonly renderMode?: 'inline' | 'block';
8
+ }
9
+ /** The A2UI renderers' Markdown plug-in contract: Markdown in, an HTML string out. */
10
+ export type A2uiMarkdownRenderer = (markdown: string, options?: A2uiMarkdownOptions) => Promise<string>;
11
+ export interface A2uiMarkdownRendererOptions extends A2uiGuardOptions {
12
+ /** Use this guard instead of creating one from the options above. */
13
+ readonly guard?: A2uiGuard;
14
+ }
15
+ /**
16
+ * A Markdown plug-in for the A2UI web renderers (React, Lit, Angular), which take
17
+ * `(markdown, options) => Promise<string>` and insert the string themselves. `Text` gets the strict
18
+ * text mode: no HTML, images or links, sanitized, with the journal through `onDecision`.
19
+ *
20
+ * This is the compatibility path and it is weaker than a fragment: the HTML is serialized here and
21
+ * parsed again by the renderer, so what reaches the page depends on how the renderer inserts the
22
+ * string. Where you render `Text` yourself, use `guard.renderText()`, which inserts a sanitized fragment.
23
+ */
24
+ export declare function createA2uiMarkdownRenderer(options?: A2uiMarkdownRendererOptions): A2uiMarkdownRenderer;
25
+ //# sourceMappingURL=markdown-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-plugin.d.ts","sourceRoot":"","sources":["../src/markdown-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEpF,mHAAmH;AACnH,MAAM,WAAW,mBAAmB;IAClC,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;IACnE,+CAA+C;IAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;CAC1C;AAED,sFAAsF;AACtF,MAAM,MAAM,oBAAoB,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAExG,MAAM,WAAW,2BAA4B,SAAQ,gBAAgB;IACnE,qEAAqE;IACrE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,2BAAgC,GAAG,oBAAoB,CAc1G"}
@@ -0,0 +1,28 @@
1
+ import { createA2uiGuard } from './guard.js';
2
+ /**
3
+ * A Markdown plug-in for the A2UI web renderers (React, Lit, Angular), which take
4
+ * `(markdown, options) => Promise<string>` and insert the string themselves. `Text` gets the strict
5
+ * text mode: no HTML, images or links, sanitized, with the journal through `onDecision`.
6
+ *
7
+ * This is the compatibility path and it is weaker than a fragment: the HTML is serialized here and
8
+ * parsed again by the renderer, so what reaches the page depends on how the renderer inserts the
9
+ * string. Where you render `Text` yourself, use `guard.renderText()`, which inserts a sanitized fragment.
10
+ */
11
+ export function createA2uiMarkdownRenderer(options = {}) {
12
+ const guard = options.guard ?? createA2uiGuard(options);
13
+ return async (markdown, renderOptions) => {
14
+ const { fragment } = guard.text(markdown, { inline: renderOptions?.renderMode === 'inline' });
15
+ // Classes come from the application's configuration and are added after sanitization.
16
+ for (const [tag, classes] of Object.entries(renderOptions?.tagClassMap ?? {})) {
17
+ if (!/^[a-z][a-z0-9]*$/i.test(tag) || classes.length === 0)
18
+ continue;
19
+ for (const element of fragment.querySelectorAll(tag))
20
+ element.classList.add(...classes);
21
+ }
22
+ // Serialize in the fragment's own document: the guard holds the window, the plug-in needs none.
23
+ const holder = fragment.ownerDocument.createElement('template');
24
+ holder.content.append(fragment);
25
+ return holder.innerHTML;
26
+ };
27
+ }
28
+ //# sourceMappingURL=markdown-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-plugin.js","sourceRoot":"","sources":["../src/markdown-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAyC,MAAM,YAAY,CAAC;AAkBpF;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAAO,GAAgC,EAAE;IAClF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,eAAe,CAAC,OAAO,CAAC,CAAC;IACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE;QACvC,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,KAAK,QAAQ,EAAE,CAAC,CAAC;QAC9F,sFAAsF;QACtF,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,EAAE,WAAW,IAAI,EAAE,CAAC,EAAE,CAAC;YAC9E,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YACrE,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC;gBAAE,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;QAC1F,CAAC;QACD,gGAAgG;QAChG,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QAChE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAChC,OAAO,MAAM,CAAC,SAAS,CAAC;IAC1B,CAAC,CAAC;AACJ,CAAC"}
package/dist/text.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { RenderDecision } from '@render-policy/core';
2
+ /** Markdown to HTML that also reports what it took out. */
3
+ export interface StrictMarkdown {
4
+ (markdown: string): string;
5
+ /** Decisions made by the last call; reset on every call. */
6
+ readonly decisions: readonly RenderDecision[];
7
+ }
8
+ /**
9
+ * Markdown for the basic catalog's `Text`: "simple Markdown formatting ... without HTML, images, or
10
+ * links". The catalog states this as guidance to the agent; here it is enforced. Raw HTML is shown as
11
+ * text, an image becomes its alt text and a link its text, so nothing is fetched or navigated to and
12
+ * nothing the agent wrote disappears silently. Each change is a journal entry. The output still goes
13
+ * through the sanitizer, which forbids `a` and `img` on top.
14
+ */
15
+ export declare function createStrictMarkdown(inline: boolean): StrictMarkdown;
16
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,2DAA2D;AAC3D,MAAM,WAAW,cAAc;IAC7B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,CAAC;CAC/C;AAKD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,cAAc,CA2BpE"}
package/dist/text.js ADDED
@@ -0,0 +1,38 @@
1
+ import { Marked } from 'marked';
2
+ const escapeHtml = (text) => text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
3
+ /**
4
+ * Markdown for the basic catalog's `Text`: "simple Markdown formatting ... without HTML, images, or
5
+ * links". The catalog states this as guidance to the agent; here it is enforced. Raw HTML is shown as
6
+ * text, an image becomes its alt text and a link its text, so nothing is fetched or navigated to and
7
+ * nothing the agent wrote disappears silently. Each change is a journal entry. The output still goes
8
+ * through the sanitizer, which forbids `a` and `img` on top.
9
+ */
10
+ export function createStrictMarkdown(inline) {
11
+ let decisions = [];
12
+ const marked = new Marked({
13
+ gfm: true,
14
+ breaks: false,
15
+ async: false,
16
+ renderer: {
17
+ html(token) {
18
+ decisions.push({ kind: 'rewritten', subject: 'markdown', code: 'a2ui-text-html', reason: 'raw HTML in Text shown as text: the catalog allows no HTML', value: token.text });
19
+ return escapeHtml(token.text);
20
+ },
21
+ image(token) {
22
+ decisions.push({ kind: 'blocked', subject: 'image', code: 'a2ui-text-image', reason: 'image in Text replaced by its alt text: the catalog allows no images', tag: 'img', attribute: 'src', value: token.href });
23
+ return escapeHtml(token.text);
24
+ },
25
+ link(token) {
26
+ decisions.push({ kind: 'blocked', subject: 'link', code: 'a2ui-text-link', reason: 'link in Text replaced by its text: the catalog allows no links', tag: 'a', attribute: 'href', value: token.href });
27
+ return this.parser.parseInline(token.tokens);
28
+ },
29
+ },
30
+ });
31
+ const render = (markdown) => {
32
+ decisions = [];
33
+ return inline ? marked.parseInline(markdown, { async: false }) : marked.parse(markdown, { async: false });
34
+ };
35
+ Object.defineProperty(render, 'decisions', { get: () => decisions });
36
+ return render;
37
+ }
38
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAUhC,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE,CAC1C,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAElG;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAe;IAClD,IAAI,SAAS,GAAqB,EAAE,CAAC;IACrC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;QACxB,GAAG,EAAE,IAAI;QACT,MAAM,EAAE,KAAK;QACb,KAAK,EAAE,KAAK;QACZ,QAAQ,EAAE;YACR,IAAI,CAAC,KAAK;gBACR,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,4DAA4D,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC5K,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAChC,CAAC;YACD,KAAK,CAAC,KAAK;gBACT,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,EAAE,sEAAsE,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;gBAChN,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAChC,CAAC;YACD,IAAI,CAAC,KAAK;gBACR,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,gEAAgE,EAAE,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;gBACvM,OAAO,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC/C,CAAC;SACF;KACF,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,CAAC,QAAgB,EAAU,EAAE;QAC1C,SAAS,GAAG,EAAE,CAAC;QACf,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5G,CAAC,CAAC;IACF,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC;IACrE,OAAO,MAAwB,CAAC;AAClC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "@render-policy/a2ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.5",
4
+ "description": "Render-policy checks for A2UI (v0.9) surfaces: image, media, icon and openUrl values checked after data binding, and the catalog's Text contract (no HTML, images or links) enforced.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "sideEffects": false,
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.build.json",
23
+ "typecheck": "tsc -p tsconfig.json --noEmit",
24
+ "test": "vitest run"
25
+ },
26
+ "dependencies": {
27
+ "marked": "^18.0.14"
28
+ },
29
+ "peerDependencies": {
30
+ "@render-policy/core": "^0.1.5"
31
+ },
32
+ "devDependencies": {
33
+ "@render-policy/core": "^0.1.5"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "keywords": [
39
+ "a2ui",
40
+ "agent",
41
+ "ui",
42
+ "sanitize",
43
+ "xss",
44
+ "exfiltration",
45
+ "markdown",
46
+ "llm",
47
+ "security"
48
+ ],
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/shteynu/render-policy.git",
52
+ "directory": "packages/a2ui"
53
+ }
54
+ }