Fix security issues #38

Merged
stb merged 11 commits from security-fixes into main 2026-11-15 13:42:03 +01:00
Showing only changes of commit 6a64083175 - Show all commits

fix: Escapes location search results

Nominatim results were inserted via innerHTML and loaded as a JSONP script
with an unencoded query. Results are now fetched as JSON, the query is URL
encoded, text is escaped and coordinates must be numeric.
Stefan Bethke 2026-11-15 11:36:49 +01:00

View file

@ -37,15 +37,32 @@ function searchLocation(maxResults) {
searchLimit = 11;
}
var searchScript = document.createElement('script');
searchScript.src = 'https://nominatim.openstreetmap.org/search.php?q=' + searchString + '&format=json&json_callback=parseResult&limit=' + searchLimit;
document.body.appendChild(searchScript);
var searchUrl = 'https://nominatim.openstreetmap.org/search?q=' + encodeURIComponent(searchString)
+ '&format=json&limit=' + encodeURIComponent(searchLimit);
fetch(searchUrl)
.then(function(response) { return response.json(); })
.then(parseResult)
.catch(function() { parseResult([]); });
}
}
// Escape a value for use in HTML text and in double-quoted attributes.
function escapeSearchHtml(value) {
return String(value)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function parseResult(response) {
var searchResultDivs = '';
if (!Array.isArray(response)) {
response = [];
}
if (response.length == searchLimit) {
var lastResultIndex = searchLimit - 1;
var nextSearchLimit = searchLimit + 10;
@ -67,10 +84,15 @@ function parseResult(response) {
// Show the current 10 search results.
for (var i=searchLimit-11; i<lastResultIndex; i++) {
searchResult = response[i];
var lat = Number(searchResult.lat);
var lon = Number(searchResult.lon);
if (!isFinite(lat) || !isFinite(lon)) {
continue;
}
searchResultDivs +=
'<div class="search-result" onClick="gotoLocation(' + searchResult.lat + ', ' + searchResult.lon + ');return false;">' +
'<div class="text-medium">' + searchResult.class + ' / ' + searchResult.type + '</div>' +
'<div>' + searchResult.display_name + '</div>' +
'<div class="search-result" onClick="gotoLocation(' + lat + ', ' + lon + ');return false;">' +
'<div class="text-medium">' + escapeSearchHtml(searchResult.class) + ' / ' + escapeSearchHtml(searchResult.type) + '</div>' +
'<div>' + escapeSearchHtml(searchResult.display_name) + '</div>' +
'</div>';
}