Files
TREK/client/tests/unit/maplibre-sanitizer.test.ts
T
Maurice 1696162d22 fix(deps): maplibre-gl 6.15.0 past the attribution sanitizer advisory
GHSA-jrc7-96c5-q579 covers every maplibre-gl up to 6.4.0 and has no 5.x
release, so npm audit kept flagging the patched 5.24.0. Moving to 6.15.0
retires the local backport: patch-maplibre.mjs and its postinstall,
predev and prebuild hooks go, along with the Dockerfile copy and its
format-baseline entry. 6.x sanitizes attributions against an allow list.

What the major changes for TREK:
- ESM only, no default export: the engine module imports the namespace
  and hands that out as its default, so every caller stays as it was.
- The worker is no longer inlined, and inside a bundle MapLibre cannot
  find it next to its own module. The engine sets it from Vite's
  ?worker&url build before any map, the Leaflet bridge's included.
- WebGL2 only: the probe asks for webgl2 alone, so a WebGL1-only browser
  gets the raster basemap instead of a GPUInitializationError.
- 6.9 shapes right-to-left text itself and deprecated setRTLTextPlugin;
  the old plugin would replace that and could only run through eval in
  the module worker, so MapLibre no longer registers it. Mapbox does.

The lockfile moves maplibre-gl and its own dependencies only; the
Leaflet bridge already allows ^6.0.0 as its peer.
2026-10-24 22:41:56 +00:00

52 lines
2.2 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { AttributionControl, type Map } from 'maplibre-gl'
/**
* GHSA-jrc7-96c5-q579: MapLibre's attribution sanitizer let a second dangerous
* attribute through after removing the first, so a tile source's attribution
* could run script. TREK carried a patch for this on 5.x; 6.x replaced the
* sanitizer with an allow list, and these cases hold the upgrade to it.
*/
function attribution(html: string, fromSource = false) {
const control = new AttributionControl({
compact: false,
customAttribution: fromSource ? undefined : html,
})
const map = {
style: {
tileManagers: fromSource
? { tiles: { used: true, getSource: () => ({ attribution: html }) } }
: {},
},
_getUIString: () => 'Toggle attribution',
on: () => {},
off: () => {},
getCanvasContainer: () => document.createElement('div'),
} as unknown as Map
return control.onAdd(map)
}
describe('MapLibre attribution sanitizer', () => {
it.each([false, true])('leaves no event handler behind (source: %s)', fromSource => {
const container = attribution('<details open onload="1" ontoggle="alert(1)">Credit</details>', fromSource)
const inner = container.querySelector('.maplibregl-ctrl-attrib-inner')!
// <details> is not on the allow list, so it goes with its subtree.
expect(inner.querySelector('details')).toBeNull()
expect(inner.querySelectorAll('[onload], [ontoggle]')).toHaveLength(0)
})
it('removes consecutive dangerous URLs and handlers in nested markup', () => {
const container = attribution('<span><a href="javascript:alert(1)" onclick="alert(2)" onfocus="alert(3)">Credit</a></span>')
const link = container.querySelector('.maplibregl-ctrl-attrib-inner a')!
expect(link.attributes.length).toBe(0)
})
it('preserves provider credit and safe links', () => {
const container = attribution('<a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener">OpenStreetMap</a>')
const link = container.querySelector('.maplibregl-ctrl-attrib-inner a')!
expect(link.getAttribute('href')).toBe('https://www.openstreetmap.org/copyright')
expect(link.getAttribute('rel')).toBe('noopener')
expect(link.textContent).toBe('OpenStreetMap')
})
})