fix: close remaining backup credential and FTPS bypasses

This commit is contained in:
github-actions[bot] 2026-09-04 23:01:38 +00:00 committed by Furox88
parent 2feccc3912
commit c14cbcec71
3 changed files with 87 additions and 32 deletions

View File

@ -332,7 +332,18 @@ describe("FTP TLS certificate verification", () => {
endpoint: "storage.example.com",
};
test.each(["--ftp-no-check-certificate", "--no-check-certificate"])(
test.each([
"--ftp-no-check-certificate",
"--ftp-no-check-certificate=true",
"--ftp-no-check-certificate=TRUE",
"--ftp-no-check-certificate=1",
"--ftp-no-check-certificate=t",
"--no-check-certificate",
"--no-check-certificate=true",
"--no-check-certificate=TRUE",
"--no-check-certificate=1",
"--no-check-certificate=t",
])(
"rejects certificate-verification bypass %s at schema validation",
(flag) => {
expect(
@ -344,28 +355,38 @@ describe("FTP TLS certificate verification", () => {
},
);
test.each(["--ftp-no-check-certificate", "--no-check-certificate"])(
"rejects certificate-verification bypass %s at runtime",
async (flag) => {
await expect(
getRclonePathAndFlags(
destination({
provider: RCLONE_DESTINATION_PROVIDERS.FTP,
endpoint: "storage.example.com",
accessKey: "backup-user",
secretAccessKey: "",
region: "",
bucket: "backups",
additionalFlags: ["--ftp-explicit-tls", flag],
}),
),
).rejects.toThrow("FTP TLS certificate verification cannot be disabled");
},
);
test.each([
"--ftp-no-check-certificate",
"--ftp-no-check-certificate=TRUE",
"--ftp-no-check-certificate=1",
"--no-check-certificate",
"--no-check-certificate=TRUE",
"--no-check-certificate=1",
])("rejects certificate-verification bypass %s at runtime", async (flag) => {
await expect(
getRclonePathAndFlags(
destination({
provider: RCLONE_DESTINATION_PROVIDERS.FTP,
endpoint: "storage.example.com",
accessKey: "backup-user",
secretAccessKey: "",
region: "",
bucket: "backups",
additionalFlags: ["--ftp-explicit-tls", flag],
}),
),
).rejects.toThrow("FTP TLS certificate verification cannot be disabled");
});
test.each([
"--ftp-no-check-certificate=false",
"--ftp-no-check-certificate=FALSE",
"--ftp-no-check-certificate=0",
"--ftp-no-check-certificate=f",
"--no-check-certificate=false",
"--no-check-certificate=FALSE",
"--no-check-certificate=0",
"--no-check-certificate=f",
])("allows explicitly safe certificate flag %s", (flag) => {
expect(
apiCreateDestination.safeParse({
@ -374,4 +395,16 @@ describe("FTP TLS certificate verification", () => {
}).success,
).toBe(true);
});
test.each([
"--ftp-explicit-tls=TRUE",
"--ftp-explicit-tls=1",
"--ftp-explicit-tls=t",
])("accepts pflag-compatible TLS true value %s", (flag) => {
expect(
apiCreateDestination.safeParse({
...input,
additionalFlags: [flag],
}).success,
).toBe(true);
});
});

View File

@ -42,12 +42,32 @@ export const FTP_CERTIFICATE_VERIFICATION_REQUIRED_ERROR =
export const SFTP_HOST_KEY_REQUIRED_ERROR =
"SFTP destinations must verify the server host key. Add --sftp-known-hosts-file=/path/to/known_hosts.";
const parseBooleanFlagValue = (
flag: string,
flagName: string,
): boolean | undefined => {
if (flag === flagName) return true;
const prefix = `${flagName}=`;
if (!flag.startsWith(prefix)) return undefined;
const value = flag.slice(prefix.length).toLowerCase();
if (["1", "t", "true"].includes(value)) return true;
if (["0", "f", "false"].includes(value)) return false;
return undefined;
};
const getBooleanFlagValues = (flags: readonly string[], flagName: string) =>
flags
.filter((flag) => flag === flagName || flag.startsWith(`${flagName}=`))
.map((flag) => parseBooleanFlagValue(flag, flagName));
const isBooleanFlagEnabled = (
flags: readonly string[],
flagName: string,
): boolean =>
(flags.includes(flagName) || flags.includes(`${flagName}=true`)) &&
!flags.includes(`${flagName}=false`);
): boolean => {
const values = getBooleanFlagValues(flags, flagName);
return values.length > 0 && values.every((value) => value === true);
};
export const getFtpTlsState = (flags: readonly string[] | null | undefined) => {
const values = flags ?? [];
@ -62,7 +82,13 @@ export const hasDisabledFtpCertificateVerification = (
): boolean => {
const values = flags ?? [];
return ["--ftp-no-check-certificate", "--no-check-certificate"].some(
(flag) => values.includes(flag) || values.includes(`${flag}=true`),
(flagName) => {
const matchingValues = getBooleanFlagValues(values, flagName);
return (
matchingValues.length > 0 &&
matchingValues.some((value) => value !== false)
);
},
);
};

View File

@ -4,6 +4,7 @@ import { join } from "node:path";
import { IS_CLOUD, paths } from "@dokploy/server/constants";
import type { Destination } from "@dokploy/server/services/destination";
import { quote } from "shell-quote";
import { getSafeRcloneErrorMessage } from "../backups/redact";
import { getRclonePathAndFlags } from "../backups/utils";
import { execAsync } from "../process/execAsync";
@ -146,14 +147,9 @@ export const restoreWebServerBackup = async (
await execAsync(`rm -rf ${quote([tempDir])}`);
}
} catch (error) {
console.error(error);
emit(
`Error: ${
error instanceof Error
? error.message
: "Error restoring web server backup"
}`,
);
throw error;
const safeErrorMessage = getSafeRcloneErrorMessage(error);
console.error("Restore error:", safeErrorMessage);
emit(`Error: ${safeErrorMessage}`);
throw new Error(safeErrorMessage);
}
};