July 29, 2026
Why Command Injection in a Web App is a Critical Bug, but in a CLI Tool It’s Just a Feature
When security engineers transition from web pentesting to code review of desktop applications, CLI utilities, or Go libraries, they often…

By Johnny Cold
3 min read
- 1 1. The Web Threat Model: Trust No One Behind the Network
- 2 2. The CLI Threat Model: The User IS the Execution Environment
- 3 3. When DOES an OS Injection in CLI / Go Code Become a Real Vulnerability?
- 4 Scenario A: Developer Tools Processing Untrusted Repositories
- 5 Scenario B: Privileged CLI Utilities (SUID / Local Privilege Escalation)
When security engineers transition from web pentesting to code review of desktop applications, CLI utilities, or Go libraries, they often fall into a cognitive trap.
They open a repository, run a static analyzer or scan the source code with their eyes, find an exec.Command(userInput)construction, and immediately mark it as a Critical Vulnerability (RCE).
However, in 90% of cases in CLI applications or standalone packages, this is not a vulnerability at all.
Understanding why requires looking at the core concept of Application Security: The Threat Model.
1. The Web Threat Model: Trust No One Behind the Network
In classic web applications, the trust boundary is crystal clear:
Untrusted Attacker (Internet) -> Web Server -> Internal OS / DB
The attacker sits on the outside. They have zero execution rights on the underlying server. Their only interface is sending HTTP requests, payload parameters, or headers.
If a web server takes a parameter from a GET request and passes it into a shell:
This is a textbook Remote Code Execution (RCE). The attacker elevates their privilege from an anonymous network user to a shell user running the web process.
2. The CLI Threat Model: The User IS the Execution Environment
Now let's look at a CLI utility written in Go (e.g., a custom deployment tool or developer CLI):
Is this a vulnerability? No.
Why? Because the person supplying targetHost is the local user who launched the binary in their own terminal.
- If the user passes
; calc.exeor$(rm -rf /), they are executing commands under their own local user privileges. - They already have full access to their terminal. They don't need your CLI tool to execute malicious commands on their own machine — they could just type
calc.exeorrm -rf /directly into bash/zsh.
Passing raw input into OS commands in a standard CLI isn't a security flaw; it's simply how command-line tools function.
3. When DOES an OS Injection in CLI / Go Code Become a Real Vulnerability?
Does this mean CLI tools are immune to Command Injection? Absolutely not. A vulnerability appears the moment the input comes from an untrusted external source, rather than directly from the local operator.
Here are three scenarios where OS Injection in CLI/Go code becomes a legitimate CVE:
Scenario A: Developer Tools Processing Untrusted Repositories
Imagine a CLI tool that parses config.yaml from a Git repository or unzips a downloaded archive:
Here, the developer just wanted to audit or build someone else's project. The input came from an untrusted external Git repo. This is a Supply Chain Attack/RCE.
Scenario B: Privileged CLI Utilities (SUID / Local Privilege Escalation)
If a CLI tool is designed to run with elevated privileges (e.g., via sudo, setuid, or as a background daemon/system service):
- Attacker: Low-privileged local user.
- Target: Root/System privileges.
If an unprivileged user can pass a payload into a sudo my-cli --option "payload" that gets passed to an unsafe exec.Command, the user escalates their privileges to root. This is a Local Privilege Escalation (LPE).
Scenario C: Argument Injection (Subverting Subprocesses)
Even without a shell (e.g., using exec.Command("git", userInput) instead of sh -c), passing untrusted input as an argument can lead to unexpected execution if the target binary supports dangerous flags:
In this case, git executes arbitrary code via its own internal flags.
Key Takeaway for Code Reviewers
When auditing Go code (or any other language), never judge a code snippet in isolation.
Before flagging exec.Command, os/exec, or string concatenations as a critical bug, ask yourself two fundamental questions:
- Where do these data originate? (User CLI arguments vs. untrusted network payload/external file/database)
- What privilege boundary is being crossed? (Unauthenticated network -> Server, Low-priv local user ->Root)
If no trust boundary is crossed, you haven't found a vulnerability - you've just found program logic. Context is everything.
About My Research
I am an AppSec researcher documenting my journey in Secure Code Reviewwith a focus on Go & Kotlin.
- Join my Telegram Channel for short security breakdowns and code patterns: https://t.me/sharkeyhot
- Follow me on X (Twitter) for daily AppSec tweets: https://x.com/shark_null
- Support my research on Ko-fi: https://ko-fi.com/sharkeyhot
Thank you very much for your attention.