---
title: Avoid command injection
description: Datadog, the leading service for cloud-scale monitoring.
breadcrumbs: >-
  Docs > Datadog Security > Code Security > Static Code Analysis (SAST) > SAST
  Rules > Avoid command injection
---

# Avoid command injection

{% callout %}
# Important note for users on the following Datadog sites: app.ddog-gov.com, us2.ddog-gov.com

{% alert level="danger" %}
This product is not supported for your selected [Datadog site](https://docs.datadoghq.com/getting_started/site.md). ({% placeholder "user-datadog-site-name" /%}).
{% /alert %}

{% /callout %}

## Metadata{% #metadata %}

**ID:** `rust-security/command-injection`

**Language:** Rust

**Severity:** Warning

**Category:** Security

**CWE**: [78](https://cwe.mitre.org/data/definitions/78.html)

## Description{% #description %}

Passing a non-literal argument to `Command::new("sh").arg("-c").arg(...)` (or another shell variant) lets the shell interpret the argument as a script. If any part of that argument comes from user-controlled input, an attacker can append shell metacharacters (`;`, `&&`, ```, `$(...)`) to inject arbitrary commands. Pass arguments directly to the target binary without a shell wrapper, or rigorously validate and escape the input.

#### Learn More{% #learn-more %}

- [CWE-78: Improper Neutralization of Special Elements used in an OS Command](https://cwe.mitre.org/data/definitions/78.html)

## Non-Compliant Code Examples{% #non-compliant-code-examples %}

```rust
use std::process::Command;

async fn bad() {
    let user_input = std::env::args().nth(1).unwrap();

    // .arg().arg() chain
    let _ = Command::new("sh").arg("-c").arg(user_input.clone()).output();

    // Full path
    let _ = Command::new("/bin/sh").arg("-c").arg(user_input.clone()).output();

    // format! macro — non-literal
    let _ = Command::new("sh").arg("-c").arg(format!("ls {}", user_input)).output();

    // Other shell
    let _ = Command::new("zsh").arg("-c").arg(&user_input).output();

    // Fully qualified path — tokio
    let _ = tokio::process::Command::new("sh").arg("-c").arg(&user_input).output();

    // Fully qualified path — std::process
    let _ = std::process::Command::new("bash").arg("-c").arg(&user_input).output();

    // .args array form
    let _ = Command::new("sh").args(["-c", &user_input]).output();
}
```

## Compliant Code Examples{% #compliant-code-examples %}

```rust
use std::process::Command;

fn ok() {
    // Hardcoded shell script — not tainted
    let _ = Command::new("sh").arg("-c").arg("ls -la").output();

    // Not a shell binary
    let user_input = std::env::args().nth(1).unwrap();
    let _ = Command::new("ls").arg(&user_input).output();

    // Shell binary but not -c
    let _ = Command::new("sh").arg("--version").output();

    // Structural match but @flag predicate filters out "-v"
    let _ = Command::new("sh").arg("-v").arg(&user_input).output();

    // No shell wrapper — args passed directly to the target binary
    let _ = Command::new("grep").arg("pattern").arg(&user_input).output();

    // Raw string — exercises the raw_string_literal branch of the JS guard
    let _ = Command::new("sh").arg("-c").arg(r"echo hi").output();

    // .args form with both literals
    let _ = Command::new("sh").args(["-c", "ls -la"]).output();

    // .args form but not a shell binary
    let _ = Command::new("ls").args(["-l", &user_input]).output();
}
```
  Seamless integrations. Try Datadog Code SecurityDatadog Code Security 
{% icon name="icon-external-link" /%}
 