Command injection — Process.run / Process.start invoked with a shell or interpolated command
ID |
dart.code.command_injection |
Severity |
critical |
Remediation Complexity |
hard |
Remediation Risk |
high |
Remediation Effort |
medium |
Resource |
Code |
Language |
Dart |
Description
Process.run / Process.start / Process.runSync is invoked with a shell (sh -c, cmd /c, PowerShell, runInShell: true) or with a string-interpolated executable. Tainted argument values are parsed by the shell and execute arbitrary commands.
Rationale
When a process is launched through a shell, the shell parses the
command line and applies all its own metacharacter rules:
;, &&, ||, backticks, $(…), redirection (>, <),
globbing (*, ?), and quoting. ANY of these characters in a
user-controlled value escapes the intended command and lets the
attacker run their own.
Concrete attacks on Flutter (mostly desktop / server-side Dart):
-
Process.run('sh', ['-c', 'process $userId'])— attacker submitsuserId = "alice; cat /etc/passwd". -
Process.run('cmd', ['/c', 'type $logfile'])on Windows — attacker controlslogfile. -
Process.run('powershell', ['-Command', '$script'])— total PowerShell injection. -
runInShell: trueplus an interpolated argument — every metacharacter in the argument is interpreted by the shell.
Even when a Flutter app does not run external commands today, introducing one with shell parsing creates a new attack surface reachable from any tainted source the app already accepts (deeplinks, push notifications, network payloads).
The following code illustrates a vulnerable pattern detected by this rule:
class CommandInjection {
Future<void> shDashC(String userCmd) async {
// VULNERABLE: Command injection — Process.run / Process.start invoked with a shell or interpolated command
await Process.run('sh', ['-c', 'echo "$userCmd"']);
// VULNERABLE: Command injection — Process.run / Process.start invoked with a shell or interpolated command
await Process.run('/bin/sh', ['-c', userCmd]);
// VULNERABLE: Command injection — Process.run / Process.start invoked with a shell or interpolated command
await Process.run('/bin/bash', ['-c', userCmd]);
// VULNERABLE: Command injection — Process.run / Process.start invoked with a shell or interpolated command
await Process.run('zsh', ['-c', userCmd]);
}
}
Remediation
Call the binary directly with a list of separate arguments — no shell, no interpolation. If you absolutely must invoke a shell, pass the dynamic value as a SEPARATE argument so the shell does not parse it.
// Vulnerable
await Process.run('sh', ['-c', 'process "$userId"']);
await Process.run('ls', [userArg], runInShell: true);
await Process.run('$tool', [arg]);
// Fixed: direct binary call, separate args, no shell
await Process.run('/usr/bin/process', [userId], runInShell: false);
References
-
OWASP Top 10 2025 - A05 : Injection.
-
OWASP — Command Injection — Generic attack reference and metacharacter taxonomy
-
dart:io Process — API docs —
Process.run/runSync/startreference, includingrunInShell: