The endpoint field let anyone with dnsProvider create permission point
the AWS SDK at an arbitrary URL (loopback, link-local, internal
network, cloud metadata), turning testConnection and every other
Route53 operation into an SSRF oracle. There's no way to keep
LocalStack-style testing working while blocking that, since the
target address is identical either way, so the field is removed
entirely rather than validated. Route53 always talks to real AWS now.
Also pin @aws-sdk/client-route-53 to ^3.1097.0 (matching the existing
client-secrets-manager pin) instead of whatever caret-latest resolved
to at install time. The version that landed in the merged PR was
published less than a day before this, which violates the
minimumReleaseAge policy that merged into canary around the same time
and broke the Docker build.
Route53 rejects the literal "@" (RRSet with DNS name @. is not
permitted); Cloudflare happens to accept it, but relying on
provider-specific handling of that shorthand isn't safe. Resolve it
client-side to the zone's actual domain name before building the
create/update payload.
Lets you connect a DNS provider and manage its records (create,
update, delete) from Settings -> DNS Providers, instead of doing it
by hand in Cloudflare/AWS.
- dns_provider table, org-scoped, jsonb config as a discriminated
union per provider type
- Cloudflare adapter (REST, bearer token)
- Route53 adapter (AWS SDK, SigV4); records are identified by
type:name since Route53 has no native record id, so update
handles renames as delete-old + upsert-new
- listZones/listRecords/createRecord/updateRecord/deleteRecord/
testConnection wired through a shared DnsClient interface
- Settings UI: provider management, zone browser, record CRUD,
IP-fill dropdown for A records (panel IP + remote servers)
- Access control: dnsProvider resource wired into custom roles
- Unit tests for both adapters and the config mask/merge logic
- Add Nodes sub-tab under Docker > Swarm (visible with organization.update permission)
- Remove Cluster item from settings sidebar and delete /dashboard/settings/cluster page
- Point internal cluster links to /dashboard/docker?tab=swarm&subtab=nodes
- Make ShowNodes card full-width to match the other docker tabs
- Included the @aws-sdk/client-secrets-manager package version 3.1097.0 to facilitate AWS Secrets Manager integration for managing environment secrets.
- Updated the environment autocomplete component to include projectId and environmentId in vault secret fetching logic.
- Added authorization checks in the vault provider router to ensure users have access to the specified project and environment.
- Modified the vault provider schema to include projectId and optional environmentId for better validation.
- Improved error handling for unauthorized access and invalid vault provider assignments.
- Introduced support for project and environment assignments for vault providers, allowing for more granular access control.
- Updated the vault provider schema to include assignments and modified related components to handle these changes.
- Enhanced UI to display assignment status and allow users to manage project and environment associations effectively.
- Added validation to ensure assignments reference valid projects and environments within the organization.
- Updated tests to cover new functionality related to vault provider assignments and environment variable resolution.
- Added support for managing various vault providers (HashiCorp, AWS, Azure, Doppler, Infisical) in the dashboard.
- Implemented environment variable resolution using vault references, allowing seamless integration of secrets into application environments.
- Enhanced UI components to display and manage vault providers effectively.
- Introduced tests for vault reference resolution and environment variable preparation.
- Updated related components to utilize new vault management features.
- Refactored SwarmMonitorCard to use optional chaining for node data handling.
- Consolidated loading and error states into a single conditional rendering block.
- Updated dashboard layout to include tabs for containers and swarm, enhancing navigation.
- Removed deprecated network-related code from the dashboard.
The cli/sdk steps copied the repo-root openapi.json, which was last committed in March, and the mcp step fetched docs.dokploy.com, which canary pushes can overwrite. Generate the spec from the release commit instead so each package release matches its dokploy tag.
npm install -g pnpm resolved to pnpm 11, which turns the esbuild ignored-build-scripts warning into a fatal error, so sync-version has failed on every release since v0.29.5 and mcp/cli/sdk were never bumped or published. Pin pnpm to 10.22.0 (same as packageManager) and write the version without the v prefix so npm packages get valid semver.
- Updated ShowIconSettings component to handle both application and compose service types.
- Added new icon column to the compose table in the database.
- Implemented fetching and updating of icons for services in the environment and application pages.
- Introduced fetchTemplateLogo function to retrieve logos for templates, improving icon handling for compose services.
Dokploy stopped using Redis in v0.29.9 when the deployment queue moved
to an in-memory implementation, and install.sh no longer creates the
dokploy-redis service. Remove the remaining references so fresh installs
don't report Redis as unhealthy:
- checkRedisHealth from infrastructure health check
- cleanRedis/reloadRedis endpoints and their UI actions
- initializeRedis dev setup and redis-connection config
- unused bullmq dependency
- Added new endpoints for application creation, retrieval, and reloading with detailed request and response schemas.
- Updated existing network validation schemas to enforce minimum length requirements for network IDs.
- Improved OpenAPI documentation to ensure better clarity and validation for application-related operations.
- Added auditing functionality for network creation, recreation, and deletion actions in the network API.
- Updated the audit log schema to include "network" as a resource type.
- Enhanced the network router to log actions with relevant metadata, improving traceability and accountability for network management operations.
- 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.
Updated the SSO configuration to trust verified emails and modified user creation to set emailVerified to true by default. This enhances security and ensures that user email verification is properly handled.
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.
writeTraefikConfigRemote piped the stringified YAML through echo '...' where a
single quote in any label/host/serviceName broke out (GHSA-478p). Encode it as
base64 like the other writers, and quote() every configPath used in remote
rm/cat/redirect commands (including the input.path-derived ones).
quote() the user-derived paths that reach the shell in file mounts
(mount.ts, docker getCreateFileCommand), patch repo read (repoPath/filePath),
certificate create/remove (certificatePath), and the remote scheduled command
(containerId/shellType/command/logPath), so $(), backticks and traversal in
these fields can no longer inject commands.
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.
quote() legitimately wraps the payload text inside single quotes, so the raw
substring is present but inert. leaksShellSyntax (via shell-quote parse) is the
correct assertion; the literal not.toContain check was wrong.
writeDomainsToCompose returns a shell fragment that is executed as part of the
compose build script. On error it interpolated error.message (which embeds the
user-controlled serviceName/host) directly into an echo, so a serviceName like
$(cmd) executed as root. Escape the message with quote().
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.
createInsertSchema inferred scheduleType as a broadened union (drizzle-zod 0.5.1),
so passing input.scheduleType into assertHostScheduleAccess failed typecheck on a
cold build. Refine the insert schema's scheduleType to a plain z.enum and type the
helper param as the schedule enum.
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
Simpler and consistent with the existing registry column exclusion in the same
query: drop the secret columns from the nested github/gitlab/gitea/bitbucket
relations via columns:{ ...: false } instead of a post-fetch redaction helper.
Server-side clone paths re-fetch providers by id (find{Github,Gitlab,...}ById),
so deployments are unaffected. Column names are validated at compile time by
drizzle's typed columns config.
Closes GHSA-hg9j-j5mc-phf5, GHSA-wx75-vxph-2m2f
findApplicationById eagerly loads the github/gitlab/gitea/bitbucket relations
(needed server-side to clone) including OAuth tokens, the GitHub App private key
and webhook secret. application.one returned them to the client, exposing them to
any member with service:read even when hasGitProviderAccess was false.
Adds redactApplicationGitSecrets() in the application service (blanks the secret
columns, immutably) and applies it to application.one. No client feature reads
these secrets (verified in the frontend); server-side clone paths use
findApplicationById directly, so behaviour is unchanged.
Closes GHSA-hg9j-j5mc-phf5, GHSA-wx75-vxph-2m2f
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