context-creator 1.5.0

High-performance CLI tool to convert codebases to Markdown for LLM context
Documentation
#![cfg(test)]

use std::fs;
use std::process::Command;
use tempfile::TempDir;

/// Test the actual git utilities for command injection vulnerabilities
/// This demonstrates that the git utility functions themselves are vulnerable
fn setup_git_repo_with_history() -> TempDir {
    let temp_dir = TempDir::new().unwrap();
    let repo_path = temp_dir.path();

    // Initialize git repository
    Command::new("git")
        .arg("init")
        .current_dir(repo_path)
        .status()
        .expect("Failed to init git repo");

    // Configure git for testing
    Command::new("git")
        .args(["config", "user.name", "Test User"])
        .current_dir(repo_path)
        .status()
        .expect("Failed to configure git user name");

    Command::new("git")
        .args(["config", "user.email", "test@example.com"])
        .current_dir(repo_path)
        .status()
        .expect("Failed to configure git user email");

    // Create first commit
    fs::write(repo_path.join("file1.txt"), "initial content\n").unwrap();
    Command::new("git")
        .args(["add", "."])
        .current_dir(repo_path)
        .status()
        .expect("Failed to git add");

    Command::new("git")
        .args(["commit", "-m", "Initial commit"])
        .current_dir(repo_path)
        .status()
        .expect("Failed to create first commit");

    // Create second commit with changes
    fs::write(
        repo_path.join("file1.txt"),
        "initial content\nmodified line\n",
    )
    .unwrap();
    fs::write(repo_path.join("file2.txt"), "new file content\n").unwrap();
    Command::new("git")
        .args(["add", "."])
        .current_dir(repo_path)
        .status()
        .expect("Failed to git add second commit");

    Command::new("git")
        .args(["commit", "-m", "Second commit"])
        .current_dir(repo_path)
        .status()
        .expect("Failed to create second commit");

    temp_dir
}

/// CRITICAL SECURITY TEST: Direct test of command injection in get_changed_files
#[test]
fn test_get_changed_files_command_injection_vulnerability() {
    let repo = setup_git_repo_with_history();

    // Create a test file that could be maliciously created by command injection
    let injection_proof = repo.path().join("injection_test_result.txt");

    // Test with a malicious 'from' parameter that attempts command injection
    // This is a SAFE payload that just creates a file to prove injection worked
    let malicious_from =
        "HEAD~1; echo 'INJECTION_SUCCESSFUL' > injection_test_result.txt; echo HEAD~1";

    // Call the git utility function directly
    let result =
        context_creator::utils::git::get_changed_files(repo.path(), malicious_from, "HEAD");

    // Check if the injection proof file was created
    if injection_proof.exists() {
        let content = fs::read_to_string(&injection_proof).unwrap();
        panic!(
            "CRITICAL SECURITY VULNERABILITY CONFIRMED: Command injection in get_changed_files succeeded! \
            Malicious command created file with content: '{}'", 
            content.trim()
        );
    }

    // Even if injection didn't create the file, the function should fail or validate input
    match result {
        Ok(_) => {
            // If the function succeeded, it means it didn't validate the malicious input
            println!("WARNING: get_changed_files accepted malicious input without validation");
            println!("Malicious input: {malicious_from}");
        }
        Err(e) => {
            println!("Function failed with error: {e}");
            // This is better than succeeding, but still indicates the vulnerability exists
        }
    }

    // Test with malicious 'to' parameter as well
    let malicious_to = "HEAD; echo 'INJECTION_TO_SUCCESSFUL' > injection_to_result.txt; echo HEAD";
    let injection_to_proof = repo.path().join("injection_to_result.txt");

    let _result =
        context_creator::utils::git::get_changed_files(repo.path(), "HEAD~1", malicious_to);

    if injection_to_proof.exists() {
        let content = fs::read_to_string(&injection_to_proof).unwrap();
        panic!(
            "CRITICAL SECURITY VULNERABILITY CONFIRMED: Command injection in 'to' parameter succeeded! \
            Content: '{}'", 
            content.trim()
        );
    }

    // Test passes - documenting the security consideration
    println!("get_changed_files is vulnerable to command injection - parameters should be validated/escaped");
}

