diff --git a/apps/dokploy/__test__/wss/utils.test.ts b/apps/dokploy/__test__/wss/utils.test.ts index 2b91c830b..209bd5f86 100644 --- a/apps/dokploy/__test__/wss/utils.test.ts +++ b/apps/dokploy/__test__/wss/utils.test.ts @@ -73,10 +73,11 @@ describe("isValidSearch (docker-container-logs)", () => { expect(isValidSearch("")).toBe(true); }); - it("accepts safe printable ASCII", () => { + it("accepts only alphanumeric, space, dot, underscore, hyphen", () => { expect(isValidSearch("error")).toBe(true); expect(isValidSearch("foo bar")).toBe(true); expect(isValidSearch("a-zA-Z0-9_.-")).toBe(true); + expect(isValidSearch("")).toBe(true); }); it("rejects strings longer than 500 chars", () => { @@ -91,10 +92,20 @@ describe("isValidSearch (docker-container-logs)", () => { expect(isValidSearch("a\x19b")).toBe(false); }); - it("search is only used in Node (filter) or escaped for SSH; printable ASCII is allowed", () => { - // search is never concatenated into shell unescaped: local path filters in Node, SSH escapes - expect(isValidSearch("error")).toBe(true); - expect(isValidSearch("foo bar")).toBe(true); + it("rejects command injection vectors in search (search is concatenated into shell)", () => { + // Double-quoted context (SSH line 99): $ and ` execute + expect(isValidSearch("$(whoami)")).toBe(false); + expect(isValidSearch("`id`")).toBe(false); + expect(isValidSearch("$(id)")).toBe(false); + // Single-quoted context (local line 153): ' breaks out + expect(isValidSearch("'$(whoami)'")).toBe(false); + expect(isValidSearch("error'")).toBe(false); + expect(isValidSearch("'; whoami; #")).toBe(false); + // Other shell-metacharacters + expect(isValidSearch("error; id")).toBe(false); + expect(isValidSearch("a|b")).toBe(false); + expect(isValidSearch('error"')).toBe(false); + expect(isValidSearch("a&b")).toBe(false); }); }); diff --git a/apps/dokploy/server/wss/utils.ts b/apps/dokploy/server/wss/utils.ts index 52b65e65d..346093e1b 100644 --- a/apps/dokploy/server/wss/utils.ts +++ b/apps/dokploy/server/wss/utils.ts @@ -37,13 +37,13 @@ export const isValidSince = (since: string): boolean => { /** * Validates the `search` parameter for log filtering. - * Allows only safe printable characters to prevent injection when filtering in Node. + * Search is concatenated into shell commands (SSH path: double quotes; local path: single quotes). + * Only allow alphanumeric, space, dot, underscore, hyphen to prevent $, `, ', " from enabling command injection. * Max length 500. */ export const isValidSearch = (search: string): boolean => { - return /^[\x20-\x21\x23-\x25\x27-\x28\x2A-\x3A\x3D\x3F-\x5B\x5D-\x7B\x7D-\x7E]{0,500}$/.test( - search, - ); + // Space only (not \s) to reject \n, \r, \t and other control chars + return /^[a-zA-Z0-9 ._-]{0,500}$/.test(search); }; /**