T09 · Insecure Skill Coding Practices
- Location
modules/execution-patterns.md:14- Finding
Argument Injection Through Unsafe Command-Template Expansion
- Content
View full analysis
Vulnerability Details
File Location:
modules/execution-patterns.md:14-49; related unsafe template configuration atmodules/service-config.md:17-19andmodules/service-config.md:50
Vulnerability Type: CLI argument injection caused by interpolating untrusted values into a command string
Risk Level: MediumVulnerable Code
python def build_command( config: ServiceConfig, prompt: str, files: list[str], model: str = None ) -> str: """Build command from template.""" file_args = " ".join(f"@{f}" for f in files) model = model or config.default_model return config.command_template.format( command=config.command, prompt=prompt, files=file_args, model=model )The resulting interpolated string is subsequently parsed into arguments:
python import subprocess import shlex def execute_safely(command: str, timeout: int = 60) -> ExecutionResult: """Execute command with safety measures.""" start = time.time() try: result = subprocess.run( shlex.split(command), capture_output=True, text=True, timeout=timeout )The documented configuration places the untrusted prompt inside manually constructed quoting:
python command: str # CLI command or endpoint command_template: str = "{command} -p {prompt}"python command_template="gemini -p '{prompt}' {files}",Technical Analysis
The command builder substitutes
prompt, file names, the model, and configuration values into one command string. It then usesshlex.split()to reconstruct an argument vector. This does not preserve the intended boundary between trusted command options and untrusted data.Although
subprocess.run()is invoked withoutshell=True, which prevents ordinary shell metacharacters from directly launching a shell comma ...[truncated 2654 chars]- Remediation
View remediation
Remediation Suggestions
-
Construct an argument array directly. Do not interpolate user-controlled values into a command string and then parse it:
python def build_argv( config: ServiceConfig, prompt: str, files: list[str], model: str | None = None ) -> list[str]: argv = [config.command, "-p", prompt] selected_model = model or config.default_model if selected_model: argv.extend(["--model", selected_model]) argv.extend(f"@{path}" for path in files) return argvExecute the array without
shlex.split():python result = subprocess.run( argv, shell=False, capture_output=True, text=True, timeout=timeout, check=False ) -
Replace free-form command templates with structured configuration. Store the executable and each fixed option as separate fields. Do not allow templates to determine quoting or argument boundaries.
-
Allowlist executables and options. Resolve the executable to an approved absolute path and reject unsupported flags, models, or service names.
-
Validate file inputs. Resolve paths canonically, restrict them to approved project directories, reject control characters, and verify that each path refers to an allowed file type.
-
Use explicit end-of-options delimiters where supported. Insert
--before positional arguments to prevent values beginning with-from being interpreted as options. This is supplementary and does not replace direct argument-array construction. -
Add adversarial tests. Test prompts and file names containing spaces, single and double quotes, leading hyphens, backslashes, newlines, and option-like strings. Verify that each untrusted value remains exactly one intended argument.
-
Apply least privilege. Run external service CLIs with minimal filesystem permissions, restrict ...[truncated 128 chars]
-
