---
title: "Never pass input to a shell · Agent Wranglers"
url: https://agentwranglers.se/playbook/application-security/input-and-injection/command-injection
id: APS-4
level: Required
risk: takeover
updated: 2026-10-27
build: a2a6bdfed830
tokens: 903
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Never pass input to a shell

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Input and injection](https://agentwranglers.se/playbook/application-security/input-and-injection.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-27.

- ID: APS-4
- Level: Required
- Chapter: [Input and injection](https://agentwranglers.se/playbook/application-security/input-and-injection.md)
- Rule: Run other programs without a shell, with input as separate arguments. Better still, use a library instead of a program.
- Check: Send `PENTEST; sleep 5` and `$(sleep 5)` wherever input might reach a program, such as file names and image tools. Replies come back at normal speed. A search of the code for `shell=True`, `exec(`, `system(` and backticks finds no call that holds user input.
- Risk: An account or system taken over
- How: Call the program directly with an argument list, such as `execFile` in Node or `subprocess.run([...])` in Python. Check input against an allow list, such as a known file name pattern. Put `--` before user values, so they cannot pass as options. It is the same idea as [passing values as parameters](https://agentwranglers.se/playbook/data/database-security/parameters.md) in database queries.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### JavaScript example

```js
// Bad: the shell reads ; and $( )
exec(`gzip --keep ${fileName}`);
// Good: no shell, input is one argument, and -- ends the options
execFile('gzip', ['--keep', '--', fileName]);
```

### Python example

```python
# Bad: shell=True reads ; and $( )
subprocess.run(f"gzip --keep {file_name}", shell=True)
# Good: an argument list, no shell
subprocess.run(["gzip", "--keep", "--", file_name], check=True)
```

### Go example

```go
// Go: exec.Command never starts a shell
err := exec.Command("gzip", "--keep", "--", fileName).Run()
```

## Sources

- [OWASP OS Command Injection Defense Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html)

## Related rules

- DSK-11 [Check the sender and content of every message to the main process](https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps/validate-ipc.md) in Desktop apps and extensions
- DSK-28 [Check who is calling before a privileged helper acts](https://agentwranglers.se/playbook/desktop-apps/installs-and-local-privileges/privileged-helper-callers.md) in Desktop apps and extensions
- DAT-8 [Pass values as parameters](https://agentwranglers.se/playbook/data/database-security/parameters.md) in Data and databases

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/application-security/input-and-injection/command-injection">Agent Wranglers application security playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Escape everything you show](https://agentwranglers.se/playbook/application-security/input-and-injection/cross-site-scripting.md)
- Next: [Keep input out of templates](https://agentwranglers.se/playbook/application-security/input-and-injection/template-injection.md)
- [Input and injection](https://agentwranglers.se/playbook/application-security/input-and-injection.md)
