@exponential-at/ui 0.0.0-stage → 0.1.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 (251) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +54 -0
  3. package/README.md +360 -2
  4. package/catalog/a11y.json +668 -0
  5. package/catalog/basic-map.json +104 -0
  6. package/catalog/code.json +58 -0
  7. package/catalog/core.catalog.json +4752 -0
  8. package/catalog/core.schema.json +22667 -0
  9. package/catalog/host.json +64 -0
  10. package/catalog/layout.json +20 -0
  11. package/catalog/limits.json +9 -0
  12. package/catalog/locale.json +48 -0
  13. package/catalog/macros.json +3417 -0
  14. package/catalog/recipes.json +61 -0
  15. package/catalog/strings.json +72 -0
  16. package/catalog/style.json +128 -0
  17. package/catalog/theme.schema.json +42543 -0
  18. package/catalog/tokens.json +47 -0
  19. package/conformance/README.md +217 -0
  20. package/conformance/baseline.ts +101 -0
  21. package/conformance/browser-page.ts +146 -0
  22. package/conformance/browser-suites.ts +683 -0
  23. package/conformance/compare.ts +201 -0
  24. package/conformance/dom.ts +96 -0
  25. package/conformance/dump.ts +163 -0
  26. package/conformance/fonts.json +154 -0
  27. package/conformance/manifest.json +224 -0
  28. package/conformance/parity-compare.ts +191 -0
  29. package/conformance/parity-report.schema.json +73 -0
  30. package/conformance/parity.ts +412 -0
  31. package/conformance/pixel.ts +177 -0
  32. package/conformance/primitives-page.ts +49 -0
  33. package/conformance/report.schema.json +66 -0
  34. package/conformance/run.ts +118 -0
  35. package/dist/chunks/index-98npjvsd.js +3940 -0
  36. package/dist/chunks/index-98npjvsd.js.map +27 -0
  37. package/dist/chunks/index-gwkt14w3.js +7314 -0
  38. package/dist/chunks/index-gwkt14w3.js.map +10 -0
  39. package/dist/chunks/index-njt7yzzm.js +134 -0
  40. package/dist/chunks/index-njt7yzzm.js.map +9 -0
  41. package/dist/chunks/index-we7va6v6.js +7984 -0
  42. package/dist/chunks/index-we7va6v6.js.map +15 -0
  43. package/dist/chunks/index-xrm6f7wc.js +2563 -0
  44. package/dist/chunks/index-xrm6f7wc.js.map +10 -0
  45. package/dist/conformance.js +182749 -0
  46. package/dist/conformance.js.map +10 -0
  47. package/dist/entries/prompt.js +251 -0
  48. package/dist/entries/prompt.js.map +11 -0
  49. package/dist/entries/schema.js +1725 -0
  50. package/dist/entries/schema.js.map +11 -0
  51. package/dist/host/index.js +122 -0
  52. package/dist/host/index.js.map +9 -0
  53. package/dist/index.js +3213 -0
  54. package/dist/index.js.map +27 -0
  55. package/dist/types/src/a11y.d.ts +21 -0
  56. package/dist/types/src/animation.d.ts +76 -0
  57. package/dist/types/src/basic-map.d.ts +38 -0
  58. package/dist/types/src/builder.d.ts +50 -0
  59. package/dist/types/src/catalog.d.ts +54 -0
  60. package/dist/types/src/catalog.described.d.ts +33 -0
  61. package/dist/types/src/catalog.generated.d.ts +829 -0
  62. package/dist/types/src/chart.d.ts +46 -0
  63. package/dist/types/src/code.d.ts +44 -0
  64. package/dist/types/src/color.d.ts +20 -0
  65. package/dist/types/src/conformance.d.ts +39 -0
  66. package/dist/types/src/connector/app.d.ts +14 -0
  67. package/dist/types/src/connector/connector.d.ts +77 -0
  68. package/dist/types/src/connector/index.d.ts +4 -0
  69. package/dist/types/src/css.d.ts +29 -0
  70. package/dist/types/src/define.d.ts +66 -0
  71. package/dist/types/src/direction.d.ts +16 -0
  72. package/dist/types/src/dynamic.d.ts +159 -0
  73. package/dist/types/src/entries/prompt.d.ts +11 -0
  74. package/dist/types/src/entries/schema.d.ts +8 -0
  75. package/dist/types/src/expr.d.ts +71 -0
  76. package/dist/types/src/extension.d.ts +4 -0
  77. package/dist/types/src/format.d.ts +112 -0
  78. package/dist/types/src/geometry.d.ts +40 -0
  79. package/dist/types/src/host/contract.d.ts +188 -0
  80. package/dist/types/src/host/decoders.d.ts +61 -0
  81. package/dist/types/src/host/index.d.ts +11 -0
  82. package/dist/types/src/host/package.d.ts +58 -0
  83. package/dist/types/src/host/policy.d.ts +105 -0
  84. package/dist/types/src/host/router.d.ts +34 -0
  85. package/dist/types/src/host/runtime.d.ts +205 -0
  86. package/dist/types/src/host/sources.d.ts +15 -0
  87. package/dist/types/src/host/transports.d.ts +109 -0
  88. package/dist/types/src/index.d.ts +63 -0
  89. package/dist/types/src/layout.d.ts +20 -0
  90. package/dist/types/src/limits.d.ts +21 -0
  91. package/dist/types/src/list.d.ts +133 -0
  92. package/dist/types/src/locale.d.ts +22 -0
  93. package/dist/types/src/macros.d.ts +29 -0
  94. package/dist/types/src/overlay.d.ts +27 -0
  95. package/dist/types/src/prompt.d.ts +44 -0
  96. package/dist/types/src/recipes.d.ts +17 -0
  97. package/dist/types/src/reducer.d.ts +41 -0
  98. package/dist/types/src/resizable.d.ts +38 -0
  99. package/dist/types/src/schema.d.ts +7 -0
  100. package/dist/types/src/strings.d.ts +13 -0
  101. package/dist/types/src/style.d.ts +201 -0
  102. package/dist/types/src/test-preload.d.ts +1 -0
  103. package/dist/types/src/text-shimmer.d.ts +24 -0
  104. package/dist/types/src/theme-types.d.ts +162 -0
  105. package/dist/types/src/theme.d.ts +73 -0
  106. package/dist/types/src/themes.d.ts +22 -0
  107. package/dist/types/src/tree-guides.d.ts +17 -0
  108. package/dist/types/src/types.d.ts +314 -0
  109. package/dist/types/src/validate.d.ts +31 -0
  110. package/dist/types/src/web/base-css.d.ts +1 -0
  111. package/dist/types/src/web/box-css.d.ts +55 -0
  112. package/dist/types/src/web/coerce.d.ts +32 -0
  113. package/dist/types/src/web/data.d.ts +105 -0
  114. package/dist/types/src/web/dates.d.ts +12 -0
  115. package/dist/types/src/web/form.d.ts +44 -0
  116. package/dist/types/src/web/host-types.d.ts +119 -0
  117. package/dist/types/src/web/index.d.ts +28 -0
  118. package/dist/types/src/web/inputs.d.ts +55 -0
  119. package/dist/types/src/web/list.d.ts +13 -0
  120. package/dist/types/src/web/listbox.d.ts +14 -0
  121. package/dist/types/src/web/markdown.d.ts +96 -0
  122. package/dist/types/src/web/media.d.ts +62 -0
  123. package/dist/types/src/web/parts.d.ts +4 -0
  124. package/dist/types/src/web/primitives/alert-dialog.d.ts +10 -0
  125. package/dist/types/src/web/primitives/alert.d.ts +9 -0
  126. package/dist/types/src/web/primitives/avatar-color.d.ts +2 -0
  127. package/dist/types/src/web/primitives/avatar.d.ts +13 -0
  128. package/dist/types/src/web/primitives/badge.d.ts +10 -0
  129. package/dist/types/src/web/primitives/breadcrumb.d.ts +10 -0
  130. package/dist/types/src/web/primitives/button.d.ts +6 -0
  131. package/dist/types/src/web/primitives/calendar.d.ts +43 -0
  132. package/dist/types/src/web/primitives/card.d.ts +9 -0
  133. package/dist/types/src/web/primitives/checkbox.d.ts +5 -0
  134. package/dist/types/src/web/primitives/choice-tiles.d.ts +32 -0
  135. package/dist/types/src/web/primitives/cn.d.ts +3 -0
  136. package/dist/types/src/web/primitives/code-block.d.ts +24 -0
  137. package/dist/types/src/web/primitives/command.d.ts +134 -0
  138. package/dist/types/src/web/primitives/context-ring.d.ts +24 -0
  139. package/dist/types/src/web/primitives/dialog-arms.d.ts +6 -0
  140. package/dist/types/src/web/primitives/dialog.d.ts +24 -0
  141. package/dist/types/src/web/primitives/disclosure-header.d.ts +5 -0
  142. package/dist/types/src/web/primitives/dock.d.ts +42 -0
  143. package/dist/types/src/web/primitives/dropdown-menu.d.ts +14 -0
  144. package/dist/types/src/web/primitives/empty-state.d.ts +15 -0
  145. package/dist/types/src/web/primitives/glass-card.d.ts +4 -0
  146. package/dist/types/src/web/primitives/hover-card.d.ts +7 -0
  147. package/dist/types/src/web/primitives/icon-disc.d.ts +12 -0
  148. package/dist/types/src/web/primitives/index.d.ts +48 -0
  149. package/dist/types/src/web/primitives/input.d.ts +5 -0
  150. package/dist/types/src/web/primitives/kbd.d.ts +4 -0
  151. package/dist/types/src/web/primitives/label.d.ts +3 -0
  152. package/dist/types/src/web/primitives/menu-surface.d.ts +17 -0
  153. package/dist/types/src/web/primitives/meter.d.ts +21 -0
  154. package/dist/types/src/web/primitives/popover.d.ts +3 -0
  155. package/dist/types/src/web/primitives/progress-ring.d.ts +5 -0
  156. package/dist/types/src/web/primitives/progress.d.ts +8 -0
  157. package/dist/types/src/web/primitives/scroll-area.d.ts +49 -0
  158. package/dist/types/src/web/primitives/segmented-control.d.ts +5 -0
  159. package/dist/types/src/web/primitives/select.d.ts +47 -0
  160. package/dist/types/src/web/primitives/separator.d.ts +3 -0
  161. package/dist/types/src/web/primitives/sheet-chrome.d.ts +4 -0
  162. package/dist/types/src/web/primitives/sheet.d.ts +20 -0
  163. package/dist/types/src/web/primitives/skeleton.d.ts +3 -0
  164. package/dist/types/src/web/primitives/spinner.d.ts +3 -0
  165. package/dist/types/src/web/primitives/switch.d.ts +4 -0
  166. package/dist/types/src/web/primitives/table.d.ts +11 -0
  167. package/dist/types/src/web/primitives/tabs.d.ts +13 -0
  168. package/dist/types/src/web/primitives/textarea.d.ts +3 -0
  169. package/dist/types/src/web/primitives/toast.d.ts +163 -0
  170. package/dist/types/src/web/primitives/tooltip.d.ts +3 -0
  171. package/dist/types/src/web/primitives/truncate.d.ts +7 -0
  172. package/dist/types/src/web/primitives/typeahead-portal.d.ts +12 -0
  173. package/dist/types/src/web/share.d.ts +6 -0
  174. package/dist/types/src/web/table.d.ts +20 -0
  175. package/dist/types/src/web/theme-css.d.ts +69 -0
  176. package/dist/types/src/web/urls.d.ts +21 -0
  177. package/dist/web/index.js +2303 -0
  178. package/dist/web/index.js.map +26 -0
  179. package/dist/web/primitives/index.js +1450 -0
  180. package/dist/web/primitives/index.js.map +57 -0
  181. package/docs/components.generated.json +6238 -0
  182. package/docs/round-1-contract.md +679 -0
  183. package/docs/round-2-contract.md +476 -0
  184. package/docs/round-3-contract.md +174 -0
  185. package/docs/themes.generated.json +3503 -0
  186. package/fixtures/animations.json +2890 -0
  187. package/fixtures/bench-list.json +58 -0
  188. package/fixtures/bind-time.json +30664 -0
  189. package/fixtures/catalog-basic-map.json +1692 -0
  190. package/fixtures/catalog-components.json +9323 -0
  191. package/fixtures/catalog-extension.json +620 -0
  192. package/fixtures/catalog-macros.json +36370 -0
  193. package/fixtures/code-tokens.json +1993 -0
  194. package/fixtures/conformance-baseline.json +55544 -0
  195. package/fixtures/conformance-cases.json +49 -0
  196. package/fixtures/conformance-known.json +2123 -0
  197. package/fixtures/control-geometry.json +4200 -0
  198. package/fixtures/dashboard.data.json +74 -0
  199. package/fixtures/dashboard.json +752 -0
  200. package/fixtures/demo-surface.json +47 -0
  201. package/fixtures/format.json +870 -0
  202. package/fixtures/host-policy.json +1067 -0
  203. package/fixtures/host-router.json +1152 -0
  204. package/fixtures/host-transport.json +590 -0
  205. package/fixtures/interactions-round1.json +773 -0
  206. package/fixtures/kitchen-sink.expanded.json +10844 -0
  207. package/fixtures/kitchen-sink.json +3049 -0
  208. package/fixtures/layout-geometry-round1.json +7278 -0
  209. package/fixtures/layout-geometry.json +2072 -0
  210. package/fixtures/overlay-geometry.json +272 -0
  211. package/fixtures/primitive-specimens.json +58 -0
  212. package/fixtures/prompt-budget.json +33 -0
  213. package/fixtures/resizable.json +714 -0
  214. package/fixtures/round4-contract.json +2985 -0
  215. package/fixtures/specimens.json +9832 -0
  216. package/fixtures/style-conditions.json +336 -0
  217. package/fixtures/template-items.json +2215 -0
  218. package/fixtures/text-direction.json +309 -0
  219. package/fixtures/text-shimmer.json +96 -0
  220. package/fixtures/theme-extends.json +573 -0
  221. package/fixtures/theme-invalid.json +1296 -0
  222. package/fixtures/theme-recipes.json +58600 -0
  223. package/fixtures/theme-resolved.json +23949 -0
  224. package/fixtures/toast-stack.json +215 -0
  225. package/fixtures/tree-guides.json +224 -0
  226. package/fixtures/virtual-list.json +733 -0
  227. package/generated/ExponentialUICatalog.generated.kt +67 -0
  228. package/generated/ExponentialUICatalog.generated.swift +67 -0
  229. package/generated/ExponentialUIThemes.generated.kt +23 -0
  230. package/generated/ExponentialUIThemes.generated.swift +23 -0
  231. package/generated/catalog.descriptions.generated.rs +6 -0
  232. package/generated/catalog.generated.rs +80 -0
  233. package/generated/catalog.themes.generated.rs +34 -0
  234. package/generated/core.descriptions.json +2505 -0
  235. package/generated/core.runtime.json +3609 -0
  236. package/package.json +87 -4
  237. package/themes/exponential.recipes.json +49 -0
  238. package/themes/exponential.theme.json +755 -0
  239. package/themes/neutral.theme.json +5676 -0
  240. package/themes/orange.theme.json +43 -0
  241. package/themes/playful.theme.json +219 -0
  242. package/vendor/a2ui/LICENSE.txt +203 -0
  243. package/vendor/a2ui/README.md +27 -0
  244. package/vendor/a2ui/v0_9/basic_catalog.json +1387 -0
  245. package/vendor/a2ui/v0_9/basic_catalog_rules.txt +5 -0
  246. package/vendor/a2ui/v0_9/client_capabilities.json +97 -0
  247. package/vendor/a2ui/v0_9/client_to_server.json +104 -0
  248. package/vendor/a2ui/v0_9/common_types.json +315 -0
  249. package/vendor/a2ui/v0_9/server_to_client.json +132 -0
  250. package/vendor/json-render/LICENSE.txt +201 -0
  251. package/vendor/json-render/README.md +13 -0
