- Added functionality for assigning Docker networks to services, allowing users to manage network associations directly from the UI.
- Introduced new components for assigning networks to both individual services and compose files, improving user experience and flexibility.
- Updated existing components to integrate network assignment features, ensuring seamless interaction within the dashboard.
- Enhanced the database schema to support new network-related fields, including `networkIds` and `detachDokployNetwork`, for better service configuration.
- Improved the layout and responsiveness of various UI elements to accommodate new features and enhance usability.
- Added a new API endpoint for recreating Docker networks that have been removed, allowing users to restore networks directly from the Dokploy interface.
- Updated the ShowNetworks and SyncNetworks components to include options for recreating networks, improving user management capabilities.
- Enhanced the network synchronization process to better handle stale records, providing users with clear actions for network maintenance.
- Refactored network creation logic to streamline the process and ensure consistency across network management operations.
- Added server filtering capability to the dashboard, allowing users to manage networks specific to selected servers.
- Introduced SyncNetworks component for synchronizing Docker networks with Dokploy, enabling import of new networks and cleanup of stale records.
- Updated HandleNetwork and ShowNetworks components to support server-specific operations, improving user experience and functionality.
- Enhanced API to support network synchronization and importing, ensuring accurate data handling for network management.
- Introduced a new ShowNetworkConfig component for displaying detailed Docker network configuration.
- Integrated ShowNetworkConfig into the ShowNetworks component, allowing users to view network details directly.
- Updated the layout of network actions for improved user experience and accessibility.
- Enhanced the API to support network inspection functionality, providing necessary data for the new component.
- Simplified the network form schema by removing unnecessary fields and enforcing validation rules for IP settings.
- Updated the HandleNetwork component to eliminate the edit functionality, focusing on network creation only.
- Improved the ShowNetworks component by adding a delete action for network management, enhancing user experience.
- Introduced a new SQL migration for the network schema, establishing necessary constraints and types.
- Cleaned up the API by removing the update network functionality, reflecting the immutable nature of Docker networks.
- Replaced zodResolver with standardSchemaResolver for improved schema validation.
- Added new toggle options for network settings, enhancing user interface clarity.
- Refactored network creation and update logic to streamline payload handling.
- Updated network form schema to remove default values, ensuring explicit user input.
- Introduced SERVER_LOCAL sentinel for local Dokploy server identification.
- Updated the ShowNetworks component to improve the layout and user experience.
- Introduced a new Pencil icon for editing networks and added a Badge component for displaying network drivers.
- Refactored loading and empty state handling for better visual feedback.
- Modified the API query to include server details for each network, enhancing the data returned for better context.
- Introduced a new SQL file defining the "network" type and its associated table structure.
- Added "networkIds" column to multiple application tables for network association.
- Established foreign key constraints for the "network" table linking to "organization" and "server" tables.
- Updated migration journal and added a new snapshot for versioning.
- Deleted the SQL file and snapshot related to the "network" management schema, cleaning up the database structure.
- Updated the migration journal to reflect these deletions.
- Added .playwright-* to .gitignore to exclude Playwright test files from version control.
Organization admins can define their own AI providers (name + API URL)
from the AI settings section. When at least one custom provider is
defined, it replaces the built-in provider list in the Add AI form,
the API URL is auto-filled and locked, and the backend rejects any
configuration whose URL is not in the allowed list.
The verify-totp handler checked for the error code
INVALID_TWO_FACTOR_AUTHENTICATION, which no longer exists in
better-auth 1.6.23 (the two-factor plugin now returns INVALID_CODE).
As a result, entering a wrong TOTP code fell through to the generic
catch and showed "Error verifying 2FA code / Unknown error" instead of
a clear "Invalid verification code" message.
Match the current better-auth error code so the specific, actionable
message is shown.
The organization switcher's DropdownMenuContent inherited its width from
the collapsed trigger (~40px) via the base primitive's
w-(--radix-dropdown-menu-trigger-width), clamping the menu to the
min-w-32 (128px) floor. This cut off the org name and action buttons in
icon mode.
Set an explicit w-64 so the menu fits its content regardless of the
trigger width, matching the pattern already used by the notification
dropdown in the same file.
Fixes#4840
The remote (execAsyncRemote) path built `echo ${password} | docker ${args.join(" ")}`
with the password, registryUrl and username interpolated unescaped, so a password
like `pw; whoami` ran arbitrary commands as root on the target server. Reuse
safeDockerLoginCommand (already used by create/update), which shell-escapes each
field and feeds the password via --password-stdin. The local argv+stdin path was
already safe.
server.remove deleted a server (and its deployment rows) by caller-supplied
serverId without checking it belongs to the active organization, unlike server.one
and server.update. An owner/admin of org A could delete org B's server registration.
Resolve and compare the server's organizationId before the active-services guard,
so cross-org callers are rejected without leaking existence.
Add VOLUME_NAME_REGEX (Docker volume-name format) and enforce it on volumeName in
create/update/runManually — a legit volume name never contains shell metacharacters,
so this blocks injection across every docker run/rm/rclone sink at once. Escape the
user-supplied backupFileName and the mount volumeName in database rebuild with quote().
Wrap S3 credential flags (getS3Credentials + destination.testConnection), the
listBackupFiles search path, and every restore backupPath/backupFile with
shell-quote's quote() so $(), backticks, quotes and spaces in destination
fields or backupFile can no longer break out of the rclone shell commands.
Use shell-quote's quote([...]) directly at each git-provider clone call site
instead of the one-line shellWord wrapper, and remove the now-unused helper.
Behavior is identical (quote([String(v ?? '')])).
The /api/providers/github/setup callback performed privileged writes (persisting
App client_secret/webhook_secret/private_key, re-pointing installations) with no
session check, deriving the write target (organizationId/userId on gh_init,
githubId on gh_setup) from the attacker-controlled state parameter. An
unauthenticated request, or a member with no gitProviders permission, could plant
or re-point a provider in any organization.
Require a valid session, gate on the gitProviders permission (same guard the tRPC
github router uses), derive the create target from the session, and verify the
gh_setup provider belongs to the caller's organization.
Host-level schedules (server / dokploy-server) run their script as root on the
host. The owner/admin gate only ran in the no-service branch, so a member could
attach an accessible applicationId to a dokploy-server schedule and skip it,
gaining root via schedule.runManually.
Extract assertHostScheduleAccess into the schedule service and call it before
the service-access branch in create/update/delete/runManually so the host-level
authorization always applies.
- application general 'Open Terminal' now passes serviceId (applicationId).
- The docker/terminal DockerTerminalModal now forwards serviceId to its Terminal.
- compose container rows thread serviceId to their terminal + logs modals.
This lets a member with service access (but without the docker permission) open
the terminal / read logs of their own service's containers from every service
view, matching the backend service-first authorization.
When a container is opened from a service page (serviceId present), gate on
service access alone — matching application.readLogs — instead of requiring the
docker permission first. This unblocks a member who has the service but not the
canAccessToDocker permission (or the server it runs on) from reading its logs /
opening its terminal. Generic Docker-overview ops (no serviceId) still require
docker permission + server access.
- 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.
Completes GHSA-qf9j service-level dimension: when a container terminal/logs/stats
is opened from a service page, the frontend now passes serviceId, and the wss
authorizer calls checkServiceAccess so a member restricted to specific services
cannot reach another service's containers. Generic Docker-overview opens have no
serviceId and keep the docker-permission + server-access gate.
Verified against the local WebSocket: a member (docker:read=false) is rejected
with close code 4003; owner is allowed.
Closes GHSA-qf9j-c9p4-r4xp
The /terminal, /docker-container-terminal, /docker-container-logs and
/docker-stats WebSocket handlers only checked session + organization, so any
authenticated member could open a root shell / read logs of any container, and
the local (serverId=local) branch reached a root SSH shell on the control-plane
host.
Adds an authorization layer (server/wss/authorize.ts):
- container ops (terminal/logs/stats): require the docker permission (owner/admin
or a member granted canAccessToDocker); for a remote server, require the server
be accessible to the caller.
- host/server terminal: the local host root terminal is restricted to owner/admin;
a remote server terminal is gated on server access.
Closes GHSA-c68r-7wg9-p7v2, GHSA-899j-cjwp-v4gw, GHSA-7r6p-v9gw-pwc8, GHSA-qf9j-c9p4-r4xp
- cluster.removeWorker: input.nodeId (z.string(), no regex) was interpolated raw
into 'docker node update/rm ${nodeId}' — now shell-quoted.
- swarm image upload (getRegistryCommands): registryTag / imageName were
interpolated raw into 'docker tag'/'docker push' and an echo. registryTag is
built from username and imagePrefix, which have no schema regex, so it was
injectable — now shell-quoted. (The docker login already used
safeDockerLoginCommand, so credentials were already safe.)
- gpu-setup: nodeId (derived from 'docker info', not user input) escaped as
defense-in-depth.
Closes GHSA-4mfc-grxw-6858, GHSA-hfwh-69ch-gv47, GHSA-prwq-2mcm-mvhr
- composePath / appName are now passed through shell-quote in createCommand,
getCreateEnvFileCommand and services/compose.ts deploy commands, instead of
being interpolated raw into 'docker compose'/'docker stack' shell commands.
- compose.command: sanitizeCommand was cosmetic (trim + strip quotes). It now
rejects shell control characters (; & | ` $ () {} <> newline), which a normal
docker compose CLI line never contains, blocking breakout into host commands.
Closes GHSA-8r5w-vqjr-8c44, GHSA-5xv2-7f8w-9j5c, GHSA-qh6h-669j-77rw
The backup and restore command builders interpolated database name / user /
password directly into a 'docker exec ... {bash,sh} -c "..."' string executed
via execAsync / execAsyncRemote. Because the values sit inside the outer shell's
double quotes, a simple quote() is insufficient: the outer shell expands $(),
backticks and $VAR before the inner quoting applies (double-nested shell).
Values are now passed to the container as environment variables (docker exec -e
VAR=<shell-quoted>) and referenced as "$VAR" inside a single-quoted inner
script, so they never enter the inner command text and cannot break out of
either shell layer. The rest of each command (pg_dump/mysqldump/etc., flags,
| gzip) is unchanged.
Closes GHSA-qc73-mp78-4833, GHSA-qf8x-98cv-92qh, GHSA-ww4j-wjrr-rq8v, GHSA-7m3w-rm5f-h4fr, GHSA-f7mp-9jfp-mjrr
The deploy functions for postgres/mysql/mariadb/mongo/redis/libsql interpolated
the user-settable dockerImage field unquoted into 'docker pull ${dockerImage}'
executed via execAsyncRemote (SSH) on the remote server path. Now passed through
shell-quote. The local path already used pullImage() (execFile-based) and is
unaffected.
Closes GHSA-6jrh-8qmg-jj3p
- dockerImage (buildRemoteDocker) -> docker pull / echo
- dockerContextPath (docker-file builder) -> cd
- publishDirectory (nixpacks builder) -> docker cp source/dest paths
These fields were interpolated unescaped into shell commands run via execAsync /
execAsyncRemote during deployment. All are now passed through shell-quote.
Registry credentials already went through safeDockerLoginCommand (unchanged).
Closes GHSA-g9cg-4mmj-mh7p, GHSA-jxxj-gmpx-h5rj, GHSA-qjrc-g63x-qhp9, GHSA-98j8-6vjr-c3xw
swarm.getNodes/getNodeInfo/getNodeApps/getAppInfos accepted a caller-supplied
serverId and ran remote Docker Swarm reads against it with no organization
check, exposing another tenant's swarm topology cross-org (getContainerStats
already had the check; the others did not). getNodeInfo additionally
interpolated nodeId unescaped into 'docker node inspect ${nodeId}'.
Adds an org-ownership assertion to the four unscoped handlers and passes nodeId
through shell-quote in getNodeInfo.
Closes GHSA-jj6h-388v-9rwm