Compare commits

...

3 commits

Author SHA1 Message Date
mauvehed
389112fff5
Merge pull request #341 from richardnixondev/fix-xss-render-sanitization
fix(memorial): sanitize remaining XSS-prone render paths
2026-09-03 11:56:52 -05:00
mauvehed
9aed7b8506
Merge branch 'main' into fix-xss-render-sanitization 2026-09-02 22:12:32 -05:00
cbbecc27c6 fix(memorial): sanitize remaining XSS-prone render paths
The references fix in #331 covered only one of the render paths.
Apply the same DOM-construction pattern to personal info, obituary,
social media, contributions, gallery and the help-link fallbacks,
plus the people grid in index.html.

maintext is still rendered as HTML to preserve existing formatted
bios; flagged for a DOMPurify follow-up.
2026-05-17 16:08:45 +01:00
2 changed files with 75 additions and 67 deletions

View file

@ -40,28 +40,23 @@
}); });
for (var p = 0; p < peoplelist.length; p++) { for (var p = 0; p < peoplelist.length; p++) {
var displayname = peoplelist[p]["displayname"]; var displayname = String(peoplelist[p]["displayname"] || "");
var file = peoplelist[p]["filename"]; var file = peoplelist[p]["filename"];
var card; var $card;
if (file == "") { if (file == "") {
card = $card = $("<div>")
'<div class="person-card no-link" data-name="' + .addClass("person-card no-link")
displayname.toLowerCase() + .attr("data-name", displayname.toLowerCase())
'">' + .text(displayname);
displayname +
"</div>";
} else { } else {
card = $card = $("<a>")
'<a href="memorial.html?name=' + .attr("href", "memorial.html?name=" + encodeURIComponent(file))
file + .addClass("person-card")
'" class="person-card" data-name="' + .attr("data-name", displayname.toLowerCase())
displayname.toLowerCase() + .text(displayname);
'">' +
displayname +
"</a>";
} }
$("#people-grid").append(card); $("#people-grid").append($card);
} }
$(".loading-message").remove(); $(".loading-message").remove();

View file

