From 135b7354c36ecf03f25030bf3e32811eccb9dea7 Mon Sep 17 00:00:00 2001 From: Raymond De Castro Date: Sat, 4 Apr 2026 06:42:48 +0800 Subject: [PATCH 1/2] fix: quote and escape environment variable values Ensure environment variables containing spaces or special characters are correctly handled in generated .env files by quoting and escaping values. --- packages/server/src/utils/builders/compose.ts | 5 ++++- packages/server/src/utils/builders/utils.ts | 10 ++++++++-- packages/server/src/utils/docker/utils.ts | 7 +++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts index 3165706266..fb881af3f7 100644 --- a/packages/server/src/utils/builders/compose.ts +++ b/packages/server/src/utils/builders/compose.ts @@ -8,6 +8,7 @@ import { encodeBase64, getEnvironmentVariablesObject, prepareEnvironmentVariables, + quoteDotenvValue, } from "../docker/utils"; export type ComposeNested = InferResultType< @@ -119,7 +120,9 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => { envContent, compose.environment.project.env, compose.environment.env, - ).join("\n"); + ) + .map(quoteDotenvValue) + .join("\n"); const encodedContent = encodeBase64(envFileContent); return ` diff --git a/packages/server/src/utils/builders/utils.ts b/packages/server/src/utils/builders/utils.ts index 97ea69ef8c..894d076900 100644 --- a/packages/server/src/utils/builders/utils.ts +++ b/packages/server/src/utils/builders/utils.ts @@ -1,5 +1,9 @@ import { dirname, join } from "node:path"; -import { encodeBase64, prepareEnvironmentVariables } from "../docker/utils"; +import { + encodeBase64, + prepareEnvironmentVariables, + quoteDotenvValue, +} from "../docker/utils"; export const createEnvFileCommand = ( directory: string, @@ -11,7 +15,9 @@ export const createEnvFileCommand = ( env, projectEnv, environmentEnv, - ).join("\n"); + ) + .map(quoteDotenvValue) + .join("\n"); const encodedContent = encodeBase64(envFileContent || ""); const envFilePath = join(dirname(directory), ".env"); diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index dd645cd1bf..a41733d7dd 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -408,6 +408,13 @@ export const prepareEnvironmentVariables = ( return resolvedVars; }; +export const quoteDotenvValue = (pair: string): string => { + const i = pair.indexOf("="); + if (i === -1) return pair; + const value = pair.substring(i + 1).replace(/\\/g, "\\\\").replace(/"/g, '\\"'); + return `${pair.substring(0, i)}="${value}"`; +}; + export const prepareEnvironmentVariablesForShell = ( serviceEnv: string | null, projectEnv?: string | null, From 7be13d389aa09f68206ef70384e50fc7a4977545 Mon Sep 17 00:00:00 2001 From: Raymond De Castro Date: Wed, 8 Apr 2026 08:20:26 +0800 Subject: [PATCH 2/2] fix: enhance quoting and escaping for environment variable values Properly escape special characters such as dollar signs, newlines, and backslashes to prevent unintended variable expansion and ensure correct .env file formatting. --- apps/dokploy/__test__/env/environment.test.ts | 45 +++++++++++++++++++ packages/server/src/utils/docker/utils.ts | 19 ++++++-- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/apps/dokploy/__test__/env/environment.test.ts b/apps/dokploy/__test__/env/environment.test.ts index 24ef18b009..6f7d71f5c1 100644 --- a/apps/dokploy/__test__/env/environment.test.ts +++ b/apps/dokploy/__test__/env/environment.test.ts @@ -1,6 +1,7 @@ import { prepareEnvironmentVariables, prepareEnvironmentVariablesForShell, + quoteDotenvValue, } from "@dokploy/server/index"; import { describe, expect, it } from "vitest"; @@ -363,6 +364,50 @@ SIMPLE=\${{environment.SIMPLE_VAR}} }); }); +describe("quoteDotenvValue (dotenv value quoting)", () => { + it("wraps a simple value in double quotes", () => { + expect(quoteDotenvValue("KEY=value")).toBe('KEY="value"'); + }); + + it("returns pair unchanged when no = present", () => { + expect(quoteDotenvValue("NOEQUALS")).toBe("NOEQUALS"); + }); + + it("handles empty value", () => { + expect(quoteDotenvValue("KEY=")).toBe('KEY=""'); + }); + + it("escapes backslashes", () => { + expect(quoteDotenvValue("PATH=C:\\Users\\docs")).toBe( + 'PATH="C:\\\\Users\\\\docs"', + ); + }); + + it("escapes double quotes in values", () => { + expect(quoteDotenvValue('MSG=say "hello"')).toBe( + 'MSG="say \\"hello\\""', + ); + }); + + it("escapes literal newlines from dotenv expansion", () => { + expect(quoteDotenvValue("MSG=line1\nline2")).toBe('MSG="line1\\nline2"'); + }); + + it("escapes carriage returns", () => { + expect(quoteDotenvValue("MSG=line1\rline2")).toBe('MSG="line1\\rline2"'); + }); + + it("escapes dollar signs to prevent variable expansion", () => { + expect(quoteDotenvValue("PRICE=$100")).toBe('PRICE="\\$100"'); + }); + + it("handles value with multiple special characters", () => { + expect(quoteDotenvValue('COMPLEX=a\\b"c\n$d')).toBe( + 'COMPLEX="a\\\\b\\"c\\n\\$d"', + ); + }); +}); + describe("prepareEnvironmentVariablesForShell (shell escaping)", () => { it("escapes single quotes in environment variable values", () => { const serviceEnv = ` diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts index a41733d7dd..e6f7bb5c23 100644 --- a/packages/server/src/utils/docker/utils.ts +++ b/packages/server/src/utils/docker/utils.ts @@ -408,11 +408,22 @@ export const prepareEnvironmentVariables = ( return resolvedVars; }; +const DOTENV_ESCAPE_MAP: Record = { + "\\": "\\\\", + '"': '\\"', + "\n": "\\n", + "\r": "\\r", + $: "\\$", +}; + export const quoteDotenvValue = (pair: string): string => { - const i = pair.indexOf("="); - if (i === -1) return pair; - const value = pair.substring(i + 1).replace(/\\/g, "\\\\").replace(/"/g, '\\"'); - return `${pair.substring(0, i)}="${value}"`; + const eqIndex = pair.indexOf("="); + if (eqIndex === -1) return pair; + const key = pair.substring(0, eqIndex); + const value = pair + .substring(eqIndex + 1) + .replace(/[\\"$\n\r]/g, (ch) => DOTENV_ESCAPE_MAP[ch] ?? ch); + return `${key}="${value}"`; }; export const prepareEnvironmentVariablesForShell = (