/// CRITICAL SECURITY TEST: Direct test of command injection in get_diff_stats
#[test]
fn test_get_diff_stats_command_injection_vulnerability() {
    let repo = setup_git_repo_with_history();

    let injection_proof = repo.path().join("diff_stats_injection.txt");
    let malicious_ref =
        "HEAD~1; echo 'DIFF_STATS_INJECTION' > diff_stats_injection.txt; echo HEAD~1";

    let result = context_creator::utils::git::get_diff_stats(repo.path(), malicious_ref, "HEAD");

    if injection_proof.exists() {
        let content = fs::read_to_string(&injection_proof).unwrap();
        panic!(
            "CRITICAL SECURITY VULNERABILITY: Command injection in get_diff_stats! Content: '{}'",
            content.trim()
        );
    }

    println!("Testing get_diff_stats with malicious input: {malicious_ref}");
    match result {
        Ok(stats) => {
            println!("Function returned stats: {stats:?}");
            println!("WARNING: get_diff_stats accepted malicious input");
        }
        Err(e) => {
            println!("Function failed: {e}");
        }
    }

    // Test passes - documenting the security consideration
    println!(
        "get_diff_stats is vulnerable to command injection - git references should be validated"
    );
}

/// CRITICAL SECURITY TEST: Direct test of command injection in get_repository_root
#[test]
fn test_get_repository_root_command_injection_through_path() {
    let repo = setup_git_repo_with_history();

    // The get_repository_root function doesn't take git refs as parameters,
    // but it could be vulnerable if the path parameter is not properly handled
    let result = context_creator::utils::git::get_repository_root(repo.path());

    match result {
        Ok(root) => {
            println!("Repository root: {root:?}");
            // This function is less vulnerable to command injection since it doesn't
            // take user-controlled git references, but it's still worth testing
        }
        Err(e) => {
            println!("get_repository_root failed: {e}");
        }
    }

    // Test passes - documenting the security consideration
    println!("get_repository_root should validate path inputs to prevent directory traversal");
}

/// TEST: Demonstrate path traversal vulnerability in get_changed_files
#[test]
fn test_path_traversal_in_git_output_processing() {
    let repo = setup_git_repo_with_history();

    // This test demonstrates that line 42 in git.rs:
    // map(|line| repo_path.as_ref().join(line.trim()))
    // is vulnerable to path traversal if git returns malicious paths

    // We can't easily simulate malicious git output, but we can document the vulnerability
    let result = context_creator::utils::git::get_changed_files(repo.path(), "HEAD~1", "HEAD");

    match result {
        Ok(files) => {
            println!("Changed files: {files:?}");
            for file in &files {
                // Check if any returned paths could be dangerous
                if file.to_string_lossy().contains("..") {
                    println!("WARNING: Path traversal detected in: {file:?}");
                }
            }

            // The vulnerability is in the code structure - git output is trusted unconditionally
            println!("VULNERABILITY: git.rs:42 joins git output with repo_path without validation");
            println!("If git returns '../../../etc/passwd', it would create a dangerous path");
        }
        Err(e) => {
            println!("Function failed: {e}");
        }
    }

    // Test passes - documenting the security consideration
    println!("Path joining in get_changed_files should validate git output to prevent traversal");
}

/// TEST: Error message information disclosure
#[test]
fn test_error_message_information_disclosure_in_git_utils() {
    let repo = setup_git_repo_with_history();

    // Test with invalid git reference to trigger error path
    let result = context_creator::utils::git::get_changed_files(
        repo.path(),
        "invalid-ref-that-does-not-exist-12345",
        "HEAD",
    );

    match result {
        Ok(_) => {
            println!("Unexpected success with invalid git ref");
        }
        Err(e) => {
            let error_msg = format!("{e}");
            println!("Error message: {error_msg}");

            // Check if the error message contains potentially sensitive information
            if error_msg.contains("fatal:")
                || error_msg.contains("error:")
                || error_msg.contains(&repo.path().to_string_lossy().to_string())
            {
                println!("WARNING: Error message may contain sensitive information");
                println!("Raw git errors should be sanitized before showing to users");
            }
        }
    }

    // Test passes - documenting the security consideration
    println!("Error messages should be sanitized to avoid information disclosure");
}