@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.
Files changed (73) hide show
  1. package/dist/assert.d.ts +19 -0
  2. package/dist/assert.d.ts.map +1 -0
  3. package/dist/assert.js +29 -0
  4. package/dist/assert.js.map +1 -0
  5. package/dist/elision-state.d.ts.map +1 -1
  6. package/dist/elision-state.js +6 -0
  7. package/dist/elision-state.js.map +1 -1
  8. package/dist/events.d.ts +33 -1
  9. package/dist/events.d.ts.map +1 -1
  10. package/dist/fs-utils.d.ts +8 -0
  11. package/dist/fs-utils.d.ts.map +1 -1
  12. package/dist/fs-utils.js +29 -0
  13. package/dist/fs-utils.js.map +1 -1
  14. package/dist/index.d.ts +11 -3
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +7 -2
  17. package/dist/index.js.map +1 -1
  18. package/dist/mode-helpers.d.ts +12 -0
  19. package/dist/mode-helpers.d.ts.map +1 -1
  20. package/dist/mode-helpers.js +105 -3
  21. package/dist/mode-helpers.js.map +1 -1
  22. package/dist/mode.d.ts +9 -0
  23. package/dist/mode.d.ts.map +1 -1
  24. package/dist/mode.js.map +1 -1
  25. package/dist/plugin.d.ts +10 -1
  26. package/dist/plugin.d.ts.map +1 -1
  27. package/dist/provider-login-bridge.d.ts +53 -0
  28. package/dist/provider-login-bridge.d.ts.map +1 -0
  29. package/dist/provider-login-bridge.js +95 -0
  30. package/dist/provider-login-bridge.js.map +1 -0
  31. package/dist/provider.d.ts +53 -0
  32. package/dist/provider.d.ts.map +1 -1
  33. package/dist/semver.d.ts +16 -0
  34. package/dist/semver.d.ts.map +1 -0
  35. package/dist/semver.js +28 -0
  36. package/dist/semver.js.map +1 -0
  37. package/dist/subagent.d.ts +6 -0
  38. package/dist/subagent.d.ts.map +1 -1
  39. package/dist/surface.d.ts +159 -0
  40. package/dist/surface.d.ts.map +1 -0
  41. package/dist/surface.js +27 -0
  42. package/dist/surface.js.map +1 -0
  43. package/dist/tool-display.d.ts +73 -0
  44. package/dist/tool-display.d.ts.map +1 -0
  45. package/dist/tool-display.js +66 -0
  46. package/dist/tool-display.js.map +1 -0
  47. package/dist/view-renderer.d.ts +6 -0
  48. package/dist/view-renderer.d.ts.map +1 -1
  49. package/dist/view-renderer.js +18 -1
  50. package/dist/view-renderer.js.map +1 -1
  51. package/package.json +7 -3
  52. package/src/assert.test.ts +35 -0
  53. package/src/assert.ts +28 -0
  54. package/src/elision-state.ts +5 -0
  55. package/src/events.ts +36 -0
  56. package/src/fs-utils.test.ts +47 -1
  57. package/src/fs-utils.ts +31 -0
  58. package/src/index.ts +51 -2
  59. package/src/loop-helpers.test.ts +40 -0
  60. package/src/mode-helpers.ts +114 -3
  61. package/src/mode.ts +7 -0
  62. package/src/plugin.ts +10 -1
  63. package/src/provider-login-bridge.test.ts +78 -0
  64. package/src/provider-login-bridge.ts +105 -0
  65. package/src/provider.ts +45 -2
  66. package/src/semver.test.ts +43 -0
  67. package/src/semver.ts +27 -0
  68. package/src/subagent.ts +6 -0
  69. package/src/surface.ts +173 -0
  70. package/src/tool-display.test.ts +71 -0
  71. package/src/tool-display.ts +118 -0
  72. package/src/view-renderer.test.ts +57 -0
  73. 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
+ });
@@ -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
- const u = url.trim().toLowerCase();
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
+ }