Skip to content
Merged
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
17 changes: 17 additions & 0 deletions changelog.d/unreleased/1715.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
category: fixed
issues:
- 1715
affected:
- src/CodeIndex/Indexer/Scanning/FileIndexer.cs
- src/CodeIndex/Cli/IndexCommandRunner.cs
- src/CodeIndex/Cli/ProgramRunner.cs
---

## English

- **Ignore path normalization now respects POSIX backslashes (#1715)** — `.cdidxignore` and `.gitignore` comparisons no longer rewrite literal backslashes in POSIX filenames while Windows paths still normalize separators.

## 日本語

- **ignore path 正規化が POSIX の backslash を尊重するようになりました (#1715)** — `.cdidxignore` / `.gitignore` の比較で POSIX ファイル名内のリテラル backslash を書き換えず、Windows path では引き続き区切り文字を正規化します。
15 changes: 15 additions & 0 deletions changelog.d/unreleased/1717.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
category: fixed
issues:
- 1717
affected:
- src/CodeIndex/Cli/IndexCommandRunner.cs
---

## English

- **Project-root escape checks now reject backslash traversal (#1717)** — commit and file-scoped indexing paths treat `..\` traversal the same as `../` before accepting a relative target.

## 日本語

- **project root 外への escape 判定が backslash traversal を拒否するようになりました (#1717)** — commit / file scoped indexing path は relative target を受け入れる前に `..\` traversal を `../` と同じように扱います。
15 changes: 15 additions & 0 deletions changelog.d/unreleased/1725.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
category: fixed
issues:
- 1725
affected:
- src/CodeIndex/Cli/ProgramRunner.cs
---

## English

- **CLI project-path detection now follows platform path syntax (#1725)** — Windows drive and UNC forms are recognized on Windows, while POSIX filenames containing literal backslashes are no longer treated as path syntax.

## 日本語

- **CLI の project path 判定がプラットフォーム別の path 構文に沿うようになりました (#1725)** — Windows では drive / UNC 形式を認識し、POSIX ではリテラル backslash を含むファイル名を path 構文として扱わないようになりました。
12 changes: 10 additions & 2 deletions src/CodeIndex/Cli/IndexCommandRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,16 @@ private static bool AllowReuseWithCurrentHotspotFamilyTrust(
&& matchesCurrent;
}

private static bool IsOutsideProjectRoot(string relativePath) =>
relativePath == ".." || relativePath.StartsWith("../", StringComparison.Ordinal);
internal static bool IsOutsideProjectRoot(string relativePath)
{
if (Path.IsPathRooted(relativePath))
return true;

var normalized = OperatingSystem.IsWindows()
? relativePath.Replace('\\', '/')
: relativePath;
return normalized == ".." || normalized.StartsWith("../", StringComparison.Ordinal);
}

private static bool ContainsIgnoreFilePath(IEnumerable<string> paths)
=> paths.Any(FileIndexer.IsIgnoreFilePath);
Expand Down
26 changes: 24 additions & 2 deletions src/CodeIndex/Cli/ProgramRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,30 @@ _ when IsProjectPathArg(commandName)
}
}

internal static bool IsProjectPathArg(string arg) =>
!arg.StartsWith('-') && (Directory.Exists(arg) || arg.Contains('/') || arg.Contains('\\') || arg == ".");
internal static bool IsProjectPathArg(string arg)
{
if (arg.StartsWith('-'))
return false;

if (arg == "." || Directory.Exists(arg) || Path.IsPathRooted(arg) || Path.IsPathFullyQualified(arg))
return true;

if (arg.Contains(Path.DirectorySeparatorChar))
return true;

if (Path.AltDirectorySeparatorChar != '\0'
&& Path.AltDirectorySeparatorChar != Path.DirectorySeparatorChar
&& arg.Contains(Path.AltDirectorySeparatorChar))
return true;

return OperatingSystem.IsWindows()
&& (IsWindowsDrivePath(arg) || arg.StartsWith(@"\\", StringComparison.Ordinal));
}

private static bool IsWindowsDrivePath(string arg) =>
arg.Length >= 2
&& arg[1] == ':'
&& ((arg[0] >= 'A' && arg[0] <= 'Z') || (arg[0] >= 'a' && arg[0] <= 'z'));

internal static void EnsureRedirectedStdoutUsesUtf8()
{
Expand Down
4 changes: 2 additions & 2 deletions src/CodeIndex/Indexer/Scanning/FileIndexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2523,8 +2523,8 @@ private static IEnumerable<string> ParseSubmodulePathsFromGitmodules(IEnumerable
}
}

private static string NormalizeIgnorePath(string path)
=> path.Replace('\\', '/').TrimEnd('/');
internal static string NormalizeIgnorePath(string path)
=> NormalizePathSeparators(path).TrimEnd('/');

/// <summary>
/// Normalize OS path separators to '/' for DB storage and lookup.
Expand Down
11 changes: 11 additions & 0 deletions tests/CodeIndex.Tests/FileIndexerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ namespace CodeIndex.Tests;
/// </summary>
public class FileIndexerTests
{
[Fact]
public void NormalizeIgnorePath_PosixPreservesLiteralBackslash()
{
var normalized = FileIndexer.NormalizeIgnorePath(@"weird\name.py/");

if (OperatingSystem.IsWindows())
Assert.Equal("weird/name.py", normalized);
else
Assert.Equal(@"weird\name.py", normalized);
}

[Fact]
public void ScanFilesDetailed_CancelledToken_ThrowsBeforeEnumeration()
{
Expand Down
38 changes: 38 additions & 0 deletions tests/CodeIndex.Tests/IndexCommandRunnerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,44 @@ public class IndexCommandRunnerTests
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
};

[Theory]
[InlineData("..")]
[InlineData("../evil.txt")]
[InlineData(@"..\evil.txt")]
[InlineData(@"..\..\evil.txt")]
public void IsOutsideProjectRoot_ParentTraversalSeparators_ReturnsTrue(string relativePath)
{
if (relativePath.Contains('\\') && !OperatingSystem.IsWindows())
return;

Assert.True(IndexCommandRunner.IsOutsideProjectRoot(relativePath));
}

[Fact]
public void IsOutsideProjectRoot_PosixLiteralBackslashPath_ReturnsFalse()
{
if (OperatingSystem.IsWindows())
return;

Assert.False(IndexCommandRunner.IsOutsideProjectRoot(@"..\evil.txt"));
}

[Fact]
public void IsOutsideProjectRoot_RootedPath_ReturnsTrue()
{
var rootedPath = OperatingSystem.IsWindows()
? @"C:\Windows\evil.txt"
: "/etc/passwd";

Assert.True(IndexCommandRunner.IsOutsideProjectRoot(rootedPath));
}

[Fact]
public void IsOutsideProjectRoot_NormalRelativePath_ReturnsFalse()
{
Assert.False(IndexCommandRunner.IsOutsideProjectRoot("src/app.cs"));
}

[Fact]
public void ParseArgs_HelpFlagSetsShowHelp()
{
Expand Down
30 changes: 30 additions & 0 deletions tests/CodeIndex.Tests/ProgramRunnerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,36 @@ namespace CodeIndex.Tests;
[Collection("SQLite pool sensitive")]
public class ProgramRunnerTests
{
[Theory]
[InlineData("foo.cs", false)]
[InlineData("./foo", true)]
[InlineData(".", true)]
public void IsProjectPathArg_CommonForms_ReturnsExpectedValue(string arg, bool expected)
{
Assert.Equal(expected, ProgramRunner.IsProjectPathArg(arg));
}

[Fact]
public void IsProjectPathArg_PosixLiteralBackslashFileName_IsNotPathSyntax()
{
if (OperatingSystem.IsWindows())
return;

Assert.False(ProgramRunner.IsProjectPathArg(@"weird\name.txt"));
}

[Theory]
[InlineData(@"C:\foo")]
[InlineData("C:")]
[InlineData(@"\\server\share\foo")]
public void IsProjectPathArg_WindowsPathForms_ReturnTrueOnWindows(string arg)
{
if (!OperatingSystem.IsWindows())
return;

Assert.True(ProgramRunner.IsProjectPathArg(arg));
}

[Theory]
[InlineData("--json")]
[InlineData("--json=array")]
Expand Down
Loading