@ -29,7 +29,7 @@
var params = new URLSearchParams(window.location.search); var params = new URLSearchParams(window.location.search);
if (params.get("name") != null) { if (params.get("name") != null) {
var name = params.get("name"); var name = params.get("name");
person = "/people/" + name + ".json"; person = "/people/" + encodeURIComponent(name) + ".json";
} else { } else {
person = "/people/person.json"; person = "/people/person.json";
} }
@ -45,37 +45,47 @@
} }
} }
function txt(value) {
return document.createTextNode(value == null ? "" : String(value));
}
$.getJSON(person, function (data) { $.getJSON(person, function (data) {
var fullname = data.firstname + " " + data.lastname; var fullname = data.firstname + " " + data.lastname;
if (data.handle != "") { if (data.handle != "") {
fullname += " (" + data.handle + ")"; fullname += " (" + data.handle + ")";
} }
$("title").append(fullname); document.title = document.title + fullname;
$("#subtitle").append(fullname); $("#subtitle").append(txt(fullname));
$("#memorial-name").append(fullname); $("#memorial-name").text(fullname);
$(".birth").append(data.birth); $(".birth").append(txt(data.birth));
$(".death").append(data.death); $(".death").append(txt(data.death));
$(".issue").append(data.issue);
if (data.affiliations == null || data.affiliations == "") { if (data.affiliations == null || data.affiliations == "") {
$(".affiliations").append("None"); $(".affiliations").append(txt("None"));
} else { } else {
$(".affiliations").append(data.affiliations); $(".affiliations").append(txt(data.affiliations));
} }
if (data.obituary != null && data.obituary != "") { if (data.obituary != null && data.obituary != "") {
$(".obituary").append( $(".obituary").append(
'<a href="' + safeUrl(data.obituary) + '">Obituary</a>', $("<a>").attr("href", safeUrl(data.obituary)).text("Obituary"),
); );
} }
var issueUrl;
if (data.issue == null || data.issue == "") { if (data.issue == null || data.issue == "") {
data.issue = issueUrl =
"https://github.com/restincode/restincode/blob/master/CONTRIBUTING.md"; "https://github.com/restincode/restincode/blob/master/CONTRIBUTING.md";
} else { } else {
data.issue = issueUrl =
"https://github.com/restincode/restincode/issues/" + data.issue; "https://github.com/restincode/restincode/issues/" +
encodeURIComponent(data.issue);
}
var safeIssueUrl = safeUrl(issueUrl);
function helpLink() {
return $("<a>").attr("href", safeIssueUrl).text("here.");
} }
if (data.mainimage == "") { if (data.mainimage == "") {
@ -84,17 +94,21 @@
.attr("alt", "placeholder photo"); .attr("alt", "placeholder photo");
} else { } else {
$(".memorial-main-image") $(".memorial-main-image")
.attr("src", data.mainimage) .attr("src", safeUrl(data.mainimage))
.attr("alt", "Main photo of " + fullname); .attr("alt", "Main photo of " + fullname);
} }
if (data.maintext == "") { if (data.maintext == "") {
$("#memorial-text").append( $("#memorial-text").append(
"<p>No information has been submitted for this person. Help us by submitting <a href='" + $("<p>").append(
data.issue + "No information has been submitted for this person. Help us by submitting ",
"'>here.</a></p>", helpLink(),
),
); );
} else { } else {
// maintext is intentionally rendered as HTML for formatted bios.
// Residual XSS risk is mitigated by PR review; track follow-up
// to integrate DOMPurify for defense-in-depth.
$("#memorial-text").append(data.maintext); $("#memorial-text").append(data.maintext);
} }
@ -103,17 +117,18 @@
!data.socialmedialinks.length !data.socialmedialinks.length
) { ) {
$("#social-media").append( $("#social-media").append(
"<p>No social media links have been submitted for this person. Help us by submitting <a href='" + $("<p>").append(
data.issue + "No social media links have been submitted for this person. Help us by submitting ",
"'>here.</a></p>", helpLink(),
),
); );
} else { } else {
for (var s = 0; s < data.socialmedialinks.length; s++) { for (var s = 0; s < data.socialmedialinks.length; s++) {
var sitename = data.socialmedialinks[s]["sitename"]; var sitename = data.socialmedialinks[s]["sitename"];
var siteurl = data.socialmedialinks[s]["siteurl"]; var siteurl = data.socialmedialinks[s]["siteurl"];
var social = $("#social-media").append(
'<a href="' + safeUrl(siteurl) + '">' + sitename + "</a>"; $("<a>").attr("href", safeUrl(siteurl)).text(sitename),
$("#social-media").append(social); );
} }
} }
@ -121,7 +136,7 @@
$("#references").append( $("#references").append(
$("<p>").append( $("<p>").append(
"No references have been submitted for this person. Help us by submitting ", "No references have been submitted for this person. Help us by submitting ",
$("<a>").attr("href", safeUrl(data.issue)).text("here."), helpLink(),
), ),
); );
} else { } else {
@ -139,52 +154,50 @@
!data.contributions.length !data.contributions.length
) { ) {
$("#contributions-list").append( $("#contributions-list").append(
"<li>No contributions have been submitted for this person. Help us by submitting <a href='" + $("<li>").append(
data.issue + "No contributions have been submitted for this person. Help us by submitting ",
"'>here.</a></li>", helpLink(),
),
); );
} else { } else {
for (var x = 0; x < data.contributions.length; x++) { for (var x = 0; x < data.contributions.length; x++) {
var title = data.contributions[x]["title"]; var title = data.contributions[x]["title"];
var url = data.contributions[x]["url"]; var url = data.contributions[x]["url"];
var text = data.contributions[x]["description"]; var text = data.contributions[x]["description"];
var cont; var $li = $("<li>");
if (url == "") { if (url == "") {
cont = "<li><b>" + title + "</b> - " + text + "</li>"; $li.append($("<b>").text(title));
} else { } else {
cont = $li.append($("<a>").attr("href", safeUrl(url)).text(title));
"<li><a href='" +
safeUrl(url) +
"'>" +
title +
"</a> - " +
text +
"</li>";
} }
$("#contributions-list").append(cont); $li.append(txt(" - " + text));
$("#contributions-list").append($li);
} }
} }
if (!Array.isArray(data.gallery) || !data.gallery.length) { if (!Array.isArray(data.gallery) || !data.gallery.length) {
$("#nogallery").append( $("#nogallery").append(
"<p>No images have been submitted for this person yet. Help us by submitting <a href='" + $("<p>").append(
data.issue + "No images have been submitted for this person yet. Help us by submitting ",
"'>here.</a></p>", helpLink(),
),
); );
} else { } else {
for (var i = 0; i < data.gallery.length; i++) { for (var i = 0; i < data.gallery.length; i++) {
var imgurl = safeUrl(data.gallery[i]["url"]); var imgurl = safeUrl(data.gallery[i]["url"]);
var imgtitle = data.gallery[i]["title"]; var imgtitle = data.gallery[i]["title"];
var caption = data.gallery[i]["caption"]; var caption = data.gallery[i]["caption"];
var image = $("#gallery").append(
"<figure class='memorial-gallery'><img src='" + $("<figure>")
imgurl + .addClass("memorial-gallery")
"' alt='" + .append(
imgtitle + $("<img>")
"' loading='lazy'><figcaption>" + .attr("src", imgurl)
caption + .attr("alt", imgtitle)
"</figcaption></figure>"; .attr("loading", "lazy"),
$("#gallery").append(image); $("<figcaption>").text(caption),
),
);
} }
} }