pre-deploy: escape external feed strings in InfoBox HTML (ship-check)
Defense-in-depth for the public deploy: USGS/EONET place/title/category are now HTML-escaped before interpolation into Cesium InfoBox descriptions, and the USGS/EONET links are passed through lib.safeUrl (http(s)-only) so a hostile feed can't inject markup or a javascript:/data: href. Entity names/labels stay raw (Cesium renders those as text). Verified: escapeHtml neutralizes <img onerror>, safeUrl drops javascript:/data:, 219 quakes + 500 fires still render normally. ship-check pass: no auth surface (dev-only history/snap not deployed); no secrets in tree (AIS key empty, data/ gitignored); proxy upstreams are a fixed dict (no SSRF); no money paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b56684c6e6
commit
0162ec0cdf
@ -59,7 +59,7 @@ export default function create(ctx) {
|
||||
if (!loc) continue;
|
||||
const title = ev.title || 'Wildfire';
|
||||
const category = (ev.categories && ev.categories[0] && ev.categories[0].title) || 'Wildfires';
|
||||
const link = ev.link || (ev.id ? `https://eonet.gsfc.nasa.gov/api/v3/events/${ev.id}` : null);
|
||||
const link = lib.safeUrl(ev.link || (ev.id ? `https://eonet.gsfc.nasa.gov/api/v3/events/${encodeURIComponent(ev.id)}` : ''));
|
||||
const dateTxt = loc.date ? new Date(loc.date).toISOString().replace('T', ' ').replace('.000Z', ' UTC') : '—';
|
||||
|
||||
ds.entities.add({
|
||||
@ -88,11 +88,11 @@ export default function create(ctx) {
|
||||
},
|
||||
description:
|
||||
`<table class="cesium-infoBox-defaultTable"><tbody>` +
|
||||
`<tr><th>Event</th><td>${title}</td></tr>` +
|
||||
`<tr><th>Category</th><td>${category}</td></tr>` +
|
||||
`<tr><th>Event</th><td>${lib.escapeHtml(title)}</td></tr>` +
|
||||
`<tr><th>Category</th><td>${lib.escapeHtml(category)}</td></tr>` +
|
||||
`<tr><th>Last report</th><td>${dateTxt}</td></tr>` +
|
||||
`</tbody></table>` +
|
||||
(link ? `<p><a href="${link}" target="_blank" rel="noopener">Open event on EONET ↗</a></p>` : '') +
|
||||
(link ? `<p><a href="${lib.escapeHtml(link)}" target="_blank" rel="noopener">Open event on EONET ↗</a></p>` : '') +
|
||||
`<p style="opacity:.7">Active wildfire event from NASA EONET (real data).</p>`,
|
||||
});
|
||||
count++;
|
||||
|
||||
@ -64,7 +64,8 @@ export default function create(ctx) {
|
||||
const depth = coords[2];
|
||||
const mag = p.mag;
|
||||
const timeMs = p.time;
|
||||
const place = p.place || 'Unknown location';
|
||||
const place = p.place || 'Unknown location'; // raw: used in name/label (text contexts)
|
||||
const url = lib.safeUrl(p.url);
|
||||
const size = baseSize(mag);
|
||||
const color = magColor(mag);
|
||||
const recentAtBuild = typeof timeMs === 'number' && (Date.now() - timeMs) < 3_600_000;
|
||||
@ -93,11 +94,11 @@ export default function create(ctx) {
|
||||
description:
|
||||
`<table class="cesium-infoBox-defaultTable"><tbody>` +
|
||||
`<tr><th>Magnitude</th><td>M${mag.toFixed(1)}</td></tr>` +
|
||||
`<tr><th>Place</th><td>${place}</td></tr>` +
|
||||
`<tr><th>Place</th><td>${lib.escapeHtml(place)}</td></tr>` +
|
||||
`<tr><th>Depth</th><td>${depth != null ? depth.toFixed(1) + ' km' : '—'}</td></tr>` +
|
||||
`<tr><th>Time (UTC)</th><td>${timeMs != null ? new Date(timeMs).toISOString().replace('T', ' ').replace('.000Z', ' UTC') : '—'}</td></tr>` +
|
||||
`</tbody></table>` +
|
||||
(p.url ? `<p><a href="${p.url}" target="_blank" rel="noopener">USGS event page ↗</a></p>` : ''),
|
||||
(url ? `<p><a href="${lib.escapeHtml(url)}" target="_blank" rel="noopener">USGS event page ↗</a></p>` : ''),
|
||||
});
|
||||
|
||||
// Labels only for the notable quakes, to keep the globe legible.
|
||||
|
||||
18
js/lib.js
18
js/lib.js
@ -77,6 +77,24 @@ export function altitudeColor(CONFIG, altMeters) {
|
||||
return c.aircraftHigh;
|
||||
}
|
||||
|
||||
// Escape a string for safe interpolation into InfoBox description HTML.
|
||||
// Feeds like USGS/EONET are trusted, but this is defense-in-depth for the
|
||||
// public deploy (and the InfoBox is a real HTML sink).
|
||||
export function escapeHtml(s) {
|
||||
return String(s == null ? '' : s)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
// Only allow http(s) URLs into href attributes (blocks javascript:/data: URLs).
|
||||
export function safeUrl(u) {
|
||||
const s = String(u == null ? '' : u).trim();
|
||||
return /^https?:\/\//i.test(s) ? s : '';
|
||||
}
|
||||
|
||||
// CSS hex → Cesium.Color, optional alpha.
|
||||
export function cz(hex, alpha) {
|
||||
const col = Cesium.Color.fromCssColorString(hex);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user