fix: Sanitize all innerHTML dynamic values to prevent XSS
Add centralized escapeHtml() utility and apply it to every dynamic value inserted via innerHTML/template literals across the frontend. Data from VRS JSON sources and external CSV files (airline names, countries) flows through the backend as arbitrary strings that could contain HTML. While Go's json.Marshal escapes < > &, JavaScript's JSON.parse reverses those escapes before the values reach innerHTML — enabling script injection. Affected modules: aircraft-manager, ui-manager, callsign-manager, map-manager, and the 3D radar labels in app.js. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
30fcf1a58e
commit
4a0a993e81
6 changed files with 95 additions and 57 deletions
|
|
@ -1,4 +1,6 @@
|
|||
// Aircraft marker and data management module
|
||||
import { escapeHtml } from './html-utils.js';
|
||||
|
||||
export class AircraftManager {
|
||||
constructor(map, callsignManager = null) {
|
||||
this.map = map;
|
||||
|
|
@ -437,67 +439,68 @@ export class AircraftManager {
|
|||
const distance = this.calculateDistance(aircraft);
|
||||
const distanceKm = distance ? (distance * 1.852).toFixed(1) : 'N/A';
|
||||
|
||||
const esc = escapeHtml;
|
||||
return `
|
||||
<div class="aircraft-popup">
|
||||
<div class="popup-header">
|
||||
<div class="flight-info">
|
||||
<span class="icao-flag">${flag}</span>
|
||||
<span class="flight-id">${aircraft.ICAO24 || 'N/A'}</span>
|
||||
${aircraft.Callsign ? `→ <span class="callsign-loading" data-callsign="${aircraft.Callsign}"><span class="callsign">${aircraft.Callsign}</span></span>` : ''}
|
||||
<span class="icao-flag">${esc(flag)}</span>
|
||||
<span class="flight-id">${esc(aircraft.ICAO24) || 'N/A'}</span>
|
||||
${aircraft.Callsign ? `→ <span class="callsign-loading" data-callsign="${esc(aircraft.Callsign)}"><span class="callsign">${esc(aircraft.Callsign)}</span></span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="popup-details">
|
||||
<div class="detail-row">
|
||||
<strong>Country:</strong> ${country}
|
||||
<strong>Country:</strong> ${esc(country)}
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<strong>Type:</strong> ${type}
|
||||
<strong>Type:</strong> ${esc(type)}
|
||||
</div>
|
||||
${aircraft.TransponderCapability ? `
|
||||
<div class="detail-row">
|
||||
<strong>Transponder:</strong> ${aircraft.TransponderCapability}
|
||||
<strong>Transponder:</strong> ${esc(aircraft.TransponderCapability)}
|
||||
</div>` : ''}
|
||||
${aircraft.SignalQuality ? `
|
||||
<div class="detail-row">
|
||||
<strong>Signal Quality:</strong> ${aircraft.SignalQuality}
|
||||
<strong>Signal Quality:</strong> ${esc(aircraft.SignalQuality)}
|
||||
</div>` : ''}
|
||||
|
||||
|
||||
<div class="detail-grid">
|
||||
<div class="detail-item">
|
||||
<div class="label">Altitude:</div>
|
||||
<div class="value${!altitude ? ' no-data' : ''}">${altitude ? `${altitude} ft | ${altitudeM} m` : 'N/A'}</div>
|
||||
<div class="value${!altitude ? ' no-data' : ''}">${altitude ? `${esc(altitude)} ft | ${esc(altitudeM)} m` : 'N/A'}</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="label">Squawk:</div>
|
||||
<div class="value${!aircraft.Squawk ? ' no-data' : ''}">${aircraft.Squawk || 'N/A'}</div>
|
||||
<div class="value${!aircraft.Squawk ? ' no-data' : ''}">${esc(aircraft.Squawk) || 'N/A'}</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="label">Speed:</div>
|
||||
<div class="value${!aircraft.GroundSpeed ? ' no-data' : ''}">${aircraft.GroundSpeed !== undefined && aircraft.GroundSpeed !== null ? `${aircraft.GroundSpeed} kt | ${speedKmh} km/h` : 'N/A'}</div>
|
||||
<div class="value${!aircraft.GroundSpeed ? ' no-data' : ''}">${aircraft.GroundSpeed !== undefined && aircraft.GroundSpeed !== null ? `${esc(aircraft.GroundSpeed)} kt | ${esc(speedKmh)} km/h` : 'N/A'}</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="label">Track:</div>
|
||||
<div class="value${aircraft.Track === undefined || aircraft.Track === null ? ' no-data' : ''}">${aircraft.Track !== undefined && aircraft.Track !== null ? `${aircraft.Track}°` : 'N/A'}</div>
|
||||
<div class="value${aircraft.Track === undefined || aircraft.Track === null ? ' no-data' : ''}">${aircraft.Track !== undefined && aircraft.Track !== null ? `${esc(aircraft.Track)}°` : 'N/A'}</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="label">V/Rate:</div>
|
||||
<div class="value${!aircraft.VerticalRate ? ' no-data' : ''}">${aircraft.VerticalRate ? `${aircraft.VerticalRate} ft/min` : 'N/A'}</div>
|
||||
<div class="value${!aircraft.VerticalRate ? ' no-data' : ''}">${aircraft.VerticalRate ? `${esc(aircraft.VerticalRate)} ft/min` : 'N/A'}</div>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<div class="label">Distance:</div>
|
||||
<div class="value${distance ? '' : ' no-data'}">${distanceKm !== 'N/A' ? `${distanceKm} km` : 'N/A'}</div>
|
||||
<div class="value${distance ? '' : ' no-data'}">${distanceKm !== 'N/A' ? `${esc(distanceKm)} km` : 'N/A'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="detail-row">
|
||||
<strong>Position:</strong> ${aircraft.Latitude?.toFixed(4)}°, ${aircraft.Longitude?.toFixed(4)}°
|
||||
<strong>Position:</strong> ${esc(aircraft.Latitude?.toFixed(4))}°, ${esc(aircraft.Longitude?.toFixed(4))}°
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<strong>Messages:</strong> ${aircraft.TotalMessages || 0}
|
||||
<strong>Messages:</strong> ${esc(aircraft.TotalMessages || 0)}
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<strong>Age:</strong> ${this.calculateAge(aircraft).toFixed(1)}s
|
||||
<strong>Age:</strong> ${esc(this.calculateAge(aircraft).toFixed(1))}s
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue