Skip to content

Conversation

filipslezaklab
Copy link
Contributor

No description provided.

@filipslezaklab filipslezaklab self-assigned this Oct 9, 2025
@filipslezaklab filipslezaklab added the ignore-for-release Don't list PR in release notes label Oct 9, 2025
return Boolean(containers.length);
const dbRestore = () => {
const restore = `${dockerCompose} exec db pg_restore --clean -U defguard -d defguard /tmp/db.dump`;
execSync(restore);

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium

This shell command depends on an uncontrolled
absolute path
.

Copilot Autofix

AI about 7 hours ago

To fix this issue, we should avoid constructing the shell command using interpolation and passing it to execSync() as a single string, as this allows shell interpretation of dynamic arguments such as file paths. Instead, we should use execFileSync() and pass the command and its arguments as an array, which gives the shell no opportunity to misinterpret path values, regardless of what they contain.

Specifically, change the construction in the dbRestore function on lines 12-15 so that:

  • The base command (docker, not dockerCompose) is used as the executable.
  • All subcommands ("compose", "-f", <dockerFilePath>, "exec", "db", "pg_restore", "--clean", "-U", "defguard", "-d", "defguard", "/tmp/db.dump") are passed as discrete arguments, with variables (paths) inserted only as elements of the array.

This requires adjusting both the use of dockerCompose and string interpolation, to use proper argument arrays.

Summary:

  • In the block for dbRestore, replace the dynamic/interpolated string for restore with an array of arguments for execFileSync.
  • Import execFileSync from child_process (it is not currently imported), and use it in place of execSync.
  • Similar changes should be considered for other uses of execSync with command strings containing dynamic paths (though only line 14 is flagged for now).

Suggested changeset 1
e2e/utils/docker.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/e2e/utils/docker.ts b/e2e/utils/docker.ts
--- a/e2e/utils/docker.ts
+++ b/e2e/utils/docker.ts
@@ -1,4 +1,4 @@
-import { execSync } from 'child_process';
+import { execSync, execFileSync } from 'child_process';
 import path from 'path';
 
 const defguardPath = __dirname.split('e2e')[0];
@@ -10,8 +10,17 @@
 export const restorePath = path.resolve(defguardPath, 'e2e', 'defguard_backup.dump');
 
 const dbRestore = () => {
-  const restore = `${dockerCompose} exec db pg_restore --clean -U defguard -d defguard /tmp/db.dump`;
-  execSync(restore);
+  const args = [
+    'compose',
+    '-f', dockerFilePath,
+    'exec', 'db',
+    'pg_restore',
+    '--clean',
+    '-U', 'defguard',
+    '-d', 'defguard',
+    '/tmp/db.dump'
+  ];
+  execFileSync('docker', args, { stdio: 'inherit' });
 };
 
 export const dockerRestart = () => {
EOF
@@ -1,4 +1,4 @@
import { execSync } from 'child_process';
import { execSync, execFileSync } from 'child_process';
import path from 'path';

const defguardPath = __dirname.split('e2e')[0];
@@ -10,8 +10,17 @@
export const restorePath = path.resolve(defguardPath, 'e2e', 'defguard_backup.dump');

const dbRestore = () => {
const restore = `${dockerCompose} exec db pg_restore --clean -U defguard -d defguard /tmp/db.dump`;
execSync(restore);
const args = [
'compose',
'-f', dockerFilePath,
'exec', 'db',
'pg_restore',
'--clean',
'-U', 'defguard',
'-d', 'defguard',
'/tmp/db.dump'
];
execFileSync('docker', args, { stdio: 'inherit' });
};

export const dockerRestart = () => {
Copilot is powered by AI and may make mistakes. Always verify output.
const wait_for_db = `${dockerCompose} exec db sh -c 'until pg_isready; do sleep 1; done'`;
execSync(wait_for_db);
}
execSync(`${dockerCompose} stop core`);

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium

This shell command depends on an uncontrolled
absolute path
.

Copilot Autofix

AI about 7 hours ago

To fix this vulnerability, commands executed by execSync should be refactored to avoid shell interpolation of environment-derived strings (a file path, in this case). Instead of constructing a shell command string with the file path embedded, use execFileSync from the child_process module, which accepts commands and arguments as separate parameters, preventing the shell from interpreting special characters in dynamic data. In file e2e/utils/docker.ts, change calls such as execSync(${dockerCompose} stop core) to use execFileSync, passing the command and relevant arguments as an array. This requires splitting up ${dockerCompose} (which is itself a string) into its components:

  • command: 'docker'
  • arguments: ['compose', '-f', dockerFilePath, 'stop', 'core']

Do the same for all similar cases in the file, including dbRestore and dockerRestart steps.
The refactor only needs to change lines calling execSync with shell-constructed string; other path calculation code remains untouched.
You'll also need to import execFileSync instead of (or in addition to) execSync.
Update the method implementations so all shell commands provide their dynamic data as arguments.


Suggested changeset 1
e2e/utils/docker.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/e2e/utils/docker.ts b/e2e/utils/docker.ts
--- a/e2e/utils/docker.ts
+++ b/e2e/utils/docker.ts
@@ -1,21 +1,31 @@
-import { execSync } from 'child_process';
+import { execFileSync } from 'child_process';
 import path from 'path';
 
 const defguardPath = __dirname.split('e2e')[0];
 
 const dockerFilePath = path.resolve(defguardPath, 'docker-compose.e2e.yaml');
 
-export const dockerCompose = `docker compose -f ${dockerFilePath}`;
+export const dockerComposeCmd = 'docker';
+export const dockerComposeArgsBase = ['compose', '-f', dockerFilePath];
 
 export const restorePath = path.resolve(defguardPath, 'e2e', 'defguard_backup.dump');
 
 const dbRestore = () => {
-  const restore = `${dockerCompose} exec db pg_restore --clean -U defguard -d defguard /tmp/db.dump`;
-  execSync(restore);
+  const restoreArgs = [
+    ...dockerComposeArgsBase,
+    'exec',
+    'db',
+    'pg_restore',
+    '--clean',
+    '-U', 'defguard',
+    '-d', 'defguard',
+    '/tmp/db.dump'
+  ];
+  execFileSync(dockerComposeCmd, restoreArgs, { stdio: 'inherit' });
 };
 
 export const dockerRestart = () => {
-  execSync(`${dockerCompose} stop core`);
+  execFileSync(dockerComposeCmd, [...dockerComposeArgsBase, 'stop', 'core'], { stdio: 'inherit' });
   dbRestore();
-  execSync(`${dockerCompose} start core`);
+  execFileSync(dockerComposeCmd, [...dockerComposeArgsBase, 'start', 'core'], { stdio: 'inherit' });
 };
EOF
@@ -1,21 +1,31 @@
import { execSync } from 'child_process';
import { execFileSync } from 'child_process';
import path from 'path';

const defguardPath = __dirname.split('e2e')[0];

const dockerFilePath = path.resolve(defguardPath, 'docker-compose.e2e.yaml');

export const dockerCompose = `docker compose -f ${dockerFilePath}`;
export const dockerComposeCmd = 'docker';
export const dockerComposeArgsBase = ['compose', '-f', dockerFilePath];

export const restorePath = path.resolve(defguardPath, 'e2e', 'defguard_backup.dump');

const dbRestore = () => {
const restore = `${dockerCompose} exec db pg_restore --clean -U defguard -d defguard /tmp/db.dump`;
execSync(restore);
const restoreArgs = [
...dockerComposeArgsBase,
'exec',
'db',
'pg_restore',
'--clean',
'-U', 'defguard',
'-d', 'defguard',
'/tmp/db.dump'
];
execFileSync(dockerComposeCmd, restoreArgs, { stdio: 'inherit' });
};

export const dockerRestart = () => {
execSync(`${dockerCompose} stop core`);
execFileSync(dockerComposeCmd, [...dockerComposeArgsBase, 'stop', 'core'], { stdio: 'inherit' });
dbRestore();
execSync(`${dockerCompose} start core`);
execFileSync(dockerComposeCmd, [...dockerComposeArgsBase, 'start', 'core'], { stdio: 'inherit' });
};
Copilot is powered by AI and may make mistakes. Always verify output.
}
execSync(`${dockerCompose} stop core`);
dbRestore();
execSync(`${dockerCompose} start core`);

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium

This shell command depends on an uncontrolled
absolute path
.

Copilot Autofix

AI about 7 hours ago

To fix this vulnerability, the shell command must avoid passing dynamic paths as part of the command string. Instead, use the execFileSync method from child_process, which allows you to specify the command and its arguments as an array, ensuring that special shell characters in paths are correctly handled as arguments and not interpreted by the shell.

  • Edit all use of execSync shell commands that interpolate dockerFilePath or otherwise use paths derived from the environment.
  • Specifically, update the dockerRestart function (lines 17–21) and dbRestore (lines 12–15), replacing commands like `${dockerCompose} start core` with argument arrays.
  • To do so, construct argument arrays representing: ["compose", "-f", dockerFilePath, "stop", "core"], ["compose", "-f", dockerFilePath, "start", "core"], etc.
  • Change all corresponding execSync calls to execFileSync.
  • Import execFileSync from child_process at the top of the file.

Suggested changeset 1
e2e/utils/docker.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/e2e/utils/docker.ts b/e2e/utils/docker.ts
--- a/e2e/utils/docker.ts
+++ b/e2e/utils/docker.ts
@@ -1,21 +1,33 @@
-import { execSync } from 'child_process';
+import { execSync, execFileSync } from 'child_process';
 import path from 'path';
 
 const defguardPath = __dirname.split('e2e')[0];
 
 const dockerFilePath = path.resolve(defguardPath, 'docker-compose.e2e.yaml');
 
-export const dockerCompose = `docker compose -f ${dockerFilePath}`;
+export const dockerComposeCmd = "docker";
+export const dockerComposeArgsBase = ["compose", "-f", dockerFilePath];
 
 export const restorePath = path.resolve(defguardPath, 'e2e', 'defguard_backup.dump');
 
 const dbRestore = () => {
-  const restore = `${dockerCompose} exec db pg_restore --clean -U defguard -d defguard /tmp/db.dump`;
-  execSync(restore);
+  const args = [
+    ...dockerComposeArgsBase,
+    "exec",
+    "db",
+    "pg_restore",
+    "--clean",
+    "-U",
+    "defguard",
+    "-d",
+    "defguard",
+    "/tmp/db.dump"
+  ];
+  execFileSync(dockerComposeCmd, args, { stdio: 'inherit' });
 };
 
 export const dockerRestart = () => {
-  execSync(`${dockerCompose} stop core`);
+  execFileSync(dockerComposeCmd, [...dockerComposeArgsBase, "stop", "core"], { stdio: 'inherit' });
   dbRestore();
-  execSync(`${dockerCompose} start core`);
+  execFileSync(dockerComposeCmd, [...dockerComposeArgsBase, "start", "core"], { stdio: 'inherit' });
 };
EOF
@@ -1,21 +1,33 @@
import { execSync } from 'child_process';
import { execSync, execFileSync } from 'child_process';
import path from 'path';

const defguardPath = __dirname.split('e2e')[0];

const dockerFilePath = path.resolve(defguardPath, 'docker-compose.e2e.yaml');

export const dockerCompose = `docker compose -f ${dockerFilePath}`;
export const dockerComposeCmd = "docker";
export const dockerComposeArgsBase = ["compose", "-f", dockerFilePath];

export const restorePath = path.resolve(defguardPath, 'e2e', 'defguard_backup.dump');

const dbRestore = () => {
const restore = `${dockerCompose} exec db pg_restore --clean -U defguard -d defguard /tmp/db.dump`;
execSync(restore);
const args = [
...dockerComposeArgsBase,
"exec",
"db",
"pg_restore",
"--clean",
"-U",
"defguard",
"-d",
"defguard",
"/tmp/db.dump"
];
execFileSync(dockerComposeCmd, args, { stdio: 'inherit' });
};

export const dockerRestart = () => {
execSync(`${dockerCompose} stop core`);
execFileSync(dockerComposeCmd, [...dockerComposeArgsBase, "stop", "core"], { stdio: 'inherit' });
dbRestore();
execSync(`${dockerCompose} start core`);
execFileSync(dockerComposeCmd, [...dockerComposeArgsBase, "start", "core"], { stdio: 'inherit' });
};
Copilot is powered by AI and may make mistakes. Always verify output.
// Start Defguard stack with docker compose.
export const dockerUp = () => {
const command = `${dockerCompose} up --wait`;
execSync(command);

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium

This shell command depends on an uncontrolled
absolute path
.

Copilot Autofix

AI about 7 hours ago

General approach:
Replace the use of execSync with dynamically built shell commands with the safer alternative spawnSync (or execFileSync), providing the command and arguments as separate array entries. This avoids the shell interpreting the command string and any included special characters.

Detailed steps:

  • In dockerUp in e2e/utils/setup.ts, instead of creating a literal string (command = ...) and passing it to execSync, construct the Docker command and its arguments as an array, and call spawnSync or execFileSync with shell: false.

  • Adapt the call so that the executable is docker, the arguments are the split up version of dockerCompose plus the actual operation (up, --wait), and the file path (-f, ...).

  • As dockerCompose is defined as a string like docker compose -f ${dockerFilePath}, for this fix, we should export the split-out elements instead (e.g., separate the command from its arguments).
    However, since we can only edit the provided snippets, and since dockerCompose is imported from the other file, we cannot change its representation in general—so we must minimally adjust within the constraints.

  • The best edit, given the constraints, is to parse the space-separated dockerCompose into command and arguments directly in the edited region, or reconstruct them locally to remove string interpolation of untrusted data.

  • After splitting/extracting, run with spawnSync instead of execSync for consistency and direct control of arguments.

Required changes:

  • In e2e/utils/setup.ts, update the implementation of dockerUp (lines 20-22) to:
    • Replace the use of execSync(command) with a safe call to spawnSync, providing the exec and args as an array, not through the shell.
    • Parse dockerCompose safely to extract docker, arguments, and append the up/--wait suffix.
  • Handle errors (throw if process fails, matching prior behavior).
  • No additional imports are required, as spawnSync and execSync are already imported.

Suggested changeset 1
e2e/utils/setup.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/e2e/utils/setup.ts b/e2e/utils/setup.ts
--- a/e2e/utils/setup.ts
+++ b/e2e/utils/setup.ts
@@ -17,8 +17,15 @@
 
 // Start Defguard stack with docker compose.
 export const dockerUp = () => {
-  const command = `${dockerCompose} up --wait`;
-  execSync(command);
+  // Split dockerCompose into command and args to avoid shell interpolation
+  const composeParts = dockerCompose.split(' ');
+  const cmd = composeParts[0]; // 'docker'
+  const baseArgs = composeParts.slice(1); // ['compose', '-f', '/abs/path/to/docker-compose.e2e.yaml']
+  const args = [...baseArgs, 'up', '--wait'];
+  const res = spawnSync(cmd, args, { stdio: 'inherit' });
+  if (res.error) {
+    throw res.error;
+  }
   createSnapshot();
 };
 
EOF
@@ -17,8 +17,15 @@

// Start Defguard stack with docker compose.
export const dockerUp = () => {
const command = `${dockerCompose} up --wait`;
execSync(command);
// Split dockerCompose into command and args to avoid shell interpolation
const composeParts = dockerCompose.split(' ');
const cmd = composeParts[0]; // 'docker'
const baseArgs = composeParts.slice(1); // ['compose', '-f', '/abs/path/to/docker-compose.e2e.yaml']
const args = [...baseArgs, 'up', '--wait'];
const res = spawnSync(cmd, args, { stdio: 'inherit' });
if (res.error) {
throw res.error;
}
createSnapshot();
};

Copilot is powered by AI and may make mistakes. Always verify output.
// clear the db dump file
const teardownFunction = async (_: FullConfig) => {
const command = `${dockerCompose} down`;
execSync(command);

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium

This shell command depends on an uncontrolled
absolute path
.

Copilot Autofix

AI about 7 hours ago

To fix this issue, instead of building a shell command string by concatenating environment-derived paths, we should use execFileSync, which executes the file directly with argument vectors instead of parsing a shell string. This ensures all special shell characters in arguments are handled safely, and cannot cause command injection.

  • In e2e/utils/docker.ts, refactor usages of execSync where shell commands are built with tainted paths (including the exported dockerCompose string).
  • Instead of exporting a string command (dockerCompose), export an object containing the command, and file path argument separately, i.e.:
    { cmd: 'docker', args: ['compose', '-f', dockerFilePath] }
  • In dependent files (such as e2e/utils/teardown.ts), destructure and use those values with execFileSync, using the arguments array and adding any further CLI args (like "down") directly to the argument array.
  • Add any missing imports of execFileSync (and preserve execSync only if needed for legacy code).

You only need to update code that is directly shown in the snippets.

  • In e2e/utils/docker.ts, update the export and relevant usages.
  • In e2e/utils/teardown.ts, update to use execFileSync, using arguments array.

No functionality change—just a safer way to invoke the same commands.


Suggested changeset 2
e2e/utils/teardown.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/e2e/utils/teardown.ts b/e2e/utils/teardown.ts
--- a/e2e/utils/teardown.ts
+++ b/e2e/utils/teardown.ts
@@ -1,13 +1,16 @@
 /* eslint-disable @typescript-eslint/no-unused-vars */
-import { execSync } from 'child_process';
+import { execFileSync } from 'child_process';
 import { FullConfig } from 'playwright/test';
 
 import { dockerCompose } from './docker';
 
 // clear the db dump file
 const teardownFunction = async (_: FullConfig) => {
-  const command = `${dockerCompose} down`;
-  execSync(command);
+  execFileSync(
+    dockerCompose.cmd,
+    [...dockerCompose.args, 'down'],
+    { stdio: 'inherit' }
+  );
 };
 
 export default teardownFunction;
EOF
@@ -1,13 +1,16 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { execSync } from 'child_process';
import { execFileSync } from 'child_process';
import { FullConfig } from 'playwright/test';

import { dockerCompose } from './docker';

// clear the db dump file
const teardownFunction = async (_: FullConfig) => {
const command = `${dockerCompose} down`;
execSync(command);
execFileSync(
dockerCompose.cmd,
[...dockerCompose.args, 'down'],
{ stdio: 'inherit' }
);
};

export default teardownFunction;
e2e/utils/docker.ts
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/e2e/utils/docker.ts b/e2e/utils/docker.ts
--- a/e2e/utils/docker.ts
+++ b/e2e/utils/docker.ts
@@ -1,21 +1,38 @@
-import { execSync } from 'child_process';
+import { execSync, execFileSync } from 'child_process';
 import path from 'path';
 
 const defguardPath = __dirname.split('e2e')[0];
 
 const dockerFilePath = path.resolve(defguardPath, 'docker-compose.e2e.yaml');
 
-export const dockerCompose = `docker compose -f ${dockerFilePath}`;
+export const dockerCompose = {
+  cmd: 'docker',
+  args: ['compose', '-f', dockerFilePath],
+};
 
 export const restorePath = path.resolve(defguardPath, 'e2e', 'defguard_backup.dump');
 
 const dbRestore = () => {
-  const restore = `${dockerCompose} exec db pg_restore --clean -U defguard -d defguard /tmp/db.dump`;
-  execSync(restore);
+  execFileSync(
+    dockerCompose.cmd,
+    [
+      ...dockerCompose.args,
+      'exec',
+      'db',
+      'pg_restore',
+      '--clean',
+      '-U',
+      'defguard',
+      '-d',
+      'defguard',
+      '/tmp/db.dump',
+    ],
+    { stdio: 'inherit' }
+  );
 };
 
 export const dockerRestart = () => {
-  execSync(`${dockerCompose} stop core`);
+  execFileSync(dockerCompose.cmd, [...dockerCompose.args, 'stop', 'core'], { stdio: 'inherit' });
   dbRestore();
-  execSync(`${dockerCompose} start core`);
+  execFileSync(dockerCompose.cmd, [...dockerCompose.args, 'start', 'core'], { stdio: 'inherit' });
 };
EOF
@@ -1,21 +1,38 @@
import { execSync } from 'child_process';
import { execSync, execFileSync } from 'child_process';
import path from 'path';

const defguardPath = __dirname.split('e2e')[0];

const dockerFilePath = path.resolve(defguardPath, 'docker-compose.e2e.yaml');

export const dockerCompose = `docker compose -f ${dockerFilePath}`;
export const dockerCompose = {
cmd: 'docker',
args: ['compose', '-f', dockerFilePath],
};

export const restorePath = path.resolve(defguardPath, 'e2e', 'defguard_backup.dump');

const dbRestore = () => {
const restore = `${dockerCompose} exec db pg_restore --clean -U defguard -d defguard /tmp/db.dump`;
execSync(restore);
execFileSync(
dockerCompose.cmd,
[
...dockerCompose.args,
'exec',
'db',
'pg_restore',
'--clean',
'-U',
'defguard',
'-d',
'defguard',
'/tmp/db.dump',
],
{ stdio: 'inherit' }
);
};

export const dockerRestart = () => {
execSync(`${dockerCompose} stop core`);
execFileSync(dockerCompose.cmd, [...dockerCompose.args, 'stop', 'core'], { stdio: 'inherit' });
dbRestore();
execSync(`${dockerCompose} start core`);
execFileSync(dockerCompose.cmd, [...dockerCompose.args, 'start', 'core'], { stdio: 'inherit' });
};
Copilot is powered by AI and may make mistakes. Always verify output.
@wojcik91 wojcik91 merged commit 5f4b67c into main Oct 13, 2025
7 of 8 checks passed
@wojcik91 wojcik91 deleted the update-e2e-proxy branch October 13, 2025 17:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ignore-for-release Don't list PR in release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants