@seed-ship/mcp-ui-solid 6.9.0 → 6.10.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.
@@ -41,6 +41,25 @@ export interface MapRendererProps {
41
41
  * @see ExpandableWrapperProps.toolbarVariant
42
42
  */
43
43
  toolbarVariant?: 'hover' | 'always-visible';
44
+
45
+ /**
46
+ * Trust marker / popup content as raw HTML (v6.10.0, audit P1.2).
47
+ *
48
+ * Leaflet renders bound tooltip/popup strings as HTML, so `marker.tooltip`,
49
+ * `marker.popup` and a GeoJSON `popup.template` are XSS vectors when the
50
+ * payload is untrusted (the default for an LLM/connector-driven public
51
+ * package). **Default `false`** → those are escaped as plain text, and
52
+ * `popup.template` is ignored in favour of the safe auto-generated popup.
53
+ *
54
+ * This is a **host-level** trust decision, deliberately NOT a payload field
55
+ * (a malicious payload could just set its own flag). The
56
+ * `<UIResourceRenderer>` path never sets it, so payload-driven maps are
57
+ * always text-safe. A host rendering `<MapRenderer>` directly with trusted
58
+ * data may set `allowHtmlPopups` to restore rich HTML popups. The
59
+ * auto-generated popup (`titleField` / `fields`) is safe-by-construction
60
+ * and unaffected either way.
61
+ */
62
+ allowHtmlPopups?: boolean;
44
63
  }
45
64
 
46
65
  // ─── Helpers ────────────────────────────────────────────────
@@ -108,16 +127,27 @@ function buildStyleFn(
108
127
 
109
128
  /**
110
129
  * Build popup HTML from a feature's properties using popup config.
130
+ *
131
+ * `allowHtml` (audit P1.2) gates the raw-HTML `popup.template`: it is honored
132
+ * only when the host trusts the payload. On the default (untrusted) path the
133
+ * template is ignored and we fall through to the auto-generated popup, whose
134
+ * structure is renderer-authored and whose values are always escaped — safe
135
+ * by construction. Even in the trusted path, substituted `{{prop}}` values
136
+ * are escaped (they are data, not markup).
111
137
  */
112
- function buildPopupContent(feature: any, popup: MapPopupConfig | undefined): string | null {
138
+ export function buildPopupContent(
139
+ feature: any,
140
+ popup: MapPopupConfig | undefined,
141
+ allowHtml = false
142
+ ): string | null {
113
143
  if (!popup || !feature?.properties) return null;
114
144
  const props = feature.properties;
115
145
 
116
- // Custom template
117
- if (popup.template) {
146
+ // Custom template — raw HTML, so trusted-host only.
147
+ if (allowHtml && popup.template) {
118
148
  return popup.template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
119
149
  const val = props[key];
120
- return val != null ? String(val) : '';
150
+ return val != null ? escapeHtml(String(val)) : '';
121
151
  });
122
152
  }
123
153
 
@@ -150,6 +180,29 @@ function escapeHtml(str: string): string {
150
180
  .replace(/"/g, '&quot;');
151
181
  }
152
182
 
183
+ /**
184
+ * Resolve a marker's tooltip/popup string for binding (audit P1.2). Leaflet
185
+ * renders bound strings as HTML; `marker.tooltip` / `marker.popup` come from
186
+ * the (untrusted) payload, so they are escaped to plain text unless the host
187
+ * has opted into raw HTML via `allowHtmlPopups`. Pure + exported for tests.
188
+ */
189
+ export function popupSafeText(value: string | undefined, allowHtml = false): string | undefined {
190
+ if (value == null) return undefined;
191
+ return allowHtml ? value : escapeHtml(value);
192
+ }
193
+
194
+ /** Bind a marker's tooltip + popup, escaping by default (see popupSafeText). */
195
+ function bindMarkerContent(
196
+ m: any,
197
+ marker: { tooltip?: string; popup?: string },
198
+ allowHtml: boolean
199
+ ): void {
200
+ const tooltip = popupSafeText(marker.tooltip, allowHtml);
201
+ if (tooltip) m.bindTooltip(tooltip);
202
+ const popup = popupSafeText(marker.popup, allowHtml);
203
+ if (popup) m.bindPopup(popup);
204
+ }
205
+
153
206
  /**
154
207
  * Add a GeoJSON layer to the map with style and popup support.
155
208
  * Returns the layer for bounds calculation.
@@ -225,6 +278,9 @@ export const MapRenderer: Component<MapRendererProps> = (props) => {
225
278
  const [error, setError] = createSignal<string | null>(null);
226
279
  const isExpanded = useExpanded();
227
280
  const telemetry = useTelemetry();
281
+ // Host-level trust for raw HTML in marker/popup content (audit P1.2).
282
+ // Default false → tooltip/popup escaped, GeoJSON `popup.template` ignored.
283
+ const allowHtml = () => props.allowHtmlPopups === true;
228
284
 
229
285
  const params = () => props.params || (props.component?.params as MapComponentParams);
230
286