From 90a88f7e01f4d74ad7197cb301a948e2adca0ea8 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Tue, 28 Nov 2023 16:37:18 +0100 Subject: [PATCH] Fix /api/plugins leaking filesystem paths Replace PluginWebApi's requirePath member with npmPackage which is only populated if the package is not marked as private and the requirePath matches the name. This prevents the public /api/plugins endpoint from leaking filesystem paths into the controller. --- packages/controller/src/Controller.ts | 2 +- packages/controller/src/routes.ts | 4 ++-- packages/lib/src/api.ts | 7 ++++++- packages/lib/src/plugin.ts | 6 ++++++ packages/lib/src/plugin_loader.ts | 3 ++- packages/web_ui/src/components/HostsPage.tsx | 3 +-- test/lib/plugin_loader.js | 2 +- 7 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/controller/src/Controller.ts b/packages/controller/src/Controller.ts index ce132d28..05302e82 100644 --- a/packages/controller/src/Controller.ts +++ b/packages/controller/src/Controller.ts @@ -113,7 +113,7 @@ export default class Controller { constructor( clusterLogger: winston.Logger, - pluginInfos: any[], + pluginInfos: lib.PluginNodeEnvInfo[], configPath: string, config: lib.ControllerConfig, diff --git a/packages/controller/src/routes.ts b/packages/controller/src/routes.ts index 708c090b..719584ba 100644 --- a/packages/controller/src/routes.ts +++ b/packages/controller/src/routes.ts @@ -95,7 +95,7 @@ async function getMetrics(req: Request, res: Response, next: any) { function getPlugins(req: Request, res: Response) { let plugins: lib.PluginWebApi[] = []; - for (let pluginInfo of req.app.locals.controller.pluginInfos) { + for (let pluginInfo of (req.app.locals.controller as Controller).pluginInfos) { let name = pluginInfo.name; let loaded = req.app.locals.controller.plugins.has(name); let enabled = loaded && req.app.locals.controller.config.group(pluginInfo.name).get("load_plugin"); @@ -115,7 +115,7 @@ function getPlugins(req: Request, res: Response) { if (web.main === "remoteEntry.js") { web.error = "Incompatible old remoteEntry.js entrypoint."; } - plugins.push({ name, version: pluginInfo.version, enabled, loaded, web, requirePath: pluginInfo.requirePath }); + plugins.push({ name, version: pluginInfo.version, enabled, loaded, web, npmPackage: pluginInfo.npmPackage }); } res.send(plugins); } diff --git a/packages/lib/src/api.ts b/packages/lib/src/api.ts index b9ecc4d0..43289178 100644 --- a/packages/lib/src/api.ts +++ b/packages/lib/src/api.ts @@ -7,5 +7,10 @@ export type PluginWebApi = { main: string; error?: string; }; - requirePath: string; + /** + * NPM package this plugin is published as. Not present if the package + * is private or the path used to load it does not match the name of the + * package. + */ + npmPackage?: string; } diff --git a/packages/lib/src/plugin.ts b/packages/lib/src/plugin.ts index 2f6b71a9..6cf323af 100644 --- a/packages/lib/src/plugin.ts +++ b/packages/lib/src/plugin.ts @@ -35,6 +35,12 @@ export type PluginNodeEnvInfo = PluginDeclaration & { requirePath: string; version: string; manifest: any; + /** + * NPM package this plugin is published as. Not present if the package + * is private or the path used to load it does not match the name of the + * package. + */ + npmPackage?: string; }; export type PluginWebpackEnvInfo = PluginDeclaration & { diff --git a/packages/lib/src/plugin_loader.ts b/packages/lib/src/plugin_loader.ts index e99ac0c2..207e17ed 100644 --- a/packages/lib/src/plugin_loader.ts +++ b/packages/lib/src/plugin_loader.ts @@ -24,7 +24,7 @@ export async function loadPluginInfos(pluginList: Map) { let plugins: libPlugin.PluginNodeEnvInfo[] = []; for (let [pluginName, pluginPath] of pluginList) { let pluginInfo: libPlugin.PluginNodeEnvInfo; - let pluginPackage: { version: string, main?: string }; + let pluginPackage: { name?: string, version: string, main?: string, private?: boolean }; try { pluginInfo = require(pluginPath).default; @@ -47,6 +47,7 @@ export async function loadPluginInfos(pluginList: Map) { pluginInfo.requirePath = pluginPath; pluginInfo.version = pluginPackage.version; + pluginInfo.npmPackage = !pluginPackage.private && pluginPath === pluginPackage.name ? pluginPath : undefined; plugins.push(pluginInfo); } return plugins; diff --git a/packages/web_ui/src/components/HostsPage.tsx b/packages/web_ui/src/components/HostsPage.tsx index 23082644..43532a48 100644 --- a/packages/web_ui/src/components/HostsPage.tsx +++ b/packages/web_ui/src/components/HostsPage.tsx @@ -59,8 +59,7 @@ function GenerateHostTokenButton() { } }, [open]); - // Only install plugins that aren't filesystem paths. Npm modules have max 1 forward slash in their name. - const pluginString = pluginList.map(p => `"${p.requirePath}"`).filter(x => x.split("/").length <= 1).join(" "); + const pluginString = pluginList.filter(p => p.npmPackage).map(p => `"${p.npmPackage}"`).join(" "); return <>