Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/git_ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ impl GitOperations for GitOpsManager {

// And removed from index
let _ = std::process::Command::new("git")
.args(["rm", "--cached", "-r", "--ignore-unmatch"])
.args(["rm", "--cached", "-r", "--ignore-unmatch", "--"])
.arg(&opts.path)
.current_dir(workdir)
.output();
Comment on lines 391 to 395
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change hardens the git rm cleanup against hyphen-prefixed pathspecs, but the newly added integration tests don’t appear to exercise this cleanup path (it only runs when gix+git2 add_submodule fails and the code falls back to the CLI). Consider adding a targeted test that forces the fallback cleanup to run with a path starting with -/--... and asserts the overall operation succeeds (or at least that the cleanup doesn’t invoke unintended git rm options).

Copilot uses AI. Check for mistakes.
Expand Down Expand Up @@ -525,7 +525,8 @@ impl GitOperations for GitOpsManager {
.or_else(|_| {
// CLI fallback: use git read-tree to apply sparse checkout
let output = std::process::Command::new("git")
.args(["-C", path, "read-tree", "-mu", "HEAD"])
.current_dir(path)
.args(["read-tree", "-mu", "HEAD"])
.output()
.context("Failed to run git read-tree")?;
if output.status.success() {
Expand Down
72 changes: 72 additions & 0 deletions tests/security_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: 2025 Adam Poulemanos <89049923+bashandbone@users.noreply.github.com>
//
// SPDX-License-Identifier: LicenseRef-PlainMIT OR MIT

//! Security tests to ensure robustness against various attack vectors.

mod common;
use common::TestHarness;

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_path_with_hyphen_injection() {
let harness = TestHarness::new().expect("Failed to create test harness");
harness.init_git_repo().expect("Failed to init git repo");

let remote_repo = harness
.create_test_remote("hyphen-remote")
.expect("Failed to create remote");
let remote_url = format!("file://{}", remote_repo.display());

// A path starting with a hyphen that could be a git flag.
// This should be handled as a literal path component, not interpreted
// as an option to git or to any cleanup command that operates on paths.
let malicious_path = "-c";

harness.run_submod_success(&[
"add",
&remote_url,
"--name",
"hyphen-sub",
"--path",
malicious_path,
]).expect("Failed to add submodule with hyphenated path");

// Verify the submodule was actually created at the requested path.
assert!(harness.dir_exists("-c"));
}

#[test]
fn test_sparse_checkout_with_hyphen_path() {
let harness = TestHarness::new().expect("Failed to create test harness");
harness.init_git_repo().expect("Failed to init git repo");

let remote_repo = harness
.create_complex_remote("sparse-hyphen")
.expect("Failed to create remote");
let remote_url = format!("file://{}", remote_repo.display());

// Path starting with hyphen
let path = "-sparse";

// Ensure the directory exists to trigger the CLI fallback in apply_sparse_checkout if needed,
// although apply_sparse_checkout is usually called after gix/git2 which might fail or be bypassed.

Comment on lines +55 to +57
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment about "Ensure the directory exists to trigger the CLI fallback" is misleading here: this test never explicitly creates path before calling submod add, and the directory will be created by the add operation itself. Consider either removing these lines or actually creating the directory (with an explanation of why that’s necessary) so the test intent matches what it does.

Suggested change
// Ensure the directory exists to trigger the CLI fallback in apply_sparse_checkout if needed,
// although apply_sparse_checkout is usually called after gix/git2 which might fail or be bypassed.
// Add a sparse submodule at a hyphen-prefixed path and verify the path is
// treated literally rather than being interpreted as a command-line option.

Copilot uses AI. Check for mistakes.
harness.run_submod_success(&[
"add",
&remote_url,
"--name",
"sparse-hyphen",
"--path",
path,
"--sparse-paths",
"src",
]).expect("Failed to add submodule with hyphenated path");

// Verify it worked
assert!(harness.dir_exists("-sparse/src"));
}
}
Loading