From 1bc76e9e5b8a9acd14a58cd8a1828c25918f162e Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Mon, 20 Jul 2026 00:35:40 -0600 Subject: [PATCH] Reapply "feat(security): enforce service-level access on docker WebSocket handlers" This reverts commit 56169f3278c224feb0285c87de6e939fc3249e8e. --- apps/dokploy/__test__/wss/authorize.test.ts | 18 ++++++++++++++++++ .../dashboard/compose/general/actions.tsx | 1 + .../docker/terminal/docker-terminal.tsx | 4 +++- .../libsql/general/show-general-libsql.tsx | 1 + .../mariadb/general/show-general-mariadb.tsx | 1 + .../mongo/general/show-general-mongo.tsx | 1 + .../mysql/general/show-general-mysql.tsx | 1 + .../postgres/general/show-general-postgres.tsx | 1 + .../redis/general/show-general-redis.tsx | 1 + .../web-server/docker-terminal-modal.tsx | 3 +++ apps/dokploy/server/wss/authorize.ts | 14 ++++++++++++++ .../server/wss/docker-container-logs.ts | 3 ++- .../server/wss/docker-container-terminal.ts | 3 ++- apps/dokploy/server/wss/docker-stats.ts | 3 ++- 14 files changed, 51 insertions(+), 4 deletions(-) diff --git a/apps/dokploy/__test__/wss/authorize.test.ts b/apps/dokploy/__test__/wss/authorize.test.ts index 45379f684..cb83d1e1e 100644 --- a/apps/dokploy/__test__/wss/authorize.test.ts +++ b/apps/dokploy/__test__/wss/authorize.test.ts @@ -3,9 +3,11 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; // Mock the permission + server helpers the wss authorizer composes. const mockHasPermission = vi.hoisted(() => vi.fn()); const mockFindMember = vi.hoisted(() => vi.fn()); +const mockCheckServiceAccess = vi.hoisted(() => vi.fn()); vi.mock("@dokploy/server/services/permission", () => ({ hasPermission: mockHasPermission, findMemberByUserId: mockFindMember, + checkServiceAccess: mockCheckServiceAccess, })); const mockGetAccessibleServerIds = vi.hoisted(() => vi.fn()); @@ -52,6 +54,22 @@ describe("canAccessDockerOverWss", () => { mockGetAccessibleServerIds.mockResolvedValue(new Set(["srv-1"])); expect(await canAccessDockerOverWss(USER, SESSION, "srv-1")).toBe(true); }); + + it("denies when the container belongs to a service the caller cannot access", async () => { + mockHasPermission.mockResolvedValue(true); + mockCheckServiceAccess.mockRejectedValue(new Error("no access")); + expect(await canAccessDockerOverWss(USER, SESSION, null, "svc-1")).toBe( + false, + ); + }); + + it("allows when the caller has access to the container's service", async () => { + mockHasPermission.mockResolvedValue(true); + mockCheckServiceAccess.mockResolvedValue(undefined); + expect(await canAccessDockerOverWss(USER, SESSION, null, "svc-1")).toBe( + true, + ); + }); }); describe("canAccessTerminalOverWss", () => { diff --git a/apps/dokploy/components/dashboard/compose/general/actions.tsx b/apps/dokploy/components/dashboard/compose/general/actions.tsx index c2b984b39..0ac6d4c68 100644 --- a/apps/dokploy/components/dashboard/compose/general/actions.tsx +++ b/apps/dokploy/components/dashboard/compose/general/actions.tsx @@ -206,6 +206,7 @@ export const ComposeActions = ({ composeId }: Props) => { appName={data?.appName || ""} serverId={data?.serverId || ""} appType={data?.composeType || "docker-compose"} + serviceId={data?.composeId} >