toBeTrue(); // But when escaped, the shell metacharacters become literal strings $escaped = escapeshellarg($maliciousPath); // The escaped version wraps in single quotes and escapes internal single quotes expect($escaped)->toBe("'/tmp/file'\\''; whoami; '\\'''"); // Building a command with escaped path is safe $command = "rm -f {$escaped}"; // The command contains the quoted path, not an unquoted injection expect($command)->toStartWith("rm -f '"); expect($command)->toEndWith("'"); }); it('escapes paths with semicolon injection attempts', function () { $path = '/tmp/backup; rm -rf /; echo'; expect(isSafeTmpPath($path))->toBeTrue(); $escaped = escapeshellarg($path); expect($escaped)->toBe("'/tmp/backup; rm -rf /; echo'"); // The semicolons are inside quotes, so they're treated as literals $command = "rm -f {$escaped}"; expect($command)->toBe("rm -f '/tmp/backup; rm -rf /; echo'"); }); it('escapes paths with backtick command substitution attempts', function () { $path = '/tmp/backup`whoami`.sql'; expect(isSafeTmpPath($path))->toBeTrue(); $escaped = escapeshellarg($path); expect($escaped)->toBe("'/tmp/backup`whoami`.sql'"); // Backticks inside single quotes are not executed $command = "rm -f {$escaped}"; expect($command)->toBe("rm -f '/tmp/backup`whoami`.sql'"); }); it('escapes paths with $() command substitution attempts', function () { $path = '/tmp/backup$(id).sql'; expect(isSafeTmpPath($path))->toBeTrue(); $escaped = escapeshellarg($path); expect($escaped)->toBe("'/tmp/backup\$(id).sql'"); // $() inside single quotes is not executed $command = "rm -f {$escaped}"; expect($command)->toBe("rm -f '/tmp/backup\$(id).sql'"); }); it('escapes paths with pipe injection attempts', function () { $path = '/tmp/backup | cat /etc/passwd'; expect(isSafeTmpPath($path))->toBeTrue(); $escaped = escapeshellarg($path); expect($escaped)->toBe("'/tmp/backup | cat /etc/passwd'"); // Pipe inside single quotes is treated as literal $command = "rm -f {$escaped}"; expect($command)->toBe("rm -f '/tmp/backup | cat /etc/passwd'"); }); it('escapes paths with newline injection attempts', function () { $path = "/tmp/backup\nwhoami"; expect(isSafeTmpPath($path))->toBeTrue(); $escaped = escapeshellarg($path); // Newline is preserved inside single quotes expect($escaped)->toContain("\n"); expect($escaped)->toStartWith("'"); expect($escaped)->toEndWith("'"); }); it('handles normal paths without issues', function () { $normalPaths = [ '/tmp/restore-backup.sql', '/tmp/restore-script.sh', '/tmp/database-dump-abc123.sql', '/tmp/deeply/nested/path/to/file.sql', ]; foreach ($normalPaths as $path) { expect(isSafeTmpPath($path))->toBeTrue(); $escaped = escapeshellarg($path); // Normal paths are just wrapped in single quotes expect($escaped)->toBe("'{$path}'"); } }); it('escapes container names with injection attempts', function () { // Container names are not validated by isSafeTmpPath, so escaping is critical $maliciousContainer = 'container"; rm -rf /; echo "pwned'; $escaped = escapeshellarg($maliciousContainer); expect($escaped)->toBe("'container\"; rm -rf /; echo \"pwned'"); // Building a docker command with escaped container is safe $command = "docker rm -f {$escaped}"; expect($command)->toBe("docker rm -f 'container\"; rm -rf /; echo \"pwned'"); }); });