@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.
- package/CHANGELOG.md +37 -0
- package/dist/components/MapRenderer.cjs +4 -3
- package/dist/components/MapRenderer.cjs.map +1 -1
- package/dist/components/MapRenderer.d.ts +37 -1
- package/dist/components/MapRenderer.d.ts.map +1 -1
- package/dist/components/MapRenderer.js +5 -4
- package/dist/components/MapRenderer.js.map +1 -1
- package/package.json +1 -1
- package/src/components/MapRenderer.security.test.ts +83 -0
- package/src/components/MapRenderer.tsx +60 -4
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -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(
|
|
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, '"');
|
|
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
|
|