@@ -0,0 +1,1067 @@
1
+ {
2
+ "$comment": "VAPP-91: the pure host rules (catalog/host.json functions, urls, media, sources, negotiation). Inputs hand-written; `expected` filled by the generator from src/host/{policy,sources,package}.ts.",
3
+ "functions": [
4
+ {
5
+ "name": "a built-in always runs",
6
+ "policy": {
7
+ "deny": [
8
+ "*"
9
+ ]
10
+ },
11
+ "fn": "formatDate",
12
+ "registered": false,
13
+ "expected": "allow"
14
+ },
15
+ {
16
+ "name": "openUrl is a built-in",
17
+ "fn": "openUrl",
18
+ "registered": false,
19
+ "expected": "allow"
20
+ },
21
+ {
22
+ "name": "an unregistered name",
23
+ "fn": "harness.mcp",
24
+ "registered": false,
25
+ "expected": "not_found"
26
+ },
27
+ {
28
+ "name": "registered, no policy = allow",
29
+ "fn": "harness.toast",
30
+ "registered": true,
31
+ "expected": "allow"
32
+ },
33
+ {
34
+ "name": "a prefix ask",
35
+ "policy": {
36
+ "ask": [
37
+ "harness.*"
38
+ ]
39
+ },
40
+ "fn": "harness.mcp",
41
+ "registered": true,
42
+ "expected": "ask"
43
+ },
44
+ {
45
+ "name": "allow beats ask",
46
+ "policy": {
47
+ "allow": [
48
+ "harness.toast"
49
+ ],
50
+ "ask": [
51
+ "harness.*"
52
+ ]
53
+ },
54
+ "fn": "harness.toast",
55
+ "registered": true,
56
+ "expected": "allow"
57
+ },
58
+ {
59
+ "name": "deny beats allow",
60
+ "policy": {
61
+ "allow": [
62
+ "harness.*"
63
+ ],
64
+ "deny": [
65
+ "harness.mcp"
66
+ ]
67
+ },
68
+ "fn": "harness.mcp",
69
+ "registered": true,
70
+ "expected": "deny"
71
+ },
72
+ {
73
+ "name": "the default when nothing matches",
74
+ "policy": {
75
+ "allow": [
76
+ "harness.toast"
77
+ ],
78
+ "default": "deny"
79
+ },
80
+ "fn": "harness.openIssue",
81
+ "registered": true,
82
+ "expected": "deny"
83
+ },
84
+ {
85
+ "name": "a package allowlist",
86
+ "policy": {
87
+ "allow": [
88
+ "harness.toast",
89
+ "acme.*"
90
+ ],
91
+ "default": "deny"
92
+ },
93
+ "fn": "acme.refresh",
94
+ "registered": true,
95
+ "expected": "allow"
96
+ },
97
+ {
98
+ "name": "a star alone matches everything",
99
+ "policy": {
100
+ "ask": [
101
+ "*"
102
+ ]
103
+ },
104
+ "fn": "x",
105
+ "registered": true,
106
+ "expected": "ask"
107
+ }
108
+ ],
109
+ "combine": [
110
+ {
111
+ "a": "allow",
112
+ "b": "allow",
113
+ "expected": "allow"
114
+ },
115
+ {
116
+ "a": "allow",
117
+ "b": "ask",
118
+ "expected": "ask"
119
+ },
120
+ {
121
+ "a": "allow",
122
+ "b": "deny",
123
+ "expected": "deny"
124
+ },
125
+ {
126
+ "a": "allow",
127
+ "b": "not_found",
128
+ "expected": "not_found"
129
+ },
130
+ {
131
+ "a": "ask",
132
+ "b": "allow",
133
+ "expected": "ask"
134
+ },
135
+ {
136
+ "a": "ask",
137
+ "b": "ask",
138
+ "expected": "ask"
139
+ },
140
+ {
141
+ "a": "ask",
142
+ "b": "deny",
143
+ "expected": "deny"
144
+ },
145
+ {
146
+ "a": "ask",
147
+ "b": "not_found",
148
+ "expected": "not_found"
149
+ },
150
+ {
151
+ "a": "deny",
152
+ "b": "allow",
153
+ "expected": "deny"
154
+ },
155
+ {
156
+ "a": "deny",
157
+ "b": "ask",
158
+ "expected": "deny"
159
+ },
160
+ {
161
+ "a": "deny",
162
+ "b": "deny",
163
+ "expected": "deny"
164
+ },
165
+ {
166
+ "a": "deny",
167
+ "b": "not_found",
168
+ "expected": "not_found"
169
+ },
170
+ {
171
+ "a": "not_found",
172
+ "b": "allow",
173
+ "expected": "not_found"
174
+ },
175
+ {
176
+ "a": "not_found",
177
+ "b": "ask",
178
+ "expected": "not_found"
179
+ },
180
+ {
181
+ "a": "not_found",
182
+ "b": "deny",
183
+ "expected": "not_found"
184
+ },
185
+ {
186
+ "a": "not_found",
187
+ "b": "not_found",
188
+ "expected": "not_found"
189
+ }
190
+ ],
191
+ "urls": [
192
+ {
193
+ "name": "https",
194
+ "url": "https://exponential.at/docs",
195
+ "expected": {
196
+ "allowed": true,
197
+ "url": "https://exponential.at/docs"
198
+ }
199
+ },
200
+ {
201
+ "name": "mailto",
202
+ "url": "mailto:hi@exponential.at",
203
+ "expected": {
204
+ "allowed": true,
205
+ "url": "mailto:hi@exponential.at"
206
+ }
207
+ },
208
+ {
209
+ "name": "javascript is never a default scheme",
210
+ "url": "javascript:alert(1)",
211
+ "expected": {
212
+ "allowed": false,
213
+ "url": "javascript:alert(1)",
214
+ "reason": "scheme"
215
+ }
216
+ },
217
+ {
218
+ "name": "data is not a default scheme",
219
+ "url": "data:text/html,hi",
220
+ "expected": {
221
+ "allowed": false,
222
+ "url": "data:text/html,hi",
223
+ "reason": "scheme"
224
+ }
225
+ },
226
+ {
227
+ "name": "file is not a default scheme",
228
+ "url": "file:///etc/passwd",
229
+ "expected": {
230
+ "allowed": false,
231
+ "url": "file:///etc/passwd",
232
+ "reason": "scheme"
233
+ }
234
+ },
235
+ {
236
+ "name": "upper-case scheme",
237
+ "url": "HTTPS://Example.com/A",
238
+ "expected": {
239
+ "allowed": true,
240
+ "url": "https://example.com/A"
241
+ }
242
+ },
243
+ {
244
+ "name": "relative without a base",
245
+ "url": "/api/attachments/a1",
246
+ "expected": {
247
+ "allowed": false,
248
+ "reason": "invalid"
249
+ }
250
+ },
251
+ {
252
+ "name": "relative against the base",
253
+ "policy": {
254
+ "baseUrl": "https://app.exponential.at"
255
+ },
256
+ "url": "/api/attachments/a1",
257
+ "expected": {
258
+ "allowed": true,
259
+ "url": "https://app.exponential.at/api/attachments/a1"
260
+ }
261
+ },
262
+ {
263
+ "name": "a host allowlist",
264
+ "policy": {
265
+ "hosts": [
266
+ "exponential.at",
267
+ "*.exponential.at"
268
+ ]
269
+ },
270
+ "url": "https://ui.exponential.at/catalogs",
271
+ "expected": {
272
+ "allowed": true,
273
+ "url": "https://ui.exponential.at/catalogs"
274
+ }
275
+ },
276
+ {
277
+ "name": "outside the host allowlist",
278
+ "policy": {
279
+ "hosts": [
280
+ "exponential.at"
281
+ ]
282
+ },
283
+ "url": "https://evil.example/",
284
+ "expected": {
285
+ "allowed": false,
286
+ "url": "https://evil.example/",
287
+ "reason": "host"
288
+ }
289
+ },
290
+ {
291
+ "name": "a wildcard host does not match its apex",
292
+ "policy": {
293
+ "hosts": [
294
+ "*.exponential.at"
295
+ ]
296
+ },
297
+ "url": "https://exponential.at/",
298
+ "expected": {
299
+ "allowed": false,
300
+ "url": "https://exponential.at/",
301
+ "reason": "host"
302
+ }
303
+ },
304
+ {
305
+ "name": "hosts do not gate mailto",
306
+ "policy": {
307
+ "hosts": [
308
+ "exponential.at"
309
+ ]
310
+ },
311
+ "url": "mailto:a@b.c",
312
+ "expected": {
313
+ "allowed": true,
314
+ "url": "mailto:a@b.c"
315
+ }
316
+ },
317
+ {
318
+ "name": "a custom scheme list",
319
+ "policy": {
320
+ "schemes": [
321
+ "https",
322
+ "exponential"
323
+ ]
324
+ },
325
+ "url": "exponential://issue/EXP-1",
326
+ "expected": {
327
+ "allowed": true,
328
+ "url": "exponential://issue/EXP-1"
329
+ }
330
+ },
331
+ {
332
+ "name": "garbage",
333
+ "url": "::not a url",
334
+ "expected": {
335
+ "allowed": false,
336
+ "reason": "invalid"
337
+ }
338
+ },
339
+ {
340
+ "name": "a data url is not a link scheme",
341
+ "url": "data:text/html,<script>alert(1)</script>",
342
+ "expected": {
343
+ "allowed": false,
344
+ "url": "data:text/html,<script>alert(1)</script>",
345
+ "reason": "scheme"
346
+ }
347
+ },
348
+ {
349
+ "name": "a host allow-list holds for a relative href",
350
+ "policy": {
351
+ "baseUrl": "https://evil.example",
352
+ "hosts": [
353
+ "exponential.at"
354
+ ]
355
+ },
356
+ "url": "/x",
357
+ "expected": {
358
+ "allowed": false,
359
+ "url": "https://evil.example/x",
360
+ "reason": "host"
361
+ }
362
+ },
363
+ {
364
+ "name": "a url with balanced parentheses",
365
+ "url": "https://en.wikipedia.org/wiki/A_(b)",
366
+ "expected": {
367
+ "allowed": true,
368
+ "url": "https://en.wikipedia.org/wiki/A_(b)"
369
+ }
370
+ },
371
+ {
372
+ "name": "VAPP-107: loopback is private by default",
373
+ "url": "http://127.0.0.1:3000/api/attachments/a1",
374
+ "expected": {
375
+ "allowed": false,
376
+ "url": "http://127.0.0.1:3000/api/attachments/a1",
377
+ "reason": "private"
378
+ }
379
+ },
380
+ {
381
+ "name": "localhost is private",
382
+ "url": "http://localhost:5173/",
383
+ "expected": {
384
+ "allowed": false,
385
+ "url": "http://localhost:5173/",
386
+ "reason": "private"
387
+ }
388
+ },
389
+ {
390
+ "name": "a localhost subdomain is private",
391
+ "url": "https://app.localhost/",
392
+ "expected": {
393
+ "allowed": false,
394
+ "url": "https://app.localhost/",
395
+ "reason": "private"
396
+ }
397
+ },
398
+ {
399
+ "name": "an IPv6 loopback is private",
400
+ "url": "http://[::1]:8080/",
401
+ "expected": {
402
+ "allowed": false,
403
+ "url": "http://[::1]:8080/",
404
+ "reason": "private"
405
+ }
406
+ },
407
+ {
408
+ "name": "the unspecified addresses are private",
409
+ "url": "http://0.0.0.0/",
410
+ "expected": {
411
+ "allowed": false,
412
+ "url": "http://0.0.0.0/",
413
+ "reason": "private"
414
+ }
415
+ },
416
+ {
417
+ "name": "the 10/8 block is private",
418
+ "url": "http://10.0.0.5/x.png",
419
+ "expected": {
420
+ "allowed": false,
421
+ "url": "http://10.0.0.5/x.png",
422
+ "reason": "private"
423
+ }
424
+ },
425
+ {
426
+ "name": "the 172.16/12 block is private",
427
+ "url": "http://172.31.255.254/",
428
+ "expected": {
429
+ "allowed": false,
430
+ "url": "http://172.31.255.254/",
431
+ "reason": "private"
432
+ }
433
+ },
434
+ {
435
+ "name": "172.32 is public",
436
+ "url": "http://172.32.0.1/",
437
+ "expected": {
438
+ "allowed": true,
439
+ "url": "http://172.32.0.1/"
440
+ }
441
+ },
442
+ {
443
+ "name": "the 192.168/16 block is private",
444
+ "url": "https://192.168.1.10/cam.jpg",
445
+ "expected": {
446
+ "allowed": false,
447
+ "url": "https://192.168.1.10/cam.jpg",
448
+ "reason": "private"
449
+ }
450
+ },
451
+ {
452
+ "name": "link-local is private (the cloud metadata endpoint)",
453
+ "url": "http://169.254.169.254/latest/meta-data/",
454
+ "expected": {
455
+ "allowed": false,
456
+ "url": "http://169.254.169.254/latest/meta-data/",
457
+ "reason": "private"
458
+ }
459
+ },
460
+ {
461
+ "name": "IPv6 link-local is private",
462
+ "url": "http://[fe80::1]/",
463
+ "expected": {
464
+ "allowed": false,
465
+ "url": "http://[fe80::1]/",
466
+ "reason": "private"
467
+ }
468
+ },
469
+ {
470
+ "name": "IPv6 unique-local is private",
471
+ "url": "http://[fd12:3456::1]/",
472
+ "expected": {
473
+ "allowed": false,
474
+ "url": "http://[fd12:3456::1]/",
475
+ "reason": "private"
476
+ }
477
+ },
478
+ {
479
+ "name": "an IPv4-mapped IPv6 address of a private IPv4 is private",
480
+ "url": "http://[::ffff:127.0.0.1]/",
481
+ "expected": {
482
+ "allowed": false,
483
+ "url": "http://[::ffff:7f00:1]/",
484
+ "reason": "private"
485
+ }
486
+ },
487
+ {
488
+ "name": "a decimal IPv4 form normalizes before the check",
489
+ "url": "http://2130706433/",
490
+ "expected": {
491
+ "allowed": false,
492
+ "url": "http://127.0.0.1/",
493
+ "reason": "private"
494
+ }
495
+ },
496
+ {
497
+ "name": "a hex IPv4 form normalizes before the check",
498
+ "url": "http://0xc0.0xa8.1.1/",
499
+ "expected": {
500
+ "allowed": false,
501
+ "url": "http://192.168.1.1/",
502
+ "reason": "private"
503
+ }
504
+ },
505
+ {
506
+ "name": "a .local name is private",
507
+ "url": "http://printer.local/status.png",
508
+ "expected": {
509
+ "allowed": false,
510
+ "url": "http://printer.local/status.png",
511
+ "reason": "private"
512
+ }
513
+ },
514
+ {
515
+ "name": "a public address is not private",
516
+ "url": "http://8.8.8.8/",
517
+ "expected": {
518
+ "allowed": true,
519
+ "url": "http://8.8.8.8/"
520
+ }
521
+ },
522
+ {
523
+ "name": "a public name is not resolved",
524
+ "url": "https://intranet.corp.example/",
525
+ "expected": {
526
+ "allowed": true,
527
+ "url": "https://intranet.corp.example/"
528
+ }
529
+ },
530
+ {
531
+ "name": "a relative url against a private base is private",
532
+ "policy": {
533
+ "baseUrl": "http://localhost:3000"
534
+ },
535
+ "url": "/api/attachments/a1",
536
+ "expected": {
537
+ "allowed": false,
538
+ "url": "http://localhost:3000/api/attachments/a1",
539
+ "reason": "private"
540
+ }
541
+ },
542
+ {
543
+ "name": "the host opts localhost back in",
544
+ "policy": {
545
+ "allowPrivate": [
546
+ "localhost"
547
+ ]
548
+ },
549
+ "url": "http://localhost:3000/api/attachments/a1",
550
+ "expected": {
551
+ "allowed": true,
552
+ "url": "http://localhost:3000/api/attachments/a1"
553
+ }
554
+ },
555
+ {
556
+ "name": "an opt-in for one host does not cover another",
557
+ "policy": {
558
+ "allowPrivate": [
559
+ "localhost"
560
+ ]
561
+ },
562
+ "url": "http://127.0.0.1:3000/",
563
+ "expected": {
564
+ "allowed": false,
565
+ "url": "http://127.0.0.1:3000/",
566
+ "reason": "private"
567
+ }
568
+ },
569
+ {
570
+ "name": "an opt-in by suffix",
571
+ "policy": {
572
+ "allowPrivate": [
573
+ "*.local"
574
+ ]
575
+ },
576
+ "url": "http://printer.local/",
577
+ "expected": {
578
+ "allowed": true,
579
+ "url": "http://printer.local/"
580
+ }
581
+ },
582
+ {
583
+ "name": "a star opts every private address in",
584
+ "policy": {
585
+ "allowPrivate": [
586
+ "*"
587
+ ]
588
+ },
589
+ "url": "http://10.0.0.5/",
590
+ "expected": {
591
+ "allowed": true,
592
+ "url": "http://10.0.0.5/"
593
+ }
594
+ },
595
+ {
596
+ "name": "a hosts allowlist does not opt a private host in",
597
+ "policy": {
598
+ "hosts": [
599
+ "localhost"
600
+ ]
601
+ },
602
+ "url": "http://localhost/",
603
+ "expected": {
604
+ "allowed": false,
605
+ "url": "http://localhost/",
606
+ "reason": "private"
607
+ }
608
+ },
609
+ {
610
+ "name": "outside the allowlist reads host before private",
611
+ "policy": {
612
+ "hosts": [
613
+ "exponential.at"
614
+ ]
615
+ },
616
+ "url": "http://localhost/",
617
+ "expected": {
618
+ "allowed": false,
619
+ "url": "http://localhost/",
620
+ "reason": "host"
621
+ }
622
+ },
623
+ {
624
+ "name": "mailto is never private",
625
+ "url": "mailto:root@localhost",
626
+ "expected": {
627
+ "allowed": true,
628
+ "url": "mailto:root@localhost"
629
+ }
630
+ },
631
+ {
632
+ "name": "a custom scheme is never private",
633
+ "policy": {
634
+ "schemes": [
635
+ "exponential"
636
+ ]
637
+ },
638
+ "url": "exponential://localhost/issue/EXP-1",
639
+ "expected": {
640
+ "allowed": true,
641
+ "url": "exponential://localhost/issue/EXP-1"
642
+ }
643
+ },
644
+ {
645
+ "name": "https only",
646
+ "policy": {
647
+ "schemes": [
648
+ "https"
649
+ ]
650
+ },
651
+ "url": "http://exponential.at/",
652
+ "expected": {
653
+ "allowed": false,
654
+ "url": "http://exponential.at/",
655
+ "reason": "scheme"
656
+ }
657
+ },
658
+ {
659
+ "name": "https only keeps https",
660
+ "policy": {
661
+ "schemes": [
662
+ "https"
663
+ ]
664
+ },
665
+ "url": "https://exponential.at/",
666
+ "expected": {
667
+ "allowed": true,
668
+ "url": "https://exponential.at/"
669
+ }
670
+ },
671
+ {
672
+ "name": "https only drops mailto and tel",
673
+ "policy": {
674
+ "schemes": [
675
+ "https"
676
+ ]
677
+ },
678
+ "url": "tel:+4312345",
679
+ "expected": {
680
+ "allowed": false,
681
+ "url": "tel:+4312345",
682
+ "reason": "scheme"
683
+ }
684
+ }
685
+ ],
686
+ "media": [
687
+ {
688
+ "name": "relative attachment gets the bearer",
689
+ "options": {
690
+ "baseUrl": "https://app.exponential.at",
691
+ "rules": [
692
+ {
693
+ "prefix": "https://app.exponential.at/api/attachments/",
694
+ "headers": {
695
+ "authorization": "Bearer expu_test"
696
+ }
697
+ }
698
+ ]
699
+ },
700
+ "url": "/api/attachments/a1?poster=1",
701
+ "expected": {
702
+ "url": "https://app.exponential.at/api/attachments/a1?poster=1",
703
+ "headers": {
704
+ "authorization": "Bearer expu_test"
705
+ }
706
+ }
707
+ },
708
+ {
709
+ "name": "another origin gets nothing",
710
+ "options": {
711
+ "baseUrl": "https://app.exponential.at",
712
+ "rules": [
713
+ {
714
+ "prefix": "https://app.exponential.at/api/",
715
+ "headers": {
716
+ "authorization": "Bearer t"
717
+ }
718
+ }
719
+ ]
720
+ },
721
+ "url": "https://cdn.example/x.png",
722
+ "expected": {
723
+ "url": "https://cdn.example/x.png",
724
+ "headers": {}
725
+ }
726
+ },
727
+ {
728
+ "name": "later rules win per header",
729
+ "options": {
730
+ "rules": [
731
+ {
732
+ "prefix": "https://a.example/",
733
+ "headers": {
734
+ "x-a": "1",
735
+ "x-b": "1"
736
+ }
737
+ },
738
+ {
739
+ "prefix": "https://a.example/p/",
740
+ "headers": {
741
+ "x-b": "2"
742
+ }
743
+ }
744
+ ]
745
+ },
746
+ "url": "https://a.example/p/q.png",
747
+ "expected": {
748
+ "url": "https://a.example/p/q.png",
749
+ "headers": {
750
+ "x-a": "1",
751
+ "x-b": "2"
752
+ }
753
+ }
754
+ },
755
+ {
756
+ "name": "relative without a base",
757
+ "options": {},
758
+ "url": "x.png",
759
+ "expected": null
760
+ },
761
+ {
762
+ "name": "javascript is never a media scheme",
763
+ "options": {},
764
+ "url": "javascript:alert(1)",
765
+ "expected": null
766
+ },
767
+ {
768
+ "name": "file is denied by default",
769
+ "options": {},
770
+ "url": "file:///etc/passwd",
771
+ "expected": null
772
+ },
773
+ {
774
+ "name": "a relative path against a file base stays denied",
775
+ "options": {
776
+ "baseUrl": "file:///Users/me/"
777
+ },
778
+ "url": "/etc/passwd",
779
+ "expected": null
780
+ },
781
+ {
782
+ "name": "file only when the host lists it",
783
+ "options": {
784
+ "schemes": [
785
+ "file"
786
+ ]
787
+ },
788
+ "url": "file:///tmp/a.png",
789
+ "expected": {
790
+ "url": "file:///tmp/a.png",
791
+ "headers": {}
792
+ }
793
+ },
794
+ {
795
+ "name": "a data image",
796
+ "options": {},
797
+ "url": "data:image/png;base64,iVBORw0KGgo=",
798
+ "expected": {
799
+ "url": "data:image/png;base64,iVBORw0KGgo=",
800
+ "headers": {}
801
+ }
802
+ },
803
+ {
804
+ "name": "the media host allow-list",
805
+ "options": {
806
+ "hosts": [
807
+ "*.exponential.at"
808
+ ]
809
+ },
810
+ "url": "https://cdn.example/x.png",
811
+ "expected": null
812
+ },
813
+ {
814
+ "name": "an allowed media host",
815
+ "options": {
816
+ "hosts": [
817
+ "*.exponential.at"
818
+ ],
819
+ "rules": [
820
+ {
821
+ "prefix": "https://app.exponential.at/",
822
+ "headers": {
823
+ "authorization": "Bearer t"
824
+ }
825
+ }
826
+ ]
827
+ },
828
+ "url": "https://app.exponential.at/a.png",
829
+ "expected": {
830
+ "url": "https://app.exponential.at/a.png",
831
+ "headers": {
832
+ "authorization": "Bearer t"
833
+ }
834
+ }
835
+ },
836
+ {
837
+ "name": "VAPP-107: a camera on the LAN is denied",
838
+ "options": {},
839
+ "url": "http://192.168.1.10/cam.jpg",
840
+ "expected": null
841
+ },
842
+ {
843
+ "name": "a loopback poster is denied",
844
+ "options": {
845
+ "baseUrl": "http://127.0.0.1:3000"
846
+ },
847
+ "url": "/api/attachments/a1?poster=1",
848
+ "expected": null
849
+ },
850
+ {
851
+ "name": "the dev app opts its own origin in",
852
+ "options": {
853
+ "baseUrl": "http://localhost:3000",
854
+ "allowPrivate": [
855
+ "localhost"
856
+ ],
857
+ "rules": [
858
+ {
859
+ "prefix": "http://localhost:3000/api/attachments/",
860
+ "headers": {
861
+ "authorization": "Bearer expu_dev"
862
+ }
863
+ }
864
+ ]
865
+ },
866
+ "url": "/api/attachments/a1",
867
+ "expected": {
868
+ "url": "http://localhost:3000/api/attachments/a1",
869
+ "headers": {
870
+ "authorization": "Bearer expu_dev"
871
+ }
872
+ }
873
+ },
874
+ {
875
+ "name": "the opt-in does not widen to the LAN",
876
+ "options": {
877
+ "baseUrl": "http://localhost:3000",
878
+ "allowPrivate": [
879
+ "localhost"
880
+ ]
881
+ },
882
+ "url": "http://192.168.1.10/cam.jpg",
883
+ "expected": null
884
+ },
885
+ {
886
+ "name": "a data image is never private",
887
+ "options": {},
888
+ "url": "data:image/png;base64,iVBORw0KGgo=",
889
+ "expected": {
890
+ "url": "data:image/png;base64,iVBORw0KGgo=",
891
+ "headers": {}
892
+ }
893
+ },
894
+ {
895
+ "name": "https-only media",
896
+ "options": {
897
+ "schemes": [
898
+ "https"
899
+ ]
900
+ },
901
+ "url": "http://cdn.example/x.png",
902
+ "expected": null
903
+ }
904
+ ],
905
+ "sources": [
906
+ {
907
+ "uri": "exp:issues?board=b1&limit=20",
908
+ "expected": {
909
+ "uri": "exp:issues?board=b1&limit=20",
910
+ "scheme": "exp",
911
+ "name": "issues",
912
+ "params": {
913
+ "board": "b1",
914
+ "limit": "20"
915
+ }
916
+ }
917
+ },
918
+ {
919
+ "uri": "exp:devices",
920
+ "expected": {
921
+ "uri": "exp:devices",
922
+ "scheme": "exp",
923
+ "name": "devices",
924
+ "params": {}
925
+ }
926
+ },
927
+ {
928
+ "uri": "EXP:Members?team=t%201",
929
+ "expected": {
930
+ "uri": "EXP:Members?team=t%201",
931
+ "scheme": "exp",
932
+ "name": "Members",
933
+ "params": {
934
+ "team": "t 1"
935
+ }
936
+ }
937
+ },
938
+ {
939
+ "uri": "exp:issues?q=a+b&q=c",
940
+ "expected": {
941
+ "uri": "exp:issues?q=a+b&q=c",
942
+ "scheme": "exp",
943
+ "name": "issues",
944
+ "params": {
945
+ "q": "c"
946
+ }
947
+ }
948
+ },
949
+ {
950
+ "uri": "exp:records?vapp=v1&kind=recipe&empty=&flag",
951
+ "expected": {
952
+ "uri": "exp:records?vapp=v1&kind=recipe&empty=&flag",
953
+ "scheme": "exp",
954
+ "name": "records",
955
+ "params": {
956
+ "empty": "",
957
+ "flag": "",
958
+ "kind": "recipe",
959
+ "vapp": "v1"
960
+ }
961
+ }
962
+ },
963
+ {
964
+ "uri": "acme.data:list/items?x=%E2%9C%93",
965
+ "expected": {
966
+ "uri": "acme.data:list/items?x=%E2%9C%93",
967
+ "scheme": "acme.data",
968
+ "name": "list/items",
969
+ "params": {
970
+ "x": "✓"
971
+ }
972
+ }
973
+ },
974
+ {
975
+ "uri": "exp:",
976
+ "expected": null
977
+ },
978
+ {
979
+ "uri": "noscheme",
980
+ "expected": null
981
+ },
982
+ {
983
+ "uri": "1exp:issues",
984
+ "expected": null
985
+ },
986
+ {
987
+ "uri": "exp:issues?bad=%zz",
988
+ "expected": {
989
+ "uri": "exp:issues?bad=%zz",
990
+ "scheme": "exp",
991
+ "name": "issues",
992
+ "params": {
993
+ "bad": "%zz"
994
+ }
995
+ }
996
+ }
997
+ ],
998
+ "negotiation": [
999
+ {
1000
+ "extensionIds": [],
1001
+ "expected": {
1002
+ "supportedCatalogIds": [
1003
+ "https://ui.exponential.at/catalogs/core/v1",
1004
+ "https://ui.exponential.at/catalogs/core-lite/v1",
1005
+ "https://a2ui.org/specification/v0_9/basic_catalog.json"
1006
+ ],
1007
+ "clientCapabilities": {
1008
+ "v0.9": {
1009
+ "supportedCatalogIds": [
1010
+ "https://ui.exponential.at/catalogs/core/v1",
1011
+ "https://ui.exponential.at/catalogs/core-lite/v1",
1012
+ "https://a2ui.org/specification/v0_9/basic_catalog.json"
1013
+ ]
1014
+ }
1015
+ }
1016
+ }
1017
+ },
1018
+ {
1019
+ "extensionIds": [
1020
+ "https://ui.exponential.at/catalogs/exponential-app/v1"
1021
+ ],
1022
+ "expected": {
1023
+ "supportedCatalogIds": [
1024
+ "https://ui.exponential.at/catalogs/core/v1",
1025
+ "https://ui.exponential.at/catalogs/core-lite/v1",
1026
+ "https://a2ui.org/specification/v0_9/basic_catalog.json",
1027
+ "https://ui.exponential.at/catalogs/exponential-app/v1"
1028
+ ],
1029
+ "clientCapabilities": {
1030
+ "v0.9": {
1031
+ "supportedCatalogIds": [
1032
+ "https://ui.exponential.at/catalogs/core/v1",
1033
+ "https://ui.exponential.at/catalogs/core-lite/v1",
1034
+ "https://a2ui.org/specification/v0_9/basic_catalog.json",
1035
+ "https://ui.exponential.at/catalogs/exponential-app/v1"
1036
+ ]
1037
+ }
1038
+ }
1039
+ }
1040
+ },
1041
+ {
1042
+ "extensionIds": [
1043
+ "https://acme.example/catalog/v1",
1044
+ "https://acme.example/catalog/v1",
1045
+ "https://ui.exponential.at/catalogs/core/v1"
1046
+ ],
1047
+ "expected": {
1048
+ "supportedCatalogIds": [
1049
+ "https://ui.exponential.at/catalogs/core/v1",
1050
+ "https://ui.exponential.at/catalogs/core-lite/v1",
1051
+ "https://a2ui.org/specification/v0_9/basic_catalog.json",
1052
+ "https://acme.example/catalog/v1"
1053
+ ],
1054
+ "clientCapabilities": {
1055
+ "v0.9": {
1056
+ "supportedCatalogIds": [
1057
+ "https://ui.exponential.at/catalogs/core/v1",
1058
+ "https://ui.exponential.at/catalogs/core-lite/v1",
1059
+ "https://a2ui.org/specification/v0_9/basic_catalog.json",
1060
+ "https://acme.example/catalog/v1"
1061
+ ]
1062
+ }
1063
+ }
1064
+ }
1065
+ }
1066
+ ]
1067
+ }