From 4164ec1fcbf0229af932e0eecb7923d227d966fc Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Tue, 13 Sep 2022 22:13:46 -0500 Subject: [PATCH] ui: use DOMPurify to sanitize inputs to bootbox --- package-lock.json | 42 ++++++++++++++++++++++++++++++++++++++++-- package.json | 2 ++ src/ide/ui.ts | 39 ++++++++++++++++++++------------------- src/ide/waveform.ts | 3 ++- 4 files changed, 64 insertions(+), 22 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c21a3ab..fd28d09c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "8bitworkshop", - "version": "3.9.1", + "version": "3.10.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "8bitworkshop", - "version": "3.9.1", + "version": "3.10.0", "license": "GPL-3.0", "dependencies": { "@types/chroma-js": "^2.1.3", + "@types/dompurify": "^2.3.4", "@types/emscripten": "^1.39.5", "@types/js-yaml": "^4.0.5", "@wasmer/wasi": "^0.12.0", @@ -17,6 +18,7 @@ "binaryen": "^101.0.0", "chroma-js": "^2.1.2", "clipboard": "^2.0.6", + "dompurify": "^2.4.0", "error-stack-parser": "^2.0.6", "fast-png": "^5.0.4", "file-saver": "^2.0.5", @@ -445,6 +447,14 @@ "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz", "integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g==" }, + "node_modules/@types/dompurify": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.3.4.tgz", + "integrity": "sha512-EXzDatIb5EspL2eb/xPGmaC8pePcTHrkDCONjeisusLFrVfl38Pjea/R0YJGu3k9ZQadSvMqW0WXPI2hEo2Ajg==", + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/emscripten": { "version": "1.39.6", "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.6.tgz", @@ -563,6 +573,11 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/trusted-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", + "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" + }, "node_modules/@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", @@ -1749,6 +1764,11 @@ "webidl-conversions": "^4.0.2" } }, + "node_modules/dompurify": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.0.tgz", + "integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==" + }, "node_modules/dotenv": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", @@ -6980,6 +7000,14 @@ "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz", "integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g==" }, + "@types/dompurify": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.3.4.tgz", + "integrity": "sha512-EXzDatIb5EspL2eb/xPGmaC8pePcTHrkDCONjeisusLFrVfl38Pjea/R0YJGu3k9ZQadSvMqW0WXPI2hEo2Ajg==", + "requires": { + "@types/trusted-types": "*" + } + }, "@types/emscripten": { "version": "1.39.6", "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.6.tgz", @@ -7097,6 +7125,11 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/trusted-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", + "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" + }, "@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", @@ -8017,6 +8050,11 @@ "webidl-conversions": "^4.0.2" } }, + "dompurify": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.0.tgz", + "integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==" + }, "dotenv": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", diff --git a/package.json b/package.json index ab6dae85..4bbf4c82 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "license": "GPL-3.0", "dependencies": { "@types/chroma-js": "^2.1.3", + "@types/dompurify": "^2.3.4", "@types/emscripten": "^1.39.5", "@types/js-yaml": "^4.0.5", "@wasmer/wasi": "^0.12.0", @@ -18,6 +19,7 @@ "binaryen": "^101.0.0", "chroma-js": "^2.1.2", "clipboard": "^2.0.6", + "dompurify": "^2.4.0", "error-stack-parser": "^2.0.6", "fast-png": "^5.0.4", "file-saver": "^2.0.5", diff --git a/src/ide/ui.ts b/src/ide/ui.ts index 76861809..93c3e16f 100644 --- a/src/ide/ui.ts +++ b/src/ide/ui.ts @@ -20,6 +20,7 @@ import { AssetEditorView } from "./views/asseteditor"; import { isMobileDevice } from "./views/baseviews"; import { CallStackView, DebugBrowserView } from "./views/treeviews"; import { saveAs } from "file-saver"; +import DOMPurify = require("dompurify"); // external libs (TODO) declare var Tour, GIF, Octokat; @@ -141,12 +142,12 @@ function alertError(s:string) { setWaitDialog(false); bootbox.alert({ title: ' Alert', - message: s + message: DOMPurify.sanitize(s) }); } function alertInfo(s:string) { setWaitDialog(false); - bootbox.alert(s); + bootbox.alert(DOMPurify.sanitize(s)); } function fatalError(s:string) { alertError(s); @@ -518,7 +519,7 @@ function handleFileUpload(files: FileList) { } else { qs.file = files[0].name; bootbox.confirm({ - message: "Open '" + qs.file + "' as main project file?", + message: "Open '" + DOMPurify.sanitize(qs.file) + "' as main project file?", buttons: { confirm: { label: "Open As New Project" }, cancel: { label: "Include/Link With Project Later" }, @@ -558,7 +559,7 @@ function handleFileUpload(files: FileList) { async function _openLocalDirectory(e) { var pickerfn = window['showDirectoryPicker']; if (!pickerfn) { - bootbox.alert(`This browser can't open local files on your computer, yet. Try Chrome.`); + alertError(`This browser can't open local files on your computer, yet. Try Chrome.`); } var dirHandle = await pickerfn(); var repoid = dirHandle.name; @@ -577,7 +578,7 @@ async function _openLocalDirectory(e) { async function promptUser(message: string) : Promise { return new Promise( (resolve, reject) => { - bootbox.prompt(message, (result) => { + bootbox.prompt(DOMPurify.sanitize(message), (result) => { resolve(result); }); }); @@ -600,7 +601,7 @@ async function getLocalFilesystem(repoid: string) : Promise { granted = await dirHandle.requestPermission(options); } if (granted !== 'granted') { - bootbox.alert(`Could not get permission to access filesystem.`); + alertError(`Could not get permission to access filesystem.`); return; } return { @@ -663,7 +664,7 @@ async function getGithubService() { function getBoundGithubURL() : string { var toks = (repo_id||'').split('/'); if (toks.length != 2) { - alertError("

You are not in a GitHub repository.

Choose one from the pulldown, or Import or Publish one.

"); + alertError("You are not in a GitHub repository. Choose one from the pulldown, or Import or Publish one."); return null; } return 'https://github.com/' + toks[0] + '/' + toks[1]; @@ -701,7 +702,7 @@ async function importProjectFromGithub(githuburl:string, replaceURL:boolean) { }).catch( (e) => { setWaitDialog(false); console.log(e); - alertError("

Could not import " + githuburl + ".

" + e); + alertError("Could not import " + githuburl + "." + e); }); } @@ -710,7 +711,7 @@ async function _loginToGithub(e) { gh.login().then(() => { alertInfo("You are signed in to Github."); }).catch( (e) => { - alertError("

Could not sign in.

" + e); + alertError("Could not sign in." + e); }); } @@ -809,13 +810,13 @@ function confirmCommit(sess) : Promise { // anything changed? if (files.length == 0) { setWaitDialog(false); - bootbox.alert("No files changed."); + alertInfo("No files changed."); return; } // build commit confirm message var msg = ""; for (var f of files) { - msg += f.filename + ": " + f.status; + msg += DOMPurify.sanitize(f.filename) + ": " + f.status; if (f.additions || f.deletions || f.changes) { msg += " (" + f.additions + " additions, " + f.deletions + " deletions, " + f.changes + " changes)"; }; @@ -873,7 +874,7 @@ async function pushChangesToGithub(message:string) { function _deleteRepository() { var ghurl = getBoundGithubURL(); if (!ghurl) return; - bootbox.prompt("

Are you sure you want to delete this repository (" + ghurl + ") from browser storage?

All changes since last commit will be lost.

Type DELETE to proceed.

", (yes) => { + bootbox.prompt("

Are you sure you want to delete this repository (" + DOMPurify.sanitize(ghurl) + ") from browser storage?

All changes since last commit will be lost.

Type DELETE to proceed.

", (yes) => { if (yes.trim().toUpperCase() == "DELETE") { deleteRepository(); } @@ -976,7 +977,7 @@ function _downloadCassetteFile_apple2(e) { var blob = new Blob([audout], {type: "audio/wav"}); saveAs(blob, audpath); stdout += "Then connect your audio output to the cassette input, turn up the volume, and play the audio file."; - alertInfo('

'+stdout+'
'); + alertInfo(stdout); } }); } @@ -1008,7 +1009,7 @@ function _downloadCassetteFile_vcs(e) { let blob = new Blob([audout], {type: "audio/wav"}); saveAs(blob, audpath); stdout += "\nConnect your audio output to the SuperCharger input, turn up the volume, and play the audio file."; - alertInfo('
'+stdout+'
'); + alertInfo(stdout); } }); }); @@ -1037,7 +1038,7 @@ function _revertFile(e) { if (wnd && wnd.setText) { var fn = projectWindows.getActiveID(); $.get( "presets/"+getBasePlatform(platform_id)+"/"+fn, (text) => { - bootbox.confirm("Reset '" + fn + "' to default?", (ok) => { + bootbox.confirm("Reset '" + DOMPurify.sanitize(fn) + "' to default?", (ok) => { if (ok) { wnd.setText(text); } @@ -1056,7 +1057,7 @@ function _deleteFile(e) { var wnd = projectWindows.getActive(); if (wnd && wnd.getPath) { var fn = projectWindows.getActiveID(); - bootbox.confirm("Delete '" + fn + "'?", (ok) => { + bootbox.confirm("Delete '" + DOMPurify.sanitize(fn) + "'?", (ok) => { if (ok) { store.removeItem(fn).then( () => { // if we delete what is selected @@ -1080,7 +1081,7 @@ function _renameFile(e) { if (wnd && wnd.getPath && current_project.getFile(wnd.getPath())) { var fn = projectWindows.getActiveID(); bootbox.prompt({ - title: "Rename '" + fn + "' to?", + title: "Rename '" + DOMPurify.sanitize(fn) + "' to?", value: fn, callback: (newfn) => { var data = current_project.getFile(wnd.getPath()); @@ -1823,8 +1824,8 @@ function addFileToProject(type, ext, linefn) { var wnd = projectWindows.getActive(); if (wnd && wnd.insertText) { bootbox.prompt({ - title:"Add "+type+" File to Project", - value:"filename"+ext, + title:"Add "+DOMPurify.sanitize(type)+" File to Project", + value:"filename"+DOMPurify.sanitize(ext), callback:(filename:string) => { if (filename && filename.trim().length > 0) { if (!checkEnteredFilename(filename)) return; diff --git a/src/ide/waveform.ts b/src/ide/waveform.ts index 96da43cc..4f2fd628 100644 --- a/src/ide/waveform.ts +++ b/src/ide/waveform.ts @@ -1,6 +1,7 @@ import { Toolbar } from "./toolbar"; import { VirtualList } from "../common/vlist"; +import DOMPurify from "dompurify"; const BUILTIN_INPUT_PORTS = [ 'clk', 'reset', @@ -359,7 +360,7 @@ export class WaveformView { //min: 0, //max: meta.len-1, //placeholder: rangestr, - title: `Enter new value for "${meta.label}" (${rangestr}):`, + title: `Enter new value for "${DOMPurify.sanitize(meta.label)}" (${rangestr}):`, callback: (result) => { if (result != null) { var value = parseInt(result);