Security: add DOM-purification, add 'norefferer' and 'noopener' automatically to links to new tabs

This commit is contained in:
Pieter Vander Vennet 2023-09-20 23:05:08 +02:00
parent 9252aafa2d
commit 3a77c6f33e
5 changed files with 71 additions and 13 deletions

60
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.31.4", "version": "0.33.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.31.4", "version": "0.33.0",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"@rgossiaux/svelte-headlessui": "^1.0.2", "@rgossiaux/svelte-headlessui": "^1.0.2",
@ -18,12 +18,14 @@
"@turf/distance": "^6.5.0", "@turf/distance": "^6.5.0",
"@turf/length": "^6.5.0", "@turf/length": "^6.5.0",
"@turf/turf": "^6.5.0", "@turf/turf": "^6.5.0",
"@types/dompurify": "^3.0.2",
"@types/showdown": "^2.0.0", "@types/showdown": "^2.0.0",
"chart.js": "^3.8.0", "chart.js": "^3.8.0",
"country-language": "^0.1.7", "country-language": "^0.1.7",
"country-to-currency": "^1.0.10", "country-to-currency": "^1.0.10",
"csv-parse": "^5.1.0", "csv-parse": "^5.1.0",
"doctest-ts-improved": "^0.8.8", "doctest-ts-improved": "^0.8.8",
"dompurify": "^3.0.5",
"email-validator": "^2.0.4", "email-validator": "^2.0.4",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"fake-dom": "^1.0.4", "fake-dom": "^1.0.4",
@ -3799,6 +3801,14 @@
"@types/chai": "*" "@types/chai": "*"
} }
}, },
"node_modules/@types/dompurify": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.2.tgz",
"integrity": "sha512-YBL4ziFebbbfQfH5mlC+QTJsvh0oJUrWbmxKMyEdL7emlHJqGR2Qb34TEFKj+VCayBvjKy3xczMFNhugThUsfQ==",
"dependencies": {
"@types/trusted-types": "*"
}
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
@ -3926,6 +3936,11 @@
"resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.0.tgz",
"integrity": "sha512-70xBJoLv+oXjB5PhtA8vo7erjLDp9/qqI63SRHm4REKrwuPOLs8HhXwlZJBJaB4kC18cCZ1UUZ6Fb/PLFW4TCA==" "integrity": "sha512-70xBJoLv+oXjB5PhtA8vo7erjLDp9/qqI63SRHm4REKrwuPOLs8HhXwlZJBJaB4kC18cCZ1UUZ6Fb/PLFW4TCA=="
}, },
"node_modules/@types/trusted-types": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.4.tgz",
"integrity": "sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ=="
},
"node_modules/@types/wikidata-sdk": { "node_modules/@types/wikidata-sdk": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/wikidata-sdk/-/wikidata-sdk-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/wikidata-sdk/-/wikidata-sdk-6.1.0.tgz",
@ -6009,10 +6024,9 @@
} }
}, },
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "2.4.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.5.tgz",
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==", "integrity": "sha512-F9e6wPGtY+8KNMRAVfxeCOHU0/NPWMSENNq4pQctuXRqqdEPW7q3CrLbR5Nse044WwacyjHGOMlvNsBe1y6z9A=="
"optional": true
}, },
"node_modules/domutils": { "node_modules/domutils": {
"version": "1.3.0", "version": "1.3.0",
@ -8394,6 +8408,12 @@
"url": "https://opencollective.com/core-js" "url": "https://opencollective.com/core-js"
} }
}, },
"node_modules/jspdf/node_modules/dompurify": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.7.tgz",
"integrity": "sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==",
"optional": true
},
"node_modules/jsprim": { "node_modules/jsprim": {
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
@ -16125,6 +16145,14 @@
"@types/chai": "*" "@types/chai": "*"
} }
}, },
"@types/dompurify": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.2.tgz",
"integrity": "sha512-YBL4ziFebbbfQfH5mlC+QTJsvh0oJUrWbmxKMyEdL7emlHJqGR2Qb34TEFKj+VCayBvjKy3xczMFNhugThUsfQ==",
"requires": {
"@types/trusted-types": "*"
}
},
"@types/estree": { "@types/estree": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
@ -16252,6 +16280,11 @@
"resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.0.tgz", "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.0.tgz",
"integrity": "sha512-70xBJoLv+oXjB5PhtA8vo7erjLDp9/qqI63SRHm4REKrwuPOLs8HhXwlZJBJaB4kC18cCZ1UUZ6Fb/PLFW4TCA==" "integrity": "sha512-70xBJoLv+oXjB5PhtA8vo7erjLDp9/qqI63SRHm4REKrwuPOLs8HhXwlZJBJaB4kC18cCZ1UUZ6Fb/PLFW4TCA=="
}, },
"@types/trusted-types": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.4.tgz",
"integrity": "sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ=="
},
"@types/wikidata-sdk": { "@types/wikidata-sdk": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/wikidata-sdk/-/wikidata-sdk-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/wikidata-sdk/-/wikidata-sdk-6.1.0.tgz",
@ -17770,10 +17803,9 @@
} }
}, },
"dompurify": { "dompurify": {
"version": "2.4.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.5.tgz",
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==", "integrity": "sha512-F9e6wPGtY+8KNMRAVfxeCOHU0/NPWMSENNq4pQctuXRqqdEPW7q3CrLbR5Nse044WwacyjHGOMlvNsBe1y6z9A=="
"optional": true
}, },
"domutils": { "domutils": {
"version": "1.3.0", "version": "1.3.0",
@ -19559,6 +19591,12 @@
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz",
"integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==", "integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==",
"optional": true "optional": true
},
"dompurify": {
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.7.tgz",
"integrity": "sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==",
"optional": true
} }
} }
}, },
@ -23212,4 +23250,4 @@
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
} }
} }
} }

