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
16 changes: 16 additions & 0 deletions changelog.d/unreleased/3025.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
category: fixed
issues:
- 3025
affected:
- src/CodeIndex/Cli/ConsoleUi.cs
- tests/CodeIndex.Tests/ConsoleUiTests.cs
---

## English

- **Version metadata loading now falls back on invalid files (#3025)** — malformed, missing, unreadable, or schema-invalid `version.json` files no longer throw from CLI version-display paths and instead return the existing `0.0.0` fallback.

## 日本語

- **不正な version metadata 読み込み時に fallback するようになりました (#3025)** — malformed、missing、unreadable、schema-invalid な `version.json` でも CLI の version 表示経路で例外を投げず、既存の `0.0.0` fallback を返します。
16 changes: 16 additions & 0 deletions changelog.d/unreleased/3244.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
category: fixed
issues:
- 3244
affected:
- src/CodeIndex/Cli/ConsoleUi.cs
- tests/CodeIndex.Tests/ConsoleUiTests.cs
---

## English

- **Version metadata reads are now bounded (#3244)** — `version.json` loading now reads at most 16 KiB and parses with an explicit JSON depth limit before falling back to `0.0.0` on invalid metadata.

## 日本語

- **version metadata の読み込みに上限を設定しました (#3244)** — `version.json` の読み込みは最大 16 KiB になり、明示的な JSON depth 上限で parse したうえで、不正な metadata では `0.0.0` に fallback します。
36 changes: 32 additions & 4 deletions src/CodeIndex/Cli/ConsoleUi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;

namespace CodeIndex.Cli;

Expand Down Expand Up @@ -67,6 +68,14 @@ public static class ConsoleUi
public const string DisableProgressEnvironmentVariable = "CDIDX_DISABLE_PROGRESS";
public const string PrefersReducedMotionEnvironmentVariable = "PREFERS_REDUCED_MOTION";
public const int SummaryLabelWidth = 9;
internal const int MaxVersionJsonBytes = 16 * 1024;
internal const int MaxVersionJsonDepth = 8;
private const string FallbackVersion = "0.0.0";

private static readonly JsonDocumentOptions VersionJsonDocumentOptions = new()
{
MaxDepth = MaxVersionJsonDepth,
};

private static readonly (string Command, string Usage)[] CommandUsageLines =
[
Expand Down Expand Up @@ -714,13 +723,32 @@ public static string LoadVersion()
}
var ioPath = LongPath.EnsureWindowsPrefix(path);
if (File.Exists(ioPath))
return LoadVersionFromFile(ioPath);

return FallbackVersion;
}

internal static string LoadVersionFromFile(string ioPath)
{
try
{
var json = File.ReadAllText(ioPath);
using var doc = System.Text.Json.JsonDocument.Parse(json);
var json = DataDirectorySecurity.ReadTextWithinLimit(ioPath, MaxVersionJsonBytes);
if (json is null)
return FallbackVersion;

using var doc = JsonDocument.Parse(json, VersionJsonDocumentOptions);
if (doc.RootElement.TryGetProperty("version", out var ver))
return ver.GetString() ?? "0.0.0";
return ver.GetString() ?? FallbackVersion;
}
return "0.0.0";
catch (Exception ex) when (ex is IOException
or UnauthorizedAccessException
or JsonException
or InvalidOperationException)
{
return FallbackVersion;
}

return FallbackVersion;
}

/// <summary>
Expand Down
77 changes: 77 additions & 0 deletions tests/CodeIndex.Tests/ConsoleUiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,83 @@ public void LoadVersion_ReturnsActualVersion_NotFallback()
Assert.Contains('.', version);
}

[Fact]
public void LoadVersionFromFile_MalformedJson_ReturnsFallback()
{
var path = WriteTempVersionJson("""{"version":""");
try
{
Assert.Equal("0.0.0", ConsoleUi.LoadVersionFromFile(path));
}
finally
{
File.Delete(path);
}
}

[Fact]
public void LoadVersionFromFile_NonStringVersion_ReturnsFallback()
{
var path = WriteTempVersionJson("""{"version":{}}""");
try
{
Assert.Equal("0.0.0", ConsoleUi.LoadVersionFromFile(path));
}
finally
{
File.Delete(path);
}
}

[Fact]
public void LoadVersionFromFile_MissingFile_ReturnsFallback()
{
var path = Path.Combine(Path.GetTempPath(), $"cdidx_missing_version_{Guid.NewGuid():N}.json");

Assert.Equal("0.0.0", ConsoleUi.LoadVersionFromFile(path));
}

[Fact]
public void LoadVersionFromFile_OversizedJson_ReturnsFallback()
{
var path = WriteTempVersionJson("{\"version\":\"" + new string('1', ConsoleUi.MaxVersionJsonBytes) + "\"}");
try
{
Assert.Equal("0.0.0", ConsoleUi.LoadVersionFromFile(path));
}
finally
{
File.Delete(path);
}
}

[Fact]
public void LoadVersionFromFile_TooDeepJson_ReturnsFallback()
{
var nesting = ConsoleUi.MaxVersionJsonDepth + 1;
var path = WriteTempVersionJson(
"""{"version":"1.2.3","nested":"""
+ new string('[', nesting)
+ "0"
+ new string(']', nesting)
+ "}");
try
{
Assert.Equal("0.0.0", ConsoleUi.LoadVersionFromFile(path));
}
finally
{
File.Delete(path);
}
}

private static string WriteTempVersionJson(string content)
{
var path = Path.Combine(Path.GetTempPath(), $"cdidx_version_{Guid.NewGuid():N}.json");
File.WriteAllText(path, content, Encoding.UTF8);
return path;
}

[Fact]
public void LoadBuildMetadata_PopulatesAllFields()
{
Expand Down
Loading