diff --git a/packages/backend/src/routers/_default.js b/packages/backend/src/routers/_default.js index 23806b9ef..ebbe2bdfb 100644 --- a/packages/backend/src/routers/_default.js +++ b/packages/backend/src/routers/_default.js @@ -26,6 +26,7 @@ const auth = require('../middleware/auth.js'); const { generate_puter_page_html } = require('../temp/puter_page_loader'); const { Context } = require('../util/context'); const { DB_READ } = require('../services/database/consts'); +const { PathBuilder } = require('../util/pathutil.js'); let auth_user; @@ -246,6 +247,7 @@ router.all('*', async function(req, res, next) { // /assets/ // ------------------------ else if (path.startsWith('/assets/')) { + path = PathBuilder.resolve(path); return res.sendFile(path, { root: __dirname + '../../public' }, function (err) { if (err && err.statusCode) { return res.status(err.statusCode).send('Error /public/') @@ -338,7 +340,7 @@ router.all('*', async function(req, res, next) { // /dist/... else if(path.startsWith('/dist/') || path.startsWith('/src/')){ - path = _path.resolve(path); + path = PathBuilder.resolve(path); return res.sendFile(path, {root: config.assets.gui}, function(err){ if(err && err.statusCode){ return res.status(err.statusCode).send('Error /gui/dist/') @@ -348,6 +350,7 @@ router.all('*', async function(req, res, next) { // All other paths else{ + path = PathBuilder.resolve(path); return res.sendFile(path, {root: _path.join(config.assets.gui, 'src')}, function(err){ if(err && err.statusCode){ return res.status(err.statusCode).send('Error /gui/') @@ -364,7 +367,11 @@ router.all('*', async function(req, res, next) { subdomain === 'draw' || subdomain === 'camera' || subdomain === 'recorder' || subdomain === 'dev-center' || subdomain === 'terminal'){ - let root = _path.join(__dirname, config.defaultjs_asset_path, 'apps', subdomain); + let root = PathBuilder + .add(__dirname) + .add(config.defaultjs_asset_path, { allow_traversal: true }) + .add('apps').add(subdomain) + .build(); if ( subdomain === 'docs' ) root += '/dist'; root = _path.normalize(root); diff --git a/packages/backend/src/util/pathutil.js b/packages/backend/src/util/pathutil.js new file mode 100644 index 000000000..9069dc661 --- /dev/null +++ b/packages/backend/src/util/pathutil.js @@ -0,0 +1,60 @@ +const { AdvancedBase } = require("@heyputer/puter-js-common"); + +/** + * PathBuilder implements the builder pattern for building paths. + * This makes it clear which path fragments are allowed to traverse + * to parent directories. + */ +class PathBuilder extends AdvancedBase { + static MODULES = { + path: require('path'), + } + + constructor() { + super(); + this.path_ = ''; + } + + static create () { + return new PathBuilder(); + } + + static add (fragment, options) { + return PathBuilder.create().add(fragment, options); + } + + static resolve (fragment) { + const p = PathBuilder.create(); + const require = p.require; + const node_path = require('path'); + fragment = node_path.resolve(fragment); + return p.add(fragment).build(); + } + + add (fragment, options) { + options = options || {}; + if ( ! options.allow_traversal ) { + fragment = fragment.replace(/(\.\.\/|\.\.\\)/g, ''); + if ( fragment === '..' ) { + fragment = ''; + } + } + + const require = this.require; + const node_path = require('path'); + + this.path_ = this.path_ + ? node_path.join(this.path_, fragment) + : fragment; + + return this; + } + + build () { + return this.path_; + } +} + +module.exports = { + PathBuilder, +};