From 1e354a3cf2b8123f13db880a5c5f33311a470635 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Mon, 20 Jul 2026 00:48:20 -0600 Subject: [PATCH] fix(security): check service access before server in wss authz, and pass serviceId from log views - Reorder canAccessDockerOverWss: docker permission -> service access (if serviceId) -> server access, so the service grant is the primary signal. - Pass serviceId from the service log views (application + postgres/mysql/mariadb/ mongo/redis/libsql + compose) so container logs opened from a service page are gated by checkServiceAccess, matching the terminal path. --- .../dashboard/application/logs/show.tsx | 4 +++- .../dashboard/compose/logs/show-stack.tsx | 8 ++++++- .../dashboard/compose/logs/show.tsx | 3 +++ .../dashboard/docker/logs/docker-logs-id.tsx | 8 ++++++- .../services/application/[applicationId].tsx | 1 + .../services/compose/[composeId].tsx | 2 ++ .../services/libsql/[libsqlId].tsx | 1 + .../services/mariadb/[mariadbId].tsx | 1 + .../services/mongo/[mongoId].tsx | 1 + .../services/mysql/[mysqlId].tsx | 1 + .../services/postgres/[postgresId].tsx | 1 + .../services/redis/[redisId].tsx | 1 + apps/dokploy/server/wss/authorize.ts | 23 ++++++++++--------- 13 files changed, 41 insertions(+), 14 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/logs/show.tsx b/apps/dokploy/components/dashboard/application/logs/show.tsx index 06b257766..52ab40ec4 100644 --- a/apps/dokploy/components/dashboard/application/logs/show.tsx +++ b/apps/dokploy/components/dashboard/application/logs/show.tsx @@ -50,9 +50,10 @@ export const badgeStateColor = (state: string) => { interface Props { appName: string; serverId?: string; + serviceId?: string; } -export const ShowDockerLogs = ({ appName, serverId }: Props) => { +export const ShowDockerLogs = ({ appName, serverId, serviceId }: Props) => { const [containerId, setContainerId] = useState(); const [option, setOption] = useState<"swarm" | "native">("native"); @@ -182,6 +183,7 @@ export const ShowDockerLogs = ({ appName, serverId }: Props) => { serverId={serverId || ""} containerId={containerId || "select-a-container"} runType={option} + serviceId={serviceId} /> diff --git a/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx b/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx index 4c3067b15..23bb4536d 100644 --- a/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx +++ b/apps/dokploy/components/dashboard/compose/logs/show-stack.tsx @@ -35,9 +35,14 @@ export const DockerLogs = dynamic( interface Props { appName: string; serverId?: string; + serviceId?: string; } -export const ShowDockerLogsStack = ({ appName, serverId }: Props) => { +export const ShowDockerLogsStack = ({ + appName, + serverId, + serviceId, +}: Props) => { const [option, setOption] = useState<"swarm" | "native">("native"); const [containerId, setContainerId] = useState(); @@ -167,6 +172,7 @@ export const ShowDockerLogsStack = ({ appName, serverId }: Props) => { serverId={serverId || ""} containerId={containerId || "select-a-container"} runType={option} + serviceId={serviceId} /> diff --git a/apps/dokploy/components/dashboard/compose/logs/show.tsx b/apps/dokploy/components/dashboard/compose/logs/show.tsx index bc47f1b6e..d7c3c0226 100644 --- a/apps/dokploy/components/dashboard/compose/logs/show.tsx +++ b/apps/dokploy/components/dashboard/compose/logs/show.tsx @@ -35,12 +35,14 @@ interface Props { appName: string; serverId?: string; appType: "stack" | "docker-compose"; + serviceId?: string; } export const ShowDockerLogsCompose = ({ appName, appType, serverId, + serviceId, }: Props) => { const { data, isPending } = api.docker.getContainersByAppNameMatch.useQuery( { @@ -104,6 +106,7 @@ export const ShowDockerLogsCompose = ({ serverId={serverId || ""} containerId={containerId || "select-a-container"} runType="native" + serviceId={serviceId} /> diff --git a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx index 3a5f460e9..c6cb473e0 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -23,6 +23,7 @@ interface Props { containerId: string; serverId?: string | null; runType: "swarm" | "native"; + serviceId?: string; } export const priorities = [ @@ -52,6 +53,7 @@ export const DockerLogsId: React.FC = ({ containerId, serverId, runType, + serviceId, }) => { const { data } = api.docker.getConfig.useQuery( { @@ -157,6 +159,10 @@ export const DockerLogsId: React.FC = ({ params.append("serverId", serverId); } + if (serviceId) { + params.append("serviceId", serviceId); + } + const wsUrl = `${protocol}//${ window.location.host }/docker-container-logs?${params.toString()}`; @@ -222,7 +228,7 @@ export const DockerLogsId: React.FC = ({ ws.close(); } }; - }, [containerId, serverId, lines, search, since]); + }, [containerId, serverId, serviceId, lines, search, since]); const handleDownload = () => { const logContent = filteredLogs diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx index 86e75fd2d..fb43c186f 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/application/[applicationId].tsx @@ -347,6 +347,7 @@ const Service = ( diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx index 76ad824ca..b4b1eb0cb 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/compose/[composeId].tsx @@ -380,11 +380,13 @@ const Service = ( serverId={data?.serverId || ""} appName={data?.appName || ""} appType={data?.composeType || "docker-compose"} + serviceId={data?.composeId} /> ) : ( )} diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/libsql/[libsqlId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/libsql/[libsqlId].tsx index 9a633c176..b01f8fdae 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/libsql/[libsqlId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/libsql/[libsqlId].tsx @@ -269,6 +269,7 @@ const Libsql = ( diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mariadb/[mariadbId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mariadb/[mariadbId].tsx index 47ec1a6ff..45bc9b455 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mariadb/[mariadbId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mariadb/[mariadbId].tsx @@ -299,6 +299,7 @@ const Mariadb = ( diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mongo/[mongoId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mongo/[mongoId].tsx index 6074ed7ed..160a46ada 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mongo/[mongoId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mongo/[mongoId].tsx @@ -299,6 +299,7 @@ const Mongo = ( diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mysql/[mysqlId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mysql/[mysqlId].tsx index 3b6edf170..ba161e532 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mysql/[mysqlId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/mysql/[mysqlId].tsx @@ -276,6 +276,7 @@ const MySql = ( diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/postgres/[postgresId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/postgres/[postgresId].tsx index 36cef637d..718045d9d 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/postgres/[postgresId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/postgres/[postgresId].tsx @@ -284,6 +284,7 @@ const Postgresql = ( diff --git a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/redis/[redisId].tsx b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/redis/[redisId].tsx index 7adc36c66..5c2f2c087 100644 --- a/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/redis/[redisId].tsx +++ b/apps/dokploy/pages/dashboard/project/[projectId]/environment/[environmentId]/services/redis/[redisId].tsx @@ -297,6 +297,7 @@ const Redis = ( diff --git a/apps/dokploy/server/wss/authorize.ts b/apps/dokploy/server/wss/authorize.ts index 5fdda5ce6..ceff47b2c 100644 --- a/apps/dokploy/server/wss/authorize.ts +++ b/apps/dokploy/server/wss/authorize.ts @@ -30,18 +30,11 @@ export const canAccessDockerOverWss = async ( const ctx = buildCtx(user, session.activeOrganizationId); if (!(await hasPermission(ctx, { docker: ["read"] }))) return false; - if (serverId && serverId !== "local") { - const accessible = await getAccessibleServerIds({ - userId: user.id, - activeOrganizationId: session.activeOrganizationId, - }); - if (!accessible.has(serverId)) return false; - } - // When the container belongs to a known Dokploy service (opened from a - // service page), enforce service-level access too. Container terminals - // opened from the generic Docker overview have no serviceId and fall back to - // the docker-permission + server checks above. + // service page), enforce service-level access. Checked before the server + // gate so the service grant is the primary authorization signal. Container + // terminals opened from the generic Docker overview have no serviceId and + // fall back to the docker-permission + server checks. if (serviceId) { try { await checkServiceAccess(ctx, serviceId, "read"); @@ -50,6 +43,14 @@ export const canAccessDockerOverWss = async ( } } + if (serverId && serverId !== "local") { + const accessible = await getAccessibleServerIds({ + userId: user.id, + activeOrganizationId: session.activeOrganizationId, + }); + if (!accessible.has(serverId)) return false; + } + return true; };