diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts index 68758ca2d9..41da4fbb3c 100644 --- a/apps/dokploy/__test__/traefik/traefik.test.ts +++ b/apps/dokploy/__test__/traefik/traefik.test.ts @@ -1,6 +1,11 @@ import type { ApplicationNested, Domain, Redirect } from "@dokploy/server"; import { createRouterConfig } from "@dokploy/server"; -import { expect, test } from "vitest"; +import { execAsyncRemote } from "@dokploy/server/utils/process/execAsync"; +import { expect, test, vi } from "vitest"; + +vi.mock("@dokploy/server/utils/process/execAsync", () => ({ + execAsyncRemote: vi.fn().mockResolvedValue({ stdout: "", stderr: "" }), +})); const baseApp: ApplicationNested = { railpackVersion: "0.15.4", @@ -174,6 +179,40 @@ test("Web entrypoint on http domain", async () => { expect(router.rule).not.toContain("PathPrefix"); }); +test("Remote Traefik writer preserves shell-sensitive YAML values", async () => { + const { writeTraefikConfigRemote } = await import("@dokploy/server"); + const csp = + "default-src * 'unsafe-inline' 'unsafe-eval' data: blob: https:; frame-ancestors 'self'; object-src 'none';"; + + await writeTraefikConfigRemote( + { + http: { + middlewares: { + "secure-headers": { + headers: { + customResponseHeaders: { + "Content-Security-Policy": csp, + }, + }, + }, + }, + }, + }, + "middlewares", + "server-1", + ); + + const command = vi.mocked(execAsyncRemote).mock.calls.at(-1)?.[1] ?? ""; + const encoded = command.match(/echo "([^"]+)"/)?.[1] ?? ""; + const decoded = Buffer.from(encoded, "base64").toString("utf8"); + + expect(command).toContain('base64 -d > "'); + expect(decoded).toContain("'unsafe-inline'"); + expect(decoded).toContain("'unsafe-eval'"); + expect(decoded).toContain("frame-ancestors 'self'"); + expect(decoded).toContain("object-src 'none'"); +}); + test("Web entrypoint on http domain with custom path", async () => { const router = await createRouterConfig( baseApp, diff --git a/packages/server/src/utils/traefik/application.ts b/packages/server/src/utils/traefik/application.ts index 101574ed5f..1a927dce66 100644 --- a/packages/server/src/utils/traefik/application.ts +++ b/packages/server/src/utils/traefik/application.ts @@ -272,7 +272,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 = Buffer.from(yamlStr).toString("base64"); + await execAsyncRemote( + serverId, + `echo "${encoded}" | base64 -d > "${configPath}"`, + ); } catch (e) { console.error("Error saving the YAML config file:", e); }