Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ public static void UseESRSEndpoint(this WebApplication app)
var content = new StringContent(serializedParamJson, Encoding.UTF8, "application/json");

//Invoke Process Watcher
var response = await appContext.httpClient.PostAsync(config["GapAnalysisProcessing:processwatcherUrl"], content);
var response = await appContext.httpClient.PostAsync(config["GapAnalysisProcessing:processwatcherUrl"], content); // CodeQL [SM03781] We are reading this value from appsettings.json, this is not an user input

return Results.Accepted(locationUrl, gapAnalysisServiceRequest);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ await this.memoryWebClient.ImportDocumentAsync(content: fileStream.BaseStream,

async public Task<DocumentServiceResult> RegisterDocumentFromFileUrl(RegisterDocumentFromFileUrlServiceRequest serviceRequest)
{
//Validate URL to prevent SSRF attack
if (AntiSsrfValidation(serviceRequest.FileUrl))
{
throw new Exception("AntiSSRF validation failed - Invalid or unauthorized URL provided");
}
//Download file from URL then take only fileName from URL
//the file location URL in the document will be mixed with SAS token to get it.
//sample url - https://microsoft.seismic.com/app?ContentId=d6e9f9bb-70d4-4845-a2ad-dd25ecc343d6#/doccenter/a5266a70-9230-4c1e-a553-c5bddcb7a896/doc/%252Fdde0caec0e-9236-f21b-2991-5868e63d3984%252FdfYTZjNDRiZDMtMzEwZS1kNWZkLTNjOGEtNjliYWJjMjhmMmUw%252CPT0%253D%252CUGl0Y2ggRGVjaw%253D%253D%252Flffb13c1f1-d960-4bbe-8685-000afbf5a67f//?mode=view&parentPath=sessionStorage
Expand Down Expand Up @@ -303,6 +308,40 @@ async public Task<MemoryAnswer> AskAboutDocumentSummary(string DocumentId, strin
return await this.memoryWebClient.AskAsync(Question, filter: new MemoryFilter().ByDocument(DocumentId));
}
}

// Anti-SSRF validation method
private bool AntiSsrfValidation(string url)
{
if (string.IsNullOrWhiteSpace(url))
{
return true; // Invalid URL
}

if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? uri))
{
return true; // Invalid URL format
}

//the parameter flows to the validation method
bool isInvalidUri = !IsInAllowedDomain(uri) || !IsHttpsScheme(uri);

//validation method call flows to boolean return statement
return isInvalidUri;
}

//Domain validation helper
private bool IsInAllowedDomain(Uri uri)
{
var allowedDomains = new[] { "microsoft.seismic.com" };
return allowedDomains.Any(domain =>
uri.Host.Equals(domain, StringComparison.OrdinalIgnoreCase) ||
uri.Host.EndsWith($".{domain}", StringComparison.OrdinalIgnoreCase));
}

// HTTPS validation helper
private bool IsHttpsScheme(Uri uri)
{
return uri.Scheme == Uri.UriSchemeHttps;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@
//Get Current YYYYMMDDHHMMSS
var jobId = DateTime.Now.ToString("yyyyMMddHHmmss");
var fileName = $"GAPAnalysisReport-{disclosure_number}-{jobId}";
//validate file name
EnsureSafeSimpleFileName(fileName);
var blobClient = blobContainerClient.GetBlobClient($"{fileName}.md");
using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes(analysis_resultString)))
{
Expand All @@ -209,6 +211,8 @@

//Create BlobClient
var htmlFileName = $"{fileName}.html";
//validate html file name
EnsureSafeSimpleFileName(htmlFileName);
blobClient = blobContainerClient.GetBlobClient(htmlFileName);
byte[] byteArray_html = MarkdownHtmlConverter.Convert(analysis_resultString);

Expand Down Expand Up @@ -245,7 +249,7 @@
//Delete pdf file
System.IO.File.Delete(pdfFileName);
//Delete html file
System.IO.File.Delete(htmlFileName);
System.IO.File.Delete(htmlFileName);// CodeQL [SM00414] htmlFileName validated by EnsureSafeSimpleFileName.
}
else
{
Expand Down Expand Up @@ -293,5 +297,23 @@
return gapAnalysis_response;
//return result.GetValue<string>();
}

// Validate simple file name to prevent path traversal attacks
private static void EnsureSafeSimpleFileName(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("File name is empty or null.");

if (name.Contains("..") || name.Contains("/") || name.Contains("\\"))
throw new ArgumentException("Invalid file name (contains path components or traversal).");

// Whitelist chars: letters, digits, dash, underscore, dot
foreach (char c in name)
{
if (!(char.IsLetterOrDigit(c) || c == '-' || c == '_' || c == '.'))
throw new ArgumentException("Invalid character in file name.");
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,18 @@
{
public static bool Convert(string sourceHtmlFilePath, string targetPdfFilePath)
{
// Validate file paths to prevent command injection
ValidateFilePath(sourceHtmlFilePath);
ValidateFilePath(targetPdfFilePath);

var escapedSourceHtmlFilePath = sourceHtmlFilePath.Replace("\"", "\\\"");
var escapedTargetPdfFilePath = targetPdfFilePath.Replace("\"", "\\\"");
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = IsWindows() ? "wkhtmltopdf.exe" : "/usr/bin/wkhtmltopdf",
Arguments = $"--encoding UTF-8 -q \"{escapedSourceHtmlFilePath}\" \"{escapedTargetPdfFilePath}\"",
Arguments = $"--encoding UTF-8 -q \"{escapedSourceHtmlFilePath}\" \"{escapedTargetPdfFilePath}\"",// CodeQL [SM02383] File paths are validated by ValidateFilePath() to prevent command injection
RedirectStandardInput = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
Expand Down Expand Up @@ -54,5 +58,16 @@
{
return System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows);
}

// Simple validation to prevent command injection
private static void ValidateFilePath(string path)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentException("Invalid path");

// Block command injection characters
if (path.Any(c => ";|&`$<>\n\r".Contains(c)))
throw new ArgumentException("Path contains dangerous characters");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ public async Task CreateTableAsync(
CancellationToken cancellationToken = default)
{
var origInputTableName = tableName;

// Validate tableName parameter before using it in SQL construction
PostgresSchema.ValidateTableName(origInputTableName);

tableName = this.WithSchemaAndTableNamePrefix(tableName);
this._log.LogTrace("Creating table: {0}", tableName);

Expand All @@ -154,12 +158,12 @@ public async Task CreateTableAsync(
using NpgsqlCommand cmd = connection.CreateCommand();

var lockId = GenLockId(tableName);

#pragma warning disable CA2100 // SQL reviewed
#pragma warning disable CA2100 // SQL reviewed
if (!string.IsNullOrEmpty(this._createTableSql))
{
cmd.CommandText = this._createTableSql
.Replace(PostgresConfig.SqlPlaceholdersTableName, tableName, StringComparison.Ordinal)
.Replace(PostgresConfig.SqlPlaceholdersTableName, tableName, StringComparison.Ordinal) // CodeQL [SM03934] tableName parameter is validated by PostgresSchema.ValidateTableName to prevent SQL injection
.Replace(PostgresConfig.SqlPlaceholdersVectorSize, $"{vectorSize}", StringComparison.Ordinal)
.Replace(PostgresConfig.SqlPlaceholdersLockId, $"{lockId}", StringComparison.Ordinal);

Expand Down
Loading