Skip to content

fix(settings): confine Traefik file read/write to the Traefik config directory - #5070

Open
bunlongheng wants to merge 1 commit into
Dokploy:canaryfrom
bunlongheng:fix/traefik-file-path-traversal
Open

fix(settings): confine Traefik file read/write to the Traefik config directory#5070
bunlongheng wants to merge 1 commit into
Dokploy:canaryfrom
bunlongheng:fix/traefik-file-path-traversal

Conversation

@bunlongheng

@bunlongheng bunlongheng commented Aug 12, 2026

Copy link
Copy Markdown

Summary

The settings.readTraefikFile and settings.updateTraefikFile tRPC procedures pass a caller-supplied path straight to the filesystem helpers without confining it to the Traefik configuration directory. A user holding the traefikFiles permission can therefore read or overwrite arbitrary files on the Dokploy host.

traefikFiles (read/write) is a free-tier, member-grantable resource in access-control.ts, so this is not limited to owners/admins - a scoped member intended only to edit Traefik dynamic config files can escalate to full host file access.

Vulnerable path

apps/dokploy/server/api/routers/settings.ts

readTraefikFile: protectedProcedure
  .input(apiReadTraefikConfig)
  .query(async ({ input, ctx }) => {
    await checkPermission(ctx, { traefikFiles: ["read"] });
    ...
    return readConfigInPath(input.path, input.serverId); // input.path is unbounded
  }),

updateTraefikFile: protectedProcedure
  .input(apiModifyTraefikConfig)
  .mutation(async ({ input, ctx }) => {
    await checkPermission(ctx, { traefikFiles: ["write"] });
    await writeTraefikConfigInPath(input.path, input.traefikConfig, input?.serverId);
    ...
  }),

readConfigInPath / writeTraefikConfigInPath only call path.join(pathFile), which does not restrict the location:

export const readConfigInPath = async (pathFile: string, serverId?: string) => {
  const configPath = path.join(pathFile);
  ...
  const yamlStr = fs.readFileSync(configPath, "utf8");
  return yamlStr;
};

The companion readDirectories endpoint only ever lists files under MAIN_TRAEFIK_PATH (/etc/dokploy/traefik), so the read/write endpoints are meant to operate on that directory alone.

Impact

A permitted user can supply an absolute path or ../ traversal in path to:

  • Read secrets outside the Traefik directory, e.g. /etc/dokploy/.env (database credentials, auth secret), SSH private keys, or any host file readable by the process.
  • Overwrite arbitrary files (for example a user's ~/.ssh/authorized_keys or a cron/systemd unit), which can lead to further compromise.

Fix

Resolve the requested path against MAIN_TRAEFIK_PATH and reject anything that escapes that directory, in both the read and write procedures. Legitimate usage is unaffected because every path the UI can produce comes from readDirectories, which is already rooted at MAIN_TRAEFIK_PATH.

const resolveTraefikFilePath = (filePath: string, serverId?: string | null): string => {
  const { MAIN_TRAEFIK_PATH } = paths(!!serverId);
  const base = path.resolve(MAIN_TRAEFIK_PATH);
  const resolved = path.resolve(base, filePath);
  if (resolved !== base && !resolved.startsWith(`${base}${path.sep}`)) {
    throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid path: ..." });
  }
  return resolved;
};

Notes

  • No API shape change; only rejects paths that were never intended to be reachable.
  • The serverId (remote) branch is validated with the same directory base used to render the file tree, so remote editing of legitimate Traefik files continues to work.

Greptile Summary

The PR confines caller-supplied Traefik read and write paths to the appropriate local or remote Traefik configuration directory.

  • Adds a shared path resolver using normalized, separator-aware containment checks.
  • Applies the resolved path before both Traefik filesystem operations.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete changed-code-triggered failures identified.

The new resolver uses the same server-aware Traefik base as directory listing, accepts the absolute in-tree paths produced by existing callers, rejects traversal and external absolute paths, and passes shell-quoted paths to the existing remote helpers.

Reviews (1): Last reviewed commit: "fix(settings): confine Traefik file read..." | Re-trigger Greptile

Context used:

@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant