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
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ internal sealed class GetDocumentCommandWorker
private const string InvalidFilenameString = "..";
private const string JsonExtension = ".json";
private const string UnderscoreString = "_";
private static readonly char[] _invalidFilenameCharacters = Path.GetInvalidFileNameChars();
// Note: Path.GetInvalidFileNameChars() is OS-specific. We also explicitly treat directory separators as invalid
// so document names can't be interpreted as subpaths on any platform.
private static readonly char[] _invalidFilenameCharacters = Path.GetInvalidFileNameChars()
// Treat both common separators as invalid regardless of OS to avoid platform-specific behavior.
.Concat([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, '\\'])
.Distinct()
.ToArray();
private static readonly Encoding _utf8EncodingWithoutBOM
= new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

Expand Down Expand Up @@ -415,7 +421,8 @@ private static string GetDocumentPath(string documentName, string fileName, stri
sanitizedDocumentName = sanitizedDocumentName.Replace(InvalidFilenameString, DotString);
}

path = $"{fileName}_{documentName}{JsonExtension}";
// Use the sanitized name to ensure the output path is a file name, not a user-controlled path.
path = $"{fileName}_{sanitizedDocumentName}{JsonExtension}";
}

if (!string.IsNullOrEmpty(outputDirectory))
Expand Down
17 changes: 17 additions & 0 deletions src/Tools/GetDocumentInsider/tests/GetDocumentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,21 @@ public void GetDocument_WithEmptyFileName_Works()
Assert.True(File.Exists(Path.Combine(outputPath.FullName, "Sample.json")));
Assert.True(File.Exists(Path.Combine(outputPath.FullName, "Sample_internal.json")));
}

[Theory]
[InlineData("a/../b", "Sample_a_._b.json")]
[InlineData(@"a\..\b", "Sample_a_._b.json")]
[InlineData("..", "Sample_..json")]
public void GetDocumentPath_SanitizesDocumentName(string documentName, string expectedFileName)
{
// We validate the path generation logic directly to avoid coupling this test to sample app document names.
var method = typeof(GetDocumentCommandWorker).GetMethod("GetDocumentPath", BindingFlags.NonPublic | BindingFlags.Static);
Assert.NotNull(method);

var outputDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
var resultPath = (string)method.Invoke(null, [documentName, "Sample", outputDir]);

Assert.Equal(expectedFileName, Path.GetFileName(resultPath));
Assert.Equal(outputDir, Path.GetDirectoryName(resultPath));
}
}
Loading