View file

@ -115,12 +115,14 @@
"@turf/distance": "^6.5.0", "@turf/distance": "^6.5.0",
"@turf/length": "^6.5.0", "@turf/length": "^6.5.0",
"@turf/turf": "^6.5.0", "@turf/turf": "^6.5.0",
"@types/dompurify": "^3.0.2",
"@types/showdown": "^2.0.0", "@types/showdown": "^2.0.0",
"chart.js": "^3.8.0", "chart.js": "^3.8.0",
"country-language": "^0.1.7", "country-language": "^0.1.7",
"country-to-currency": "^1.0.10", "country-to-currency": "^1.0.10",
"csv-parse": "^5.1.0", "csv-parse": "^5.1.0",
"doctest-ts-improved": "^0.8.8", "doctest-ts-improved": "^0.8.8",
"dompurify": "^3.0.5",
"email-validator": "^2.0.4", "email-validator": "^2.0.4",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"fake-dom": "^1.0.4", "fake-dom": "^1.0.4",

View file

@ -113,6 +113,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
readonly floors: Store<string[]> readonly floors: Store<string[]>
constructor(layout: LayoutConfig) { constructor(layout: LayoutConfig) {
Utils.initDomPurify()
this.layout = layout this.layout = layout
this.featureSwitches = new FeatureSwitchState(layout) this.featureSwitches = new FeatureSwitchState(layout)
this.guistate = new MenuState( this.guistate = new MenuState(

View file

@ -2,12 +2,18 @@
/** /**
* Given an HTML string, properly shows this * Given an HTML string, properly shows this
*/ */
import DOMPurify from 'dompurify';
export let src: string export let src: string
let cleaned = DOMPurify.sanitize(src, { USE_PROFILES: { html: true },
ADD_ATTR: ['target'] // Don't remove target='_blank'. Note that Utils.initDomPurify does add a hook which automatically adds 'rel=noopener'
});
let htmlElem: HTMLElement let htmlElem: HTMLElement
$: { $: {
if (htmlElem) { if (htmlElem) {
htmlElem.innerHTML = src htmlElem.innerHTML = cleaned
} }
} }

View file

@ -1,4 +1,5 @@
import colors from "./assets/colors.json" import colors from "./assets/colors.json"
import DOMPurify from "dompurify"
export class Utils { export class Utils {
/** /**
@ -25,6 +26,16 @@ Note that these values can be prepare with javascript in the theme by using a [c
` `
public static readonly imageExtensions = new Set(["jpg", "png", "svg", "jpeg", ".gif"]) public static readonly imageExtensions = new Set(["jpg", "png", "svg", "jpeg", ".gif"])
public static initDomPurify() {
DOMPurify.addHook("afterSanitizeAttributes", function (node) {
// set all elements owning target to target=_blank + add noopener noreferrer
if ("target" in node) {
node.setAttribute("target", "_blank")
node.setAttribute("rel", "noopener noreferrer")
}
})
}
public static readonly special_visualizations_importRequirementDocs = `#### Importing a dataset into OpenStreetMap: requirements public static readonly special_visualizations_importRequirementDocs = `#### Importing a dataset into OpenStreetMap: requirements
If you want to import a dataset, make sure that: If you want to import a dataset, make sure that: