From 6f7b98d149914276ec31394daf06d25984bc179a Mon Sep 17 00:00:00 2001 From: claudiamurialdo <33756655+claudiamurialdo@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:14:33 -0300 Subject: [PATCH] Fix flaky GXDBFilePathTest by deriving base through ResolveUri (#1292) The assertion compared the resolved malicious path against Preferences.getBLOB_PATH() directly and used `new Uri(newFileName)`, which throws "Invalid URI: The format of the URI could not be determined" when ResolveUri returns a relative path. This happened intermittently: when a prior test in the same process sets GxContext.IsHttpContext = true (via GxNetCoreStartup), the cached blobPath gets rooted differently while ResolveUri can still reresolve paths against the current working directory, making the StartsWith check fail (or new Uri throw) even when no real traversal occurred. Use Uri.TryCreate(UriKind.Absolute) with a fallback so the resolution no longer throws, and derive the comparison base from ResolveUri of a known-safe filename, so both sides go through the same code path and share any environment-dependent rooting. The security invariant is unchanged: resolved malicious inputs must stay inside the blob/multimedia directory. Backport of the test fix from #1272. Co-authored-by: Claude Opus 4.8 (1M context) --- .../test/DotNetUnitTest/FileIO/FileIOTests.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/dotnet/test/DotNetUnitTest/FileIO/FileIOTests.cs b/dotnet/test/DotNetUnitTest/FileIO/FileIOTests.cs index 916c6a895..a547c8089 100644 --- a/dotnet/test/DotNetUnitTest/FileIO/FileIOTests.cs +++ b/dotnet/test/DotNetUnitTest/FileIO/FileIOTests.cs @@ -91,20 +91,21 @@ public void GXDBFilePathTest() "content%5c..%5c..%5c..%5cdocument.aspx","content%255c%252e%252e%255c%252e%252e%255c%252e%252e%255cdocument.aspx","content%255c..%255c..%255c..%255cdocument.aspx", "content%c0%af..%c0%af..%c0%af..%c0%afdocument.aspx","content%c1%9c..%c1%9c..%c1%9c..%c1%9cdocument.aspx"}; + string safeResolved = GXDbFile.ResolveUri($"{GXDbFile.Scheme}:safe.txt", false); + string safeResolvedPath = Uri.TryCreate(safeResolved, UriKind.Absolute, out Uri safeUri) && safeUri.IsFile + ? safeUri.LocalPath + : safeResolved; + string fullBase = Path.GetDirectoryName(Path.GetFullPath(safeResolvedPath)) + Path.DirectorySeparatorChar; + foreach (string fileName in filesName) { string newFileName = GXDbFile.ResolveUri($"{GXDbFile.Scheme}:{fileName}", false); - string baseDir = Preferences.getBLOB_PATH(); - try - { - bool isOK = new Uri(newFileName).LocalPath.StartsWith(Path.GetFullPath(baseDir), StringComparison.OrdinalIgnoreCase); - Assert.True(isOK); - } - catch (Exception ex) - { - - Assert.True(false, $"FileName:{newFileName} Error:{ex.Message} ExternalProvider:{ServiceFactory.GetExternalProvider()?.GetType().FullName} fileName:{fileName}"); - } + string resolvedPath = Uri.TryCreate(newFileName, UriKind.Absolute, out Uri parsedUri) && parsedUri.IsFile + ? parsedUri.LocalPath + : newFileName; + string fullResolved = Path.GetFullPath(resolvedPath); + bool isOK = fullResolved.StartsWith(fullBase, StringComparison.OrdinalIgnoreCase); + Assert.True(isOK, $"Path traversal detected: resolved '{fullResolved}' is outside base '{fullBase}' for input '{fileName}'"); } }