From 691ea169987094278178761af8bd8714402bc4c2 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 7 Oct 2020 00:24:56 +0100 Subject: [PATCH 1/4] Adding a basic sanitise path function and fixing up use cases of appId filesystem paths throughout the system. --- .../server/src/api/controllers/apikeys.js | 6 +++-- .../server/src/api/controllers/application.js | 6 ++--- .../server/src/api/controllers/component.js | 2 +- .../server/src/api/controllers/deploy/aws.js | 5 ++-- .../server/src/api/controllers/instance.js | 5 +++- packages/server/src/api/controllers/static.js | 2 +- .../server/src/api/controllers/view/index.js | 6 ++--- packages/server/src/automations/actions.js | 8 +++--- packages/server/src/electron.js | 2 +- packages/server/src/index.js | 2 +- packages/server/src/utilities/budibaseDir.js | 2 +- .../server/src/utilities/builder/buildPage.js | 2 +- .../utilities/builder/convertCssToFiles.js | 2 +- .../server/src/utilities/builder/getPages.js | 2 +- .../server/src/utilities/builder/index.js | 3 ++- .../src/utilities/builder/listScreens.js | 2 +- .../src/utilities/builder/publicPath.js | 2 +- .../server/src/utilities/createAppPackage.js | 2 +- .../src/utilities/initialiseBudibase.js | 2 +- .../server/src/utilities/sanitisedPath.js | 27 +++++++++++++++++++ packages/server/src/utilities/templates.js | 20 +++++++------- 21 files changed, 73 insertions(+), 37 deletions(-) create mode 100644 packages/server/src/utilities/sanitisedPath.js diff --git a/packages/server/src/api/controllers/apikeys.js b/packages/server/src/api/controllers/apikeys.js index 7b777cefda..14642ac83b 100644 --- a/packages/server/src/api/controllers/apikeys.js +++ b/packages/server/src/api/controllers/apikeys.js @@ -1,4 +1,5 @@ const fs = require("fs") +const { join } = require("../../utilities/sanitisedPath") const readline = require("readline") const { budibaseAppsDir } = require("../../utilities/budibaseDir") const ENV_FILE_PATH = "/.env" @@ -28,8 +29,9 @@ exports.update = async function(ctx) { async function updateValues([key, value]) { let newContent = "" let keyExists = false + let envPath = join(budibaseAppsDir(), ENV_FILE_PATH) const readInterface = readline.createInterface({ - input: fs.createReadStream(`${budibaseAppsDir()}/${ENV_FILE_PATH}`), + input: fs.createReadStream(envPath), output: process.stdout, console: false, }) @@ -47,6 +49,6 @@ async function updateValues([key, value]) { // Add API Key if it doesn't exist in the file at all newContent = `${newContent}\n${key}=${value}` } - fs.writeFileSync(`${budibaseAppsDir()}/${ENV_FILE_PATH}`, newContent) + fs.writeFileSync(envPath, newContent) }) } diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 75d6f9544f..12f2de5957 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -3,12 +3,12 @@ const ClientDb = require("../../db/clientDb") const { getPackageForBuilder, buildPage } = require("../../utilities/builder") const env = require("../../environment") const instanceController = require("./instance") -const { resolve, join } = require("path") const { copy, exists, readFile, writeFile } = require("fs-extra") const { budibaseAppsDir } = require("../../utilities/budibaseDir") const sqrl = require("squirrelly") const setBuilderToken = require("../../utilities/builder/setBuilderToken") const fs = require("fs-extra") +const { join, resolve } = require("../../utilities/sanitisedPath") const { promisify } = require("util") const chmodr = require("chmodr") const { generateAppID, getAppParams } = require("../../db/utils") @@ -116,7 +116,7 @@ exports.delete = async function(ctx) { const db = new CouchDB(ClientDb.name(getClientId(ctx))) const app = await db.get(ctx.params.applicationId) const result = await db.remove(app) - await fs.rmdir(`${budibaseAppsDir()}/${ctx.params.applicationId}`, { + await fs.rmdir(join(budibaseAppsDir(), ctx.params.applicationId), { recursive: true, }) @@ -135,7 +135,7 @@ const createEmptyAppPackage = async (ctx, app) => { ) const appsFolder = budibaseAppsDir() - const newAppFolder = resolve(appsFolder, app._id) + const newAppFolder = resolve(join(appsFolder, app._id)) if (await exists(newAppFolder)) { ctx.throw(400, "App folder already exists for this application") diff --git a/packages/server/src/api/controllers/component.js b/packages/server/src/api/controllers/component.js index fae734b7db..96da08a2df 100644 --- a/packages/server/src/api/controllers/component.js +++ b/packages/server/src/api/controllers/component.js @@ -1,6 +1,6 @@ const CouchDB = require("../../db") const ClientDb = require("../../db/clientDb") -const { resolve, join } = require("path") +const { resolve, join } = require("../../utilities/sanitisedPath") const { budibaseTempDir, budibaseAppsDir, diff --git a/packages/server/src/api/controllers/deploy/aws.js b/packages/server/src/api/controllers/deploy/aws.js index 2f89d97742..c95cd5ab55 100644 --- a/packages/server/src/api/controllers/deploy/aws.js +++ b/packages/server/src/api/controllers/deploy/aws.js @@ -1,4 +1,5 @@ const fs = require("fs") +const { join } = require("../../../utilities/sanitisedPath") const AWS = require("aws-sdk") const fetch = require("node-fetch") const { budibaseAppsDir } = require("../../../utilities/budibaseDir") @@ -108,7 +109,7 @@ exports.uploadAppAssets = async function({ }, }) - const appAssetsPath = `${budibaseAppsDir()}/${appId}/public` + const appAssetsPath = join(budibaseAppsDir(), appId, "public") const appPages = fs.readdirSync(appAssetsPath) @@ -116,7 +117,7 @@ exports.uploadAppAssets = async function({ for (let page of appPages) { // Upload HTML, CSS and JS for each page of the web app - walkDir(`${appAssetsPath}/${page}`, function(filePath) { + walkDir(join(appAssetsPath, page), function(filePath) { const appAssetUpload = prepareUploadForS3({ file: { path: filePath, diff --git a/packages/server/src/api/controllers/instance.js b/packages/server/src/api/controllers/instance.js index c0997596e2..f97eb6cdd3 100644 --- a/packages/server/src/api/controllers/instance.js +++ b/packages/server/src/api/controllers/instance.js @@ -2,6 +2,7 @@ const fs = require("fs") const CouchDB = require("../../db") const client = require("../../db/clientDb") const newid = require("../../db/newid") +const { join } = require("../../utilities/sanitisedPath") const { downloadTemplate } = require("../../utilities/templates") exports.create = async function(ctx) { @@ -34,7 +35,9 @@ exports.create = async function(ctx) { // replicate the template data to the instance DB if (template) { const templatePath = await downloadTemplate(...template.key.split("/")) - const dbDumpReadStream = fs.createReadStream(`${templatePath}/db/dump.txt`) + const dbDumpReadStream = fs.createReadStream( + join(templatePath, "db", "dump.txt") + ) const { ok } = await db.load(dbDumpReadStream) if (!ok) { ctx.throw(500, "Error loading database dump from template.") diff --git a/packages/server/src/api/controllers/static.js b/packages/server/src/api/controllers/static.js index f0388fdb86..3b0c378e50 100644 --- a/packages/server/src/api/controllers/static.js +++ b/packages/server/src/api/controllers/static.js @@ -1,5 +1,5 @@ const send = require("koa-send") -const { resolve, join } = require("path") +const { resolve, join } = require("../../utilities/sanitisedPath") const jwt = require("jsonwebtoken") const fetch = require("node-fetch") const fs = require("fs-extra") diff --git a/packages/server/src/api/controllers/view/index.js b/packages/server/src/api/controllers/view/index.js index d2ce3b0835..145ea31864 100644 --- a/packages/server/src/api/controllers/view/index.js +++ b/packages/server/src/api/controllers/view/index.js @@ -1,7 +1,7 @@ const CouchDB = require("../../../db") const viewTemplate = require("./viewBuilder") const fs = require("fs") -const path = require("path") +const { join } = require("../../../utilities/sanitisedPath") const os = require("os") const exporters = require("./exporters") @@ -105,7 +105,7 @@ const controller = { const filename = `${view.name}.${format}` - fs.writeFileSync(path.join(os.tmpdir(), filename), exportedFile) + fs.writeFileSync(join(os.tmpdir(), filename), exportedFile) ctx.body = { url: `/api/views/export/download/${filename}`, @@ -116,7 +116,7 @@ const controller = { const filename = ctx.params.fileName ctx.attachment(filename) - ctx.body = fs.createReadStream(path.join(os.tmpdir(), filename)) + ctx.body = fs.createReadStream(join(os.tmpdir(), filename)) }, } diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js index 7d86250f56..47dd3aa8bb 100644 --- a/packages/server/src/automations/actions.js +++ b/packages/server/src/automations/actions.js @@ -6,7 +6,7 @@ const createUser = require("./steps/createUser") const environment = require("../environment") const download = require("download") const fetch = require("node-fetch") -const path = require("path") +const { join } = require("../utilities/sanitisedPath") const os = require("os") const fs = require("fs") const Sentry = require("@sentry/node") @@ -43,7 +43,7 @@ async function downloadPackage(name, version, bundleName) { `${AUTOMATION_BUCKET}/${name}/${version}/${bundleName}`, AUTOMATION_DIRECTORY ) - return require(path.join(AUTOMATION_DIRECTORY, bundleName)) + return require(join(AUTOMATION_DIRECTORY, bundleName)) } module.exports.getAction = async function(actionName) { @@ -57,7 +57,7 @@ module.exports.getAction = async function(actionName) { const pkg = MANIFEST.packages[actionName] const bundleName = buildBundleName(pkg.stepId, pkg.version) try { - return require(path.join(AUTOMATION_DIRECTORY, bundleName)) + return require(join(AUTOMATION_DIRECTORY, bundleName)) } catch (err) { return downloadPackage(pkg.stepId, pkg.version, bundleName) } @@ -66,7 +66,7 @@ module.exports.getAction = async function(actionName) { module.exports.init = async function() { // set defaults if (!AUTOMATION_DIRECTORY) { - AUTOMATION_DIRECTORY = path.join(os.homedir(), DEFAULT_DIRECTORY) + AUTOMATION_DIRECTORY = join(os.homedir(), DEFAULT_DIRECTORY) } if (!AUTOMATION_BUCKET) { AUTOMATION_BUCKET = DEFAULT_BUCKET diff --git a/packages/server/src/electron.js b/packages/server/src/electron.js index 636ddc8b46..f4055d8b6b 100644 --- a/packages/server/src/electron.js +++ b/packages/server/src/electron.js @@ -1,5 +1,5 @@ const { app, BrowserWindow, shell, dialog } = require("electron") -const { join } = require("path") +const { join } = require("./utilities/sanitisedPath") const isDev = require("electron-is-dev") const { autoUpdater } = require("electron-updater") const unhandled = require("electron-unhandled") diff --git a/packages/server/src/index.js b/packages/server/src/index.js index 81391edf70..e69e37f6b0 100644 --- a/packages/server/src/index.js +++ b/packages/server/src/index.js @@ -1,4 +1,4 @@ -const { resolve, join } = require("path") +const { resolve, join } = require("./utilities/sanitisedPath") const { homedir } = require("os") const { app } = require("electron") const fixPath = require("fix-path") diff --git a/packages/server/src/utilities/budibaseDir.js b/packages/server/src/utilities/budibaseDir.js index 61044b50b4..22dc0d7efd 100644 --- a/packages/server/src/utilities/budibaseDir.js +++ b/packages/server/src/utilities/budibaseDir.js @@ -1,4 +1,4 @@ -const { join } = require("path") +const { join } = require("./sanitisedPath") const { homedir, tmpdir } = require("os") const env = require("../environment") diff --git a/packages/server/src/utilities/builder/buildPage.js b/packages/server/src/utilities/builder/buildPage.js index 1f9929ab05..7792b50848 100644 --- a/packages/server/src/utilities/builder/buildPage.js +++ b/packages/server/src/utilities/builder/buildPage.js @@ -6,7 +6,7 @@ const { readFile, writeJSON, } = require("fs-extra") -const { join, resolve } = require("path") +const { join, resolve } = require("../sanitisedPath") const sqrl = require("squirrelly") const { convertCssToFiles } = require("./convertCssToFiles") const publicPath = require("./publicPath") diff --git a/packages/server/src/utilities/builder/convertCssToFiles.js b/packages/server/src/utilities/builder/convertCssToFiles.js index 0e7095e603..0105df9cdf 100644 --- a/packages/server/src/utilities/builder/convertCssToFiles.js +++ b/packages/server/src/utilities/builder/convertCssToFiles.js @@ -1,6 +1,6 @@ const crypto = require("crypto") const { ensureDir, emptyDir, writeFile } = require("fs-extra") -const { join } = require("path") +const { join } = require("../sanitisedPath") module.exports.convertCssToFiles = async (publicPagePath, pkg) => { const cssDir = join(publicPagePath, "css") diff --git a/packages/server/src/utilities/builder/getPages.js b/packages/server/src/utilities/builder/getPages.js index a235eb4a9d..7a124d1716 100644 --- a/packages/server/src/utilities/builder/getPages.js +++ b/packages/server/src/utilities/builder/getPages.js @@ -1,5 +1,5 @@ const { readJSON, readdir } = require("fs-extra") -const { join } = require("path") +const { join } = require("../sanitisedPath") module.exports = async appPath => { const pages = {} diff --git a/packages/server/src/utilities/builder/index.js b/packages/server/src/utilities/builder/index.js index 0a68d9725b..2e87d35f9b 100644 --- a/packages/server/src/utilities/builder/index.js +++ b/packages/server/src/utilities/builder/index.js @@ -8,7 +8,8 @@ const { unlink, rmdir, } = require("fs-extra") -const { join, dirname, resolve } = require("path") +const { join, resolve } = require("../sanitisedPath") +const { dirname } = require("path") const env = require("../../environment") const buildPage = require("./buildPage") diff --git a/packages/server/src/utilities/builder/listScreens.js b/packages/server/src/utilities/builder/listScreens.js index 9d881ba4ae..9a9c7cb2d9 100644 --- a/packages/server/src/utilities/builder/listScreens.js +++ b/packages/server/src/utilities/builder/listScreens.js @@ -1,6 +1,6 @@ const { appPackageFolder } = require("../createAppPackage") const { readJSON, readdir, stat } = require("fs-extra") -const { join } = require("path") +const { join } = require("../sanitisedPath") const { keyBy } = require("lodash/fp") module.exports = async (config, appname, pagename) => { diff --git a/packages/server/src/utilities/builder/publicPath.js b/packages/server/src/utilities/builder/publicPath.js index d3426aa276..b9408f026f 100644 --- a/packages/server/src/utilities/builder/publicPath.js +++ b/packages/server/src/utilities/builder/publicPath.js @@ -1,3 +1,3 @@ -const { join } = require("path") +const { join } = require("../sanitisedPath") module.exports = (appPath, pageName) => join(appPath, "public", pageName) diff --git a/packages/server/src/utilities/createAppPackage.js b/packages/server/src/utilities/createAppPackage.js index 6b699a2f32..39647e4db4 100644 --- a/packages/server/src/utilities/createAppPackage.js +++ b/packages/server/src/utilities/createAppPackage.js @@ -1,4 +1,4 @@ -const { resolve } = require("path") +const { resolve } = require("./sanitisedPath") const { cwd } = require("process") const stream = require("stream") const fetch = require("node-fetch") diff --git a/packages/server/src/utilities/initialiseBudibase.js b/packages/server/src/utilities/initialiseBudibase.js index 3d11c852b8..56a2c059d6 100644 --- a/packages/server/src/utilities/initialiseBudibase.js +++ b/packages/server/src/utilities/initialiseBudibase.js @@ -1,5 +1,5 @@ const { exists, readFile, writeFile, ensureDir } = require("fs-extra") -const { join, resolve } = require("path") +const { join, resolve } = require("./sanitisedPath") const Sqrl = require("squirrelly") const uuid = require("uuid") diff --git a/packages/server/src/utilities/sanitisedPath.js b/packages/server/src/utilities/sanitisedPath.js new file mode 100644 index 0000000000..ba8209fc97 --- /dev/null +++ b/packages/server/src/utilities/sanitisedPath.js @@ -0,0 +1,27 @@ +const path = require("path") + +function sanitiseArgs(args) { + let sanitised = [] + for (let arg of args) { + sanitised.push(arg.replace(":", "")) + } + return sanitised +} + +/** + * Exactly the same as path.join but creates a sanitised path. + * @param args Any number of string arguments to add to a path + * @returns {string} The final path ready to use + */ +exports.join = function(...args) { + return path.join(...sanitiseArgs(args)) +} + +/** + * Exactly the same as path.resolve but creates a sanitised path. + * @param args Any number of string arguments to add to a path + * @returns {string} The final path ready to use + */ +exports.resolve = function(...args) { + return path.resolve(...sanitiseArgs(args)) +} diff --git a/packages/server/src/utilities/templates.js b/packages/server/src/utilities/templates.js index 4aef7f7db3..39ae97f839 100644 --- a/packages/server/src/utilities/templates.js +++ b/packages/server/src/utilities/templates.js @@ -1,5 +1,5 @@ -const path = require("path") const fs = require("fs-extra") +const { join } = require("./sanitisedPath") const os = require("os") const fetch = require("node-fetch") const stream = require("stream") @@ -27,10 +27,10 @@ exports.downloadTemplate = async function(type, name) { await streamPipeline( response.body, zlib.Unzip(), - tar.extract(path.join(budibaseAppsDir(), "templates", type)) + tar.extract(join(budibaseAppsDir(), "templates", type)) ) - return path.join(budibaseAppsDir(), "templates", type, name) + return join(budibaseAppsDir(), "templates", type, name) } exports.exportTemplateFromApp = async function({ @@ -39,15 +39,17 @@ exports.exportTemplateFromApp = async function({ instanceId, }) { // Copy frontend files - const appToExport = path.join(os.homedir(), ".budibase", appId, "pages") - const templatesDir = path.join(os.homedir(), ".budibase", "templates") + const appToExport = join(os.homedir(), ".budibase", appId, "pages") + const templatesDir = join(os.homedir(), ".budibase", "templates") fs.ensureDirSync(templatesDir) - const templateOutputPath = path.join(templatesDir, templateName) - fs.copySync(appToExport, `${templateOutputPath}/pages`) + const templateOutputPath = join(templatesDir, templateName) + fs.copySync(appToExport, join(templateOutputPath, "pages")) - fs.ensureDirSync(path.join(templateOutputPath, "db")) - const writeStream = fs.createWriteStream(`${templateOutputPath}/db/dump.txt`) + fs.ensureDirSync(join(templateOutputPath, "db")) + const writeStream = fs.createWriteStream( + join(templateOutputPath, "db", "dump.txt") + ) // perform couch dump const instanceDb = new CouchDB(instanceId) From a6e409a062489e2df5072d8440a46f9bd3d1e298 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 7 Oct 2020 09:58:32 +0100 Subject: [PATCH 2/4] Quick change after diffing and looking through everything. --- packages/server/src/api/controllers/application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 12f2de5957..67b005aace 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -135,7 +135,7 @@ const createEmptyAppPackage = async (ctx, app) => { ) const appsFolder = budibaseAppsDir() - const newAppFolder = resolve(join(appsFolder, app._id)) + const newAppFolder = resolve(appsFolder, app._id) if (await exists(newAppFolder)) { ctx.throw(400, "App folder already exists for this application") From 1d99cc654a4d685d1751207cb24a82f7fbd6691d Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 7 Oct 2020 11:04:36 +0100 Subject: [PATCH 3/4] Adding a regex to allow certain paths with : on windows. --- packages/server/src/utilities/sanitisedPath.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/src/utilities/sanitisedPath.js b/packages/server/src/utilities/sanitisedPath.js index ba8209fc97..a5bfcaf080 100644 --- a/packages/server/src/utilities/sanitisedPath.js +++ b/packages/server/src/utilities/sanitisedPath.js @@ -1,9 +1,11 @@ const path = require("path") +const regex = new RegExp(/:(?![\\/])/g) + function sanitiseArgs(args) { let sanitised = [] for (let arg of args) { - sanitised.push(arg.replace(":", "")) + sanitised.push(arg.replace(regex, "")) } return sanitised } From 1f8f0a044433a2197a77ea0e637012a7c87dd280 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 7 Oct 2020 11:42:28 +0100 Subject: [PATCH 4/4] Sanitising the paths to the couchDB data directories. --- packages/server/src/db/client.js | 9 ++++++++- packages/server/src/utilities/sanitisedPath.js | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/server/src/db/client.js b/packages/server/src/db/client.js index a3051eea7f..6bef09740a 100644 --- a/packages/server/src/db/client.js +++ b/packages/server/src/db/client.js @@ -2,6 +2,7 @@ const PouchDB = require("pouchdb") const replicationStream = require("pouchdb-replication-stream") const allDbs = require("pouchdb-all-dbs") const { budibaseAppsDir } = require("../utilities/budibaseDir") +const { sanitise } = require("../utilities/sanitisedPath") const env = require("../environment") const COUCH_DB_URL = env.COUCH_DB_URL || `leveldb://${budibaseAppsDir()}/.data/` @@ -26,4 +27,10 @@ const Pouch = PouchDB.defaults(POUCH_DB_DEFAULTS) allDbs(Pouch) -module.exports = Pouch +function PouchWrapper(instance) { + Pouch.apply(this, [sanitise(instance)]) +} + +PouchWrapper.prototype = Object.create(Pouch.prototype) + +module.exports = PouchWrapper diff --git a/packages/server/src/utilities/sanitisedPath.js b/packages/server/src/utilities/sanitisedPath.js index a5bfcaf080..c2c304aad9 100644 --- a/packages/server/src/utilities/sanitisedPath.js +++ b/packages/server/src/utilities/sanitisedPath.js @@ -27,3 +27,12 @@ exports.join = function(...args) { exports.resolve = function(...args) { return path.resolve(...sanitiseArgs(args)) } + +/** + * Sanitise a single string + * @param string input string to sanitise + * @returns {string} the final sanitised string + */ +exports.sanitise = function(string) { + return sanitiseArgs([string])[0] +}