From 92310ddb143c8e67ca95eb7db661838a76579f2e Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Mon, 20 Jul 2026 16:49:48 -0600 Subject: [PATCH] fix(security): base64-encode remote traefik YAML and escape config paths writeTraefikConfigRemote piped the stringified YAML through echo '...' where a single quote in any label/host/serviceName broke out (GHSA-478p). Encode it as base64 like the other writers, and quote() every configPath used in remote rm/cat/redirect commands (including the input.path-derived ones). --- .../server/src/utils/traefik/application.ts | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/server/src/utils/traefik/application.ts b/packages/server/src/utils/traefik/application.ts index 101574ed5..3da847f59 100644 --- a/packages/server/src/utils/traefik/application.ts +++ b/packages/server/src/utils/traefik/application.ts @@ -3,6 +3,7 @@ import path from "node:path"; import { createInterface } from "node:readline"; import { paths } from "@dokploy/server/constants"; import type { Domain } from "@dokploy/server/services/domain"; +import { quote } from "shell-quote"; import { parse, stringify } from "yaml"; import { encodeBase64 } from "../docker/utils"; import { execAsync, execAsyncRemote } from "../process/execAsync"; @@ -57,7 +58,7 @@ export const removeTraefikConfig = async ( try { const { DYNAMIC_TRAEFIK_PATH } = paths(!!serverId); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); - const command = `rm -f ${configPath}`; + const command = `rm -f ${quote([configPath])}`; if (serverId) { await execAsyncRemote(serverId, command); @@ -76,7 +77,7 @@ export const removeTraefikConfigRemote = async ( try { const { DYNAMIC_TRAEFIK_PATH } = paths(true); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); - await execAsyncRemote(serverId, `rm -f ${configPath}`); + await execAsyncRemote(serverId, `rm -f ${quote([configPath])}`); } catch (error) { console.error( `Error removing remote traefik config for ${appName}:`, @@ -106,7 +107,10 @@ export const loadOrCreateConfigRemote = async ( const fileConfig: FileConfig = { http: { routers: {}, services: {} } }; const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); try { - const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`); + const { stdout } = await execAsyncRemote( + serverId, + `cat ${quote([configPath])}`, + ); if (!stdout) return fileConfig; @@ -133,7 +137,10 @@ export const readRemoteConfig = async (serverId: string, appName: string) => { const { DYNAMIC_TRAEFIK_PATH } = paths(true); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); try { - const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`); + const { stdout } = await execAsyncRemote( + serverId, + `cat ${quote([configPath])}`, + ); if (!stdout) return null; return stdout; } catch { @@ -189,7 +196,10 @@ export const readConfigInPath = async (pathFile: string, serverId?: string) => { const configPath = path.join(pathFile); if (serverId) { - const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`); + const { stdout } = await execAsyncRemote( + serverId, + `cat ${quote([configPath])}`, + ); if (!stdout) return null; return stdout; } @@ -221,7 +231,7 @@ export const writeConfigRemote = async ( const encoded = encodeBase64(traefikConfig); await execAsyncRemote( serverId, - `echo "${encoded}" | base64 -d > "${configPath}"`, + `echo "${encoded}" | base64 -d > ${quote([configPath])}`, ); } catch (e) { console.error("Error saving the YAML config file:", e); @@ -239,7 +249,7 @@ export const writeTraefikConfigInPath = async ( const encoded = encodeBase64(traefikConfig); await execAsyncRemote( serverId, - `echo "${encoded}" | base64 -d > "${configPath}"`, + `echo "${encoded}" | base64 -d > ${quote([configPath])}`, ); } else { fs.writeFileSync(configPath, traefikConfig, "utf8"); @@ -272,7 +282,11 @@ export const writeTraefikConfigRemote = async ( const { DYNAMIC_TRAEFIK_PATH } = paths(true); const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`); const yamlStr = stringify(traefikConfig); - await execAsyncRemote(serverId, `echo '${yamlStr}' > ${configPath}`); + const encoded = encodeBase64(yamlStr); + await execAsyncRemote( + serverId, + `echo "${encoded}" | base64 -d > ${quote([configPath])}`, + ); } catch (e) { console.error("Error saving the YAML config file:", e); }