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
32 changes: 31 additions & 1 deletion ChatGPTExport/Assets/AssetLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ ExistingAssetLocator existingAssetLocator
) : IAssetLocator
{
private List<string>? cachedSourceList = null;
private static readonly HashSet<string> AllowedRoles = new(StringComparer.OrdinalIgnoreCase)
{
"system",
"user",
"assistant",
"tool",
"function"
};

public string? GetMarkdownMediaAsset(AssetRequest assetRequest)
{
Expand All @@ -33,7 +41,8 @@ private IEnumerable<string> GetCachedSourceFiles(string searchPattern)
if (file != null)
{
var withoutPath = fileSystem.Path.GetFileName(file);
var assetsPath = $"{assetRequest.Role}-assets";
var sanitizedRole = SanitizeRole(assetRequest.Role);
var assetsPath = $"{sanitizedRole}-assets";
var assetsDir = fileSystem.Path.Join(destinationDirectory.FullName, assetsPath);
if (fileSystem.Directory.Exists(assetsDir) == false)
{
Expand Down Expand Up @@ -63,5 +72,26 @@ private IEnumerable<string> GetCachedSourceFiles(string searchPattern)

return null;
}

private string SanitizeRole(string role)
{
if (AllowedRoles.Contains(role))
{
return role;
}

var invalidChars = fileSystem.Path.GetInvalidFileNameChars()
.Concat(new[]
{
fileSystem.Path.DirectorySeparatorChar,
fileSystem.Path.AltDirectorySeparatorChar,
'.'
})
.ToHashSet();

var cleaned = new string(role.Where(c => !invalidChars.Contains(c)).ToArray());

return AllowedRoles.Contains(cleaned) ? cleaned : "unknown";
}
}
}
34 changes: 34 additions & 0 deletions ChatGTPExportTests/Assets/AssetLocatorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using ChatGPTExport.Assets;
using System.IO.Abstractions.TestingHelpers;

namespace ChatGTPExportTests.Assets
{
public class AssetLocatorTests
{
[Fact]
public void Malformed_role_does_not_escape_destination()
{
var fs = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ MockUnixSupport.Path(@"c:\source\img.png"), new MockFileData("data") }
});
fs.AddDirectory(MockUnixSupport.Path(@"c:\dest"));

var sourceDir = fs.DirectoryInfo.New(MockUnixSupport.Path(@"c:\source"));
var destDir = fs.DirectoryInfo.New(MockUnixSupport.Path(@"c:\dest"));
var existing = new ExistingAssetLocator(fs, destDir);
var locator = new AssetLocator(fs, sourceDir, destDir, existing);

var request = new AssetRequest("img.png", "../evil", null, null);

var result = locator.GetMarkdownMediaAsset(request);

var expected = fs.Path.Combine(destDir.FullName, "unknown-assets", "img.png");
Assert.True(fs.File.Exists(expected));
Assert.Equal("![img.png](./unknown-assets/img.png) ", result);

var traversal = fs.Path.Combine(destDir.FullName, "..", "evil-assets", "img.png");
Assert.False(fs.File.Exists(traversal));
}
}
}
1 change: 1 addition & 0 deletions ChatGTPExportTests/ChatGTPExportTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="22.0.15" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading