Skip to content

Commit ee46aa0

Browse files
authored
Bound Dockerfile and TypeScript JSON parser budgets (#3346)
* Fix Dockerfile JSON form extraction budgets (#3211) * Cover TypeScript path alias target budgets (#3212)
1 parent e29024e commit ee46aa0

4 files changed

Lines changed: 185 additions & 12 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
category: fixed
3+
issues:
4+
- 3211
5+
affected:
6+
- src/CodeIndex/Indexer/Symbols/SymbolExtractor.Dockerfile.cs
7+
- tests/CodeIndex.Tests/SymbolExtractorTests.cs
8+
---
9+
10+
## English
11+
12+
- **Dockerfile JSON-form symbol extraction now enforces item and string budgets (#3211)**`VOLUME`, `SHELL`, `COPY`, and `ADD` JSON-form parsing now caps processed array entries and string lengths in addition to the existing JSON depth limit.
13+
14+
## 日本語
15+
16+
- **Dockerfile JSON form の symbol extraction が item 数と文字列長の上限を適用するようになりました (#3211)**`VOLUME``SHELL``COPY``ADD` の JSON form parse は、既存の JSON depth 上限に加えて、処理する配列要素数と文字列長にも上限を適用します。
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
category: fixed
3+
issues:
4+
- 3212
5+
affected:
6+
- src/CodeIndex/Indexer/Symbols/SymbolExtractor.TypeScriptPathAliases.cs
7+
- tests/CodeIndex.Tests/SymbolExtractorTests.cs
8+
---
9+
10+
## English
11+
12+
- **TypeScript path alias parsing now has explicit JSON and alias-map budgets tied to #3212**`tsconfig.json` / `jsconfig.json` path alias extraction is covered for JSON depth, alias rule counts, per-rule and total targets, and overlong pattern/target strings.
13+
14+
## 日本語
15+
16+
- **TypeScript path alias parse が #3212 の JSON / alias map 予算を明示的に扱うようになりました**`tsconfig.json` / `jsconfig.json` の path alias extraction は、JSON depth、alias rule 数、rule ごとおよび合計 target 数、過長な pattern / target 文字列の上限をカバーします。

src/CodeIndex/Indexer/Symbols/SymbolExtractor.Dockerfile.cs

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ namespace CodeIndex.Indexer;
77
public static partial class SymbolExtractor
88
{
99
internal const int DockerfileJsonFormMaxDepth = 8;
10+
internal const int DockerfileJsonFormMaxItems = 128;
11+
internal const int DockerfileJsonFormMaxStringLength = 4096;
1012
private static readonly JsonDocumentOptions DockerfileJsonFormDocumentOptions = new()
1113
{
1214
MaxDepth = DockerfileJsonFormMaxDepth,
@@ -313,13 +315,14 @@ private static void AddDockerfileJsonVolumeSymbols(
313315
if (document.RootElement.ValueKind != JsonValueKind.Array)
314316
return;
315317

318+
var itemCount = 0;
316319
foreach (var item in document.RootElement.EnumerateArray())
317320
{
318-
if (item.ValueKind != JsonValueKind.String)
319-
continue;
321+
if (itemCount >= DockerfileJsonFormMaxItems)
322+
break;
323+
itemCount++;
320324

321-
var name = item.GetString();
322-
if (string.IsNullOrWhiteSpace(name))
325+
if (!TryGetDockerfileJsonFormString(item, out var name))
323326
continue;
324327

325328
AddSymbolRecord(
@@ -344,6 +347,23 @@ private static void AddDockerfileJsonVolumeSymbols(
344347
}
345348
}
346349

350+
private static bool TryGetDockerfileJsonFormString(JsonElement item, out string value)
351+
{
352+
value = string.Empty;
353+
if (item.ValueKind != JsonValueKind.String)
354+
return false;
355+
356+
var text = item.GetString();
357+
if (string.IsNullOrWhiteSpace(text)
358+
|| text.Length > DockerfileJsonFormMaxStringLength)
359+
{
360+
return false;
361+
}
362+
363+
value = text;
364+
return true;
365+
}
366+
347367
private static void AddDockerfileNamedStageBaseImageSymbol(
348368
long fileId,
349369
string line,
@@ -400,11 +420,7 @@ private static void AddDockerfileShellSymbol(
400420
return;
401421

402422
var first = document.RootElement.EnumerateArray().FirstOrDefault();
403-
if (first.ValueKind != JsonValueKind.String)
404-
return;
405-
406-
var name = first.GetString();
407-
if (string.IsNullOrWhiteSpace(name))
423+
if (!TryGetDockerfileJsonFormString(first, out var name))
408424
return;
409425

410426
AddSymbolRecord(
@@ -531,11 +547,14 @@ private static bool TryGetDockerfileInstructionBody(string line, string instruct
531547
var count = 0;
532548
foreach (var item in document.RootElement.EnumerateArray())
533549
{
534-
if (item.ValueKind != JsonValueKind.String)
550+
if (count >= DockerfileJsonFormMaxItems)
535551
return null;
536-
537-
last = item.GetString();
538552
count++;
553+
554+
if (!TryGetDockerfileJsonFormString(item, out var value))
555+
return null;
556+
557+
last = value;
539558
}
540559

541560
return count >= 2 ? last : null;

tests/CodeIndex.Tests/SymbolExtractorTests.cs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15291,6 +15291,75 @@ public void Extract_Dockerfile_JsonFormsIgnorePayloadsBeyondParserDepthLimit(str
1529115291
Assert.Empty(symbols);
1529215292
}
1529315293

15294+
[Fact]
15295+
public void Extract_Dockerfile_JsonVolumeCapsArrayItems()
15296+
{
15297+
var maxItems = SymbolExtractor.DockerfileJsonFormMaxItems;
15298+
var items = Enumerable.Range(0, maxItems)
15299+
.Select(i => $"/vol{i}")
15300+
.Concat(["/too-many"]);
15301+
var content = "VOLUME [" + string.Join(", ", items.Select(item => JsonSerializer.Serialize(item))) + "]\n";
15302+
15303+
var symbols = SymbolExtractor.Extract(1, "dockerfile", content);
15304+
15305+
Assert.Equal(maxItems, symbols.Count);
15306+
Assert.Contains(symbols, s => s.Kind == "property" && s.Name == "/vol0");
15307+
Assert.Contains(symbols, s => s.Kind == "property" && s.Name == "/vol" + (maxItems - 1));
15308+
Assert.DoesNotContain(symbols, s => s.Kind == "property" && s.Name == "/too-many");
15309+
}
15310+
15311+
[Fact]
15312+
public void Extract_Dockerfile_JsonVolumeSkipsOverlongStrings()
15313+
{
15314+
var tooLong = "/" + new string('v', SymbolExtractor.DockerfileJsonFormMaxStringLength);
15315+
var content = "VOLUME [" + JsonSerializer.Serialize(tooLong) + ", \"/ok\"]\n";
15316+
15317+
var symbols = SymbolExtractor.Extract(1, "dockerfile", content);
15318+
15319+
Assert.Contains(symbols, s => s.Kind == "property" && s.Name == "/ok");
15320+
Assert.DoesNotContain(symbols, s => s.Kind == "property" && s.Name == tooLong);
15321+
Assert.Single(symbols);
15322+
}
15323+
15324+
[Fact]
15325+
public void Extract_Dockerfile_JsonShellSkipsOverlongExecutable()
15326+
{
15327+
var tooLong = "/" + new string('s', SymbolExtractor.DockerfileJsonFormMaxStringLength);
15328+
var content = "SHELL [" + JsonSerializer.Serialize(tooLong) + ", \"-c\"]\n";
15329+
15330+
var symbols = SymbolExtractor.Extract(1, "dockerfile", content);
15331+
15332+
Assert.Empty(symbols);
15333+
}
15334+
15335+
[Theory]
15336+
[InlineData("COPY")]
15337+
[InlineData("ADD")]
15338+
public void Extract_Dockerfile_JsonCopyAddSkipOverlongDestinations(string instruction)
15339+
{
15340+
var tooLong = "/" + new string('d', SymbolExtractor.DockerfileJsonFormMaxStringLength);
15341+
var content = instruction + " [\"source\", " + JsonSerializer.Serialize(tooLong) + "]\n";
15342+
15343+
var symbols = SymbolExtractor.Extract(1, "dockerfile", content);
15344+
15345+
Assert.Empty(symbols);
15346+
}
15347+
15348+
[Theory]
15349+
[InlineData("COPY")]
15350+
[InlineData("ADD")]
15351+
public void Extract_Dockerfile_JsonCopyAddSkipArraysBeyondItemBudget(string instruction)
15352+
{
15353+
var maxItems = SymbolExtractor.DockerfileJsonFormMaxItems;
15354+
var items = Enumerable.Range(0, maxItems + 1)
15355+
.Select(i => $"/src{i}");
15356+
var content = instruction + " [" + string.Join(", ", items.Select(item => JsonSerializer.Serialize(item))) + "]\n";
15357+
15358+
var symbols = SymbolExtractor.Extract(1, "dockerfile", content);
15359+
15360+
Assert.Empty(symbols);
15361+
}
15362+
1529415363
[Fact]
1529515364
public void Extract_Dockerfile_DetectsOnbuildCopyDestinationPathSymbols()
1529615365
{
@@ -18269,6 +18338,59 @@ public void Extract_TypeScript_ExcessiveTsconfigPathAliasTargetsTruncatesWithWar
1826918338
}
1827018339
}
1827118340

18341+
[Fact]
18342+
public void Extract_TypeScript_ExcessiveTsconfigPathAliasTotalTargetsTruncatesWithWarning()
18343+
{
18344+
var projectRoot = TestProjectHelper.CreateTempProject("tsconfig_alias_total_targets_symbols");
18345+
try
18346+
{
18347+
var maxTargetsPerRule = GetSymbolExtractorIntConstant("MaxTypeScriptPathAliasTargetsPerRule");
18348+
var maxTotalTargets = GetSymbolExtractorIntConstant("MaxTypeScriptPathAliasTotalTargets");
18349+
var paths = new StringBuilder();
18350+
var remainingTargets = maxTotalTargets;
18351+
var rule = 0;
18352+
while (remainingTargets > 0)
18353+
{
18354+
if (paths.Length > 0)
18355+
paths.Append(',');
18356+
18357+
var targetsForRule = Math.Min(maxTargetsPerRule, remainingTargets);
18358+
paths.Append('"').Append("@skip").Append(rule).Append("/*").Append("\":[");
18359+
for (var target = 0; target < targetsForRule; target++)
18360+
{
18361+
if (target > 0)
18362+
paths.Append(',');
18363+
paths.Append('"').Append("missing").Append(rule).Append('_').Append(target).Append("/*").Append('"');
18364+
}
18365+
18366+
paths.Append(']');
18367+
remainingTargets -= targetsForRule;
18368+
rule++;
18369+
}
18370+
18371+
paths.Append(",\"@hit/*\":[\"src/*\"]");
18372+
18373+
WriteFile(
18374+
projectRoot,
18375+
"tsconfig.json",
18376+
"{\"compilerOptions\":{\"baseUrl\":\".\",\"paths\":{" + paths + "}}}");
18377+
WriteFile(projectRoot, "src/Button.ts", "export const Button = 1;\n");
18378+
var sourcePath = WriteFile(projectRoot, "src/main.ts", "import { Button } from \"@hit/Button\";\n");
18379+
18380+
List<SymbolRecord> symbols = [];
18381+
var stderr = ConsoleCapture.CaptureError(() =>
18382+
symbols = SymbolExtractor.Extract(1, "typescript", File.ReadAllText(sourcePath), sourcePath));
18383+
18384+
Assert.Contains(symbols, s => s.Kind == "import" && s.Name == "@hit/Button");
18385+
Assert.DoesNotContain(symbols, s => s.Kind == "import" && s.Name == "src/Button.ts");
18386+
Assert.Contains("Truncated TypeScript path alias targets", stderr, StringComparison.Ordinal);
18387+
}
18388+
finally
18389+
{
18390+
TestProjectHelper.DeleteDirectory(projectRoot);
18391+
}
18392+
}
18393+
1827218394
[Fact]
1827318395
public void Extract_TypeScript_OverlongTsconfigPathAliasStringsAreIgnoredWithBoundedWarning()
1827418396
{

0 commit comments

Comments
 (0)