Skip to content

fix(security): escape user-controlled values across command-injection sinks (quote sweep) - #4873

Merged
Siumauricio merged 4 commits into
canaryfrom
fix/cmdi-quote-sweep
Jul 20, 2026
Merged

Siumauricio merged 4 commits into
canaryfrom
fix/cmdi-quote-sweep

Conversation

@Siumauricio

Copy link
Copy Markdown
Contributor

Sweeps the remaining OS-command-injection sinks where user-controlled values reached a shell unescaped, applying shell-quote's quote() (or strict validation where a shell arg cannot be quoted). Closes a batch of same-root-cause advisories at once.

Families fixed

rclone / S3 — every value in getS3Credentials (and the duplicated block in destination.testConnection) now goes through quote(); the listBackupFiles search path, all restore/*.ts backup paths, the mongo restore file names, and the web-server restore file are quoted too. (GHSA-5vjv, GHSA-2xjq, GHSA-f7mp, GHSA-f6x8, GHSA-p2c7 area.)

volumeName — added VOLUME_NAME_REGEX (Docker volume-name format) enforced on volumeName in create/update/runManually. A legit volume name never contains shell metacharacters, so this blocks injection across every docker run -v, docker volume rm, filter and rclone sink at once. The user-supplied backupFileName and the mount volumeName in database rebuild are quoted. (GHSA-w223, GHSA-xqjr, GHSA-74xw.)

file-path-to-shell — quoted the user-derived paths reaching the shell in file mounts (mount.ts, getCreateFileCommand), patch-repo read (repoPath/filePath), and certificate create/remove (certificatePath). (GHSA-56g6, GHSA-q9qw, GHSA-fgh8 area.)

schedule remote exec — quoted containerId, shellType, command and logPath in the remote docker exec scheduled-command path (the local path already used argv).

traefik remote write — writeTraefikConfigRemote now base64-encodes the YAML instead of piping it through echo ... (a single quote in any label/host/serviceName broke out — GHSA-478p / p787 residual), and every configPath in remote rm/cat/redirect commands (including the input.path-derived ones) is quoted.

Notes

  • DB changePassword (databaseUser + old credential) is intentionally not in this PR — it interpolates into both a shell and a nested SQL context and needs the env-var pattern with its own runtime verification; it will follow in a separate PR.
  • Escaping behavior is quote([value]) for values already typed string, and quote([value ?? ""]) for nullable ones.

Cold typecheck (server + app) and biome pass.

…mand injection

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.
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().
… invocations

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.
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).
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jul 20, 2026
@Siumauricio
Siumauricio merged commit d02f34f into canary Jul 20, 2026
4 checks passed
@Siumauricio
Siumauricio deleted the fix/cmdi-quote-sweep branch July 20, 2026 23:05
@Siumauricio

Copy link
Copy Markdown
Contributor Author

Added two more sinks to complete GHSA-fgh8 and GHSA-q9qw:

  • schedule appName was overridable via the drizzle-zod $defaultFn-as-optional leak → omitted from the insert schema and its rm -rf/mkdir paths quoted (GHSA-fgh8 VULN-015).
  • certificatePath had the same leak → omitted from the create schema so it stays server-generated (GHSA-q9qw path-traversal).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant