@moxxy/sdk 0.11.0 → 0.13.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.
- package/dist/assert.d.ts +19 -0
- package/dist/assert.d.ts.map +1 -0
- package/dist/assert.js +29 -0
- package/dist/assert.js.map +1 -0
- package/dist/elision-state.d.ts.map +1 -1
- package/dist/elision-state.js +6 -0
- package/dist/elision-state.js.map +1 -1
- package/dist/events.d.ts +33 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/fs-utils.d.ts +8 -0
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +29 -0
- package/dist/fs-utils.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/mode-helpers.d.ts +12 -0
- package/dist/mode-helpers.d.ts.map +1 -1
- package/dist/mode-helpers.js +105 -3
- package/dist/mode-helpers.js.map +1 -1
- package/dist/mode.d.ts +9 -0
- package/dist/mode.d.ts.map +1 -1
- package/dist/mode.js.map +1 -1
- package/dist/plugin.d.ts +10 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/provider-login-bridge.d.ts +53 -0
- package/dist/provider-login-bridge.d.ts.map +1 -0
- package/dist/provider-login-bridge.js +95 -0
- package/dist/provider-login-bridge.js.map +1 -0
- package/dist/provider.d.ts +53 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/semver.d.ts +16 -0
- package/dist/semver.d.ts.map +1 -0
- package/dist/semver.js +28 -0
- package/dist/semver.js.map +1 -0
- package/dist/subagent.d.ts +6 -0
- package/dist/subagent.d.ts.map +1 -1
- package/dist/surface.d.ts +159 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +27 -0
- package/dist/surface.js.map +1 -0
- package/dist/tool-display.d.ts +73 -0
- package/dist/tool-display.d.ts.map +1 -0
- package/dist/tool-display.js +66 -0
- package/dist/tool-display.js.map +1 -0
- package/dist/view-renderer.d.ts +6 -0
- package/dist/view-renderer.d.ts.map +1 -1
- package/dist/view-renderer.js +18 -1
- package/dist/view-renderer.js.map +1 -1
- package/package.json +7 -3
- package/src/assert.test.ts +35 -0
- package/src/assert.ts +28 -0
- package/src/elision-state.ts +5 -0
- package/src/events.ts +36 -0
- package/src/fs-utils.test.ts +47 -1
- package/src/fs-utils.ts +31 -0
- package/src/index.ts +51 -2
- package/src/loop-helpers.test.ts +40 -0
- package/src/mode-helpers.ts +114 -3
- package/src/mode.ts +7 -0
- package/src/plugin.ts +10 -1
- package/src/provider-login-bridge.test.ts +78 -0
- package/src/provider-login-bridge.ts +105 -0
- package/src/provider.ts +45 -2
- package/src/semver.test.ts +43 -0
- package/src/semver.ts +27 -0
- package/src/subagent.ts +6 -0
- package/src/surface.ts +173 -0
- package/src/tool-display.test.ts +71 -0
- package/src/tool-display.ts +118 -0
- package/src/view-renderer.test.ts +57 -0
- package/src/view-renderer.ts +18 -1
|
@@ -3,11 +3,38 @@ import {
|
|
|
3
3
|
VIEW_PRIMITIVES,
|
|
4
4
|
VIEW_COMPONENTS,
|
|
5
5
|
DEFAULT_VIEW_TAGS,
|
|
6
|
+
countNodes,
|
|
7
|
+
isSafeViewUrl,
|
|
6
8
|
defineViewRenderer,
|
|
7
9
|
defineTunnelProvider,
|
|
10
|
+
type ViewNode,
|
|
8
11
|
type ViewTagSpec,
|
|
9
12
|
} from './index.js';
|
|
10
13
|
|
|
14
|
+
describe('countNodes', () => {
|
|
15
|
+
const text = (value: string): ViewNode => ({ kind: 'text', value });
|
|
16
|
+
const el = (tag: string, children: ViewNode[] = []): ViewNode => ({
|
|
17
|
+
kind: 'element',
|
|
18
|
+
tag,
|
|
19
|
+
props: {},
|
|
20
|
+
children,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('counts a single text node as 1', () => {
|
|
24
|
+
expect(countNodes(text('hi'))).toBe(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('counts an element plus its descendants', () => {
|
|
28
|
+
const tree = el('col', [el('row', [text('a'), text('b')]), text('c')]);
|
|
29
|
+
// col + row + a + b + c = 5
|
|
30
|
+
expect(countNodes(tree)).toBe(5);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('counts a leaf element as 1', () => {
|
|
34
|
+
expect(countNodes(el('hr'))).toBe(1);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
11
38
|
describe('view vocabulary integrity', () => {
|
|
12
39
|
const all = DEFAULT_VIEW_TAGS;
|
|
13
40
|
|
|
@@ -88,3 +115,33 @@ describe('define factories freeze their specs', () => {
|
|
|
88
115
|
expect(Object.isFrozen(def)).toBe(true);
|
|
89
116
|
});
|
|
90
117
|
});
|
|
118
|
+
|
|
119
|
+
describe('isSafeViewUrl', () => {
|
|
120
|
+
it('rejects javascript/vbscript/data-text schemes', () => {
|
|
121
|
+
expect(isSafeViewUrl('javascript:alert(1)', 'href')).toBe(false);
|
|
122
|
+
expect(isSafeViewUrl('vbscript:msgbox', 'href')).toBe(false);
|
|
123
|
+
expect(isSafeViewUrl('data:text/html,<x>', 'src')).toBe(false);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('rejects schemes split by whitespace/control chars (audit u72-1)', () => {
|
|
127
|
+
// a browser strips tab/newline/CR before scheme resolution, so these
|
|
128
|
+
// collapse to javascript: on click; the gate must normalize the same way
|
|
129
|
+
expect(isSafeViewUrl('java\tscript:alert(1)', 'href')).toBe(false);
|
|
130
|
+
expect(isSafeViewUrl('java\nscript:alert(1)', 'href')).toBe(false);
|
|
131
|
+
expect(isSafeViewUrl('java\rscript:alert(1)', 'href')).toBe(false);
|
|
132
|
+
expect(isSafeViewUrl('\u0000javascript:alert(1)', 'href')).toBe(false);
|
|
133
|
+
expect(isSafeViewUrl(' javascript:alert(1)', 'href')).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('allows safe schemes + relative + data-image src', () => {
|
|
137
|
+
expect(isSafeViewUrl('https://example.com/x', 'href')).toBe(true);
|
|
138
|
+
expect(isSafeViewUrl('http://example.com', 'href')).toBe(true);
|
|
139
|
+
expect(isSafeViewUrl('mailto:a@b.com', 'href')).toBe(true);
|
|
140
|
+
expect(isSafeViewUrl('tel:+1555', 'href')).toBe(true);
|
|
141
|
+
expect(isSafeViewUrl('/relative/path', 'href')).toBe(true);
|
|
142
|
+
expect(isSafeViewUrl('#frag', 'href')).toBe(true);
|
|
143
|
+
expect(isSafeViewUrl(' https://example.com ', 'href')).toBe(true);
|
|
144
|
+
expect(isSafeViewUrl('data:image/png;base64,AAAA', 'src')).toBe(true);
|
|
145
|
+
expect(isSafeViewUrl('data:image/png;base64,AAAA', 'href')).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
});
|
package/src/view-renderer.ts
CHANGED
|
@@ -108,7 +108,14 @@ export interface ViewRendererDef {
|
|
|
108
108
|
* parties, so a `javascript:` link is click-XSS on a shared surface.
|
|
109
109
|
*/
|
|
110
110
|
export function isSafeViewUrl(url: string, attr: string): boolean {
|
|
111
|
-
|
|
111
|
+
// Strip ALL ASCII whitespace + C0 control chars (U+0000–U+0020) anywhere in
|
|
112
|
+
// the URL before the scheme check — the HTML URL parser removes tab/newline/CR
|
|
113
|
+
// before resolving the scheme, so `java\tscript:` would otherwise pass this
|
|
114
|
+
// gate and then execute as `javascript:` on click (audit u72-1). The frontend
|
|
115
|
+
// copy in @moxxy/plugin-channel-web/src/frontend/url-safety.ts is a verbatim
|
|
116
|
+
// lockstep duplicate (the browser bundle can't import this root) — keep both
|
|
117
|
+
// in sync.
|
|
118
|
+
const u = url.replace(/[\u0000-\u0020]/g, '').toLowerCase();
|
|
112
119
|
if (u.startsWith('javascript:') || u.startsWith('vbscript:')) return false;
|
|
113
120
|
if (u.startsWith('data:')) return attr === 'src' && u.startsWith('data:image/');
|
|
114
121
|
if (/^[a-z][a-z0-9+.-]*:/.test(u)) return /^(https?:|mailto:|tel:)/.test(u);
|
|
@@ -176,3 +183,13 @@ export const VIEW_COMPONENTS: ReadonlyArray<ViewTagSpec> = [
|
|
|
176
183
|
|
|
177
184
|
/** The default renderer's full allow-list (primitives + components). */
|
|
178
185
|
export const DEFAULT_VIEW_TAGS: ReadonlyArray<ViewTagSpec> = [...VIEW_PRIMITIVES, ...VIEW_COMPONENTS];
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Count element + text nodes in a view tree (the `nodeCount` a `present_view`
|
|
189
|
+
* tool reports). Single source of truth: core's parser and the plugin-view tool
|
|
190
|
+
* both import this rather than re-implementing the recursion.
|
|
191
|
+
*/
|
|
192
|
+
export function countNodes(node: ViewNode): number {
|
|
193
|
+
if (node.kind === 'text') return 1;
|
|
194
|
+
return 1 + node.children.reduce((sum, child) => sum + countNodes(child), 0);
|
|
195
|
+
}
|