diff --git a/apps/dokploy/__test__/backups/redact-credentials.test.ts b/apps/dokploy/__test__/backups/redact-credentials.test.ts index 866ceeda8..ec723c547 100644 --- a/apps/dokploy/__test__/backups/redact-credentials.test.ts +++ b/apps/dokploy/__test__/backups/redact-credentials.test.ts @@ -2,6 +2,7 @@ import { getSafeRcloneErrorMessage, redactRcloneCredentials, } from "@dokploy/server/utils/backups/redact"; +import { quote } from "shell-quote"; import { describe, expect, it } from "vitest"; describe("redactRcloneCredentials (#4621)", () => { @@ -84,4 +85,15 @@ describe("redactRcloneCredentials (#4621)", () => { } expect(safe.match(/\[REDACTED\]/g)?.length).toBe(5); }); + it("should fully redact shell-quote output with embedded quotes and whitespace", () => { + const secret = "PART_A' PART_B\" $PART_C;\\PART_D"; + const cmd = `rclone lsf --s3-secret-access-key=${quote([secret])} --s3-region=us-east-1 :s3:bucket`; + const redacted = redactRcloneCredentials(cmd); + + for (const fragment of ["PART_A", "PART_B", "PART_C", "PART_D"]) { + expect(redacted).not.toContain(fragment); + } + expect(redacted).toContain('--s3-secret-access-key="[REDACTED]"'); + expect(redacted).toContain("--s3-region=us-east-1"); + }); }); diff --git a/apps/dokploy/__test__/utils/backups.test.ts b/apps/dokploy/__test__/utils/backups.test.ts index e9ad6b6d2..192633f3d 100644 --- a/apps/dokploy/__test__/utils/backups.test.ts +++ b/apps/dokploy/__test__/utils/backups.test.ts @@ -407,4 +407,21 @@ describe("FTP TLS certificate verification", () => { }).success, ).toBe(true); }); + test("forces certificate verification on after user flags", async () => { + const result = await getRclonePathAndFlags( + destination({ + provider: RCLONE_DESTINATION_PROVIDERS.FTP, + endpoint: "storage.example.com", + accessKey: "backup-user", + secretAccessKey: "", + region: "", + bucket: "backups", + additionalFlags: ["--ftp-explicit-tls"], + }), + ); + expect(result.flags.slice(-2)).toEqual([ + "--ftp-no-check-certificate=false", + "--no-check-certificate=false", + ]); + }); }); diff --git a/packages/server/src/utils/backups/redact.ts b/packages/server/src/utils/backups/redact.ts index 2eb121b71..6caf179d4 100644 --- a/packages/server/src/utils/backups/redact.ts +++ b/packages/server/src/utils/backups/redact.ts @@ -5,7 +5,7 @@ */ export const redactRcloneCredentials = (command: string): string => { return command.replace( - /(--(?:s3-access-key-id|s3-secret-access-key|ftp-pass|sftp-pass|sftp-key-file-pass)=)(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[^\s]+)/g, + /(--(?:s3-access-key-id|s3-secret-access-key|ftp-pass|sftp-pass|sftp-key-file-pass)=)(?:(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\\[^\r\n]|[^\s"'\\])+)/g, '$1"[REDACTED]"', ); }; diff --git a/packages/server/src/utils/backups/utils.ts b/packages/server/src/utils/backups/utils.ts index 36b3cdf5f..71d434a8f 100644 --- a/packages/server/src/utils/backups/utils.ts +++ b/packages/server/src/utils/backups/utils.ts @@ -197,6 +197,14 @@ export const getRclonePathAndFlags = async ( flags.push(`--${backend}-pass=${quote([obscuredPassword])}`); } flags.push(...additionalFlags); + if (provider === RCLONE_DESTINATION_PROVIDERS.FTP) { + // CLI options override RCLONE_* environment defaults. Keep TLS + // certificate verification enabled on the execution host. + flags.push( + "--ftp-no-check-certificate=false", + "--no-check-certificate=false", + ); + } return { flags, path: `:${backend}:${joinRclonePath(destination.bucket, path)}`,