-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Security fixes: Sanitize user input in logs (CodeQL High Severity) #172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -11,6 +11,23 @@ public class ApiLoggingMiddleware | |||||
| private readonly RequestDelegate _next; | ||||||
| private readonly ILogger<ApiLoggingMiddleware> _logger; | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Sanitizes user input for safe logging by removing newlines and control characters. | ||||||
| /// </summary> | ||||||
| private static string SanitizeForLog(string? input) | ||||||
| { | ||||||
| if (string.IsNullOrEmpty(input)) | ||||||
| return string.Empty; | ||||||
|
|
||||||
| // Remove newlines and carriage returns | ||||||
| var sanitized = input.Replace("\r", "").Replace("\n", ""); | ||||||
|
|
||||||
| // Remove other control characters | ||||||
| sanitized = new string(sanitized.Where(c => !char.IsControl(c) || c == ' ').ToArray()); | ||||||
|
|
||||||
| return sanitized; | ||||||
| } | ||||||
|
Comment on lines
+17
to
+29
|
||||||
|
|
||||||
| /// <summary> | ||||||
| /// Initializes a new instance of the <see cref="ApiLoggingMiddleware"/> class. | ||||||
| /// </summary> | ||||||
|
|
@@ -64,8 +81,8 @@ public async Task InvokeAsync(HttpContext context) | |||||
|
|
||||||
| _logger.LogError(ex, | ||||||
| "API request failed. Method: {Method}, Path: {Path}, CorrelationId: {CorrelationId}, Duration: {Duration}ms", | ||||||
| context.Request.Method, | ||||||
| context.Request.Path, | ||||||
| SanitizeForLog(context.Request.Method), | ||||||
| SanitizeForLog(context.Request.Path.ToString()), | ||||||
| correlationId, | ||||||
|
||||||
| correlationId, | |
| SanitizeForLog(correlationId), |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The correlationId is not sanitized when added to the logging scope dictionary, but it should be since it can be controlled by users through the X-Correlation-ID HTTP header. An attacker could inject malicious content that may appear in structured logs.
Change line 174 to:
["CorrelationId"] = SanitizeForLog(correlationId),This is consistent with how other user-controlled inputs like Method and Path are sanitized in this same scope at lines 175-176.
| ["CorrelationId"] = correlationId, | |
| ["CorrelationId"] = SanitizeForLog(correlationId), |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -99,12 +99,12 @@ public async Task<AzureValidationMetadata> ValidateNameAsync(string resourceName | |
| // Use appropriate validation method based on scope | ||
| if (isGlobalScope) | ||
| { | ||
| _logger.LogInformation("Using CheckNameAvailability API for globally unique resource: {ResourceType}", resourceType); | ||
| _logger.LogInformation("Using CheckNameAvailability API for globally unique resource: {ResourceType}", SanitizeForLog(resourceType)); | ||
|
||
| validationResult = await CheckNameAvailabilityAsync(resourceName, resourceType, settings); | ||
| } | ||
| else | ||
| { | ||
| _logger.LogInformation("Using Resource Graph query for scoped resource: {ResourceType}", resourceType); | ||
| _logger.LogInformation("Using Resource Graph query for scoped resource: {ResourceType}", SanitizeForLog(resourceType)); | ||
| validationResult = await CheckResourceExistsAsync(resourceName, resourceType, settings); | ||
| } | ||
|
|
||
|
|
@@ -580,7 +580,7 @@ private async Task<string> GetClientSecretAsync(AzureValidationSettings settings | |
| var resourceTypeInfo = await GetResourceTypeInfoAsync(resourceType); | ||
| if (resourceTypeInfo == null) | ||
| { | ||
| _logger.LogWarning("Could not find resource type info for {ResourceType}", resourceType); | ||
| _logger.LogWarning("Could not find resource type info for {ResourceType}", SanitizeForLog(resourceType)); | ||
| return (false, new List<string>()); | ||
| } | ||
|
|
||
|
|
@@ -668,7 +668,7 @@ private async Task<string> GetClientSecretAsync(AzureValidationSettings settings | |
| } | ||
| catch (Exception ex) | ||
| { | ||
| _logger.LogWarning(ex, "Error getting resource type info for {ResourceType}", resourceTypeShortName); | ||
| _logger.LogWarning(ex, "Error getting resource type info for {ResourceType}", SanitizeForLog(resourceTypeShortName)); | ||
| return null; | ||
| } | ||
| } | ||
|
|
@@ -687,7 +687,7 @@ private async Task<string> GetClientSecretAsync(AzureValidationSettings settings | |
| var resourceTypeInfo = await GetResourceTypeInfoAsync(resourceType); | ||
| if (resourceTypeInfo == null) | ||
| { | ||
| _logger.LogWarning("Could not find resource type info for {ResourceType}, falling back to Resource Graph", resourceType); | ||
| _logger.LogWarning("Could not find resource type info for {ResourceType}, falling back to Resource Graph", SanitizeForLog(resourceType)); | ||
| return await CheckResourceExistsAsync(resourceName, resourceType, settings); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sanitization logic removes newlines and control characters, but there's a subtle issue with the implementation. After removing
\rand\non line 23, line 26 filters out all control characters except space. However, since\rand\nare control characters, they're already handled by line 26'schar.IsControl()check, making line 23 redundant.More importantly, the current implementation may not handle all attack vectors. Consider these potential issues:
\t) are control characters and will be removed, which is good|| c == ' 'preserves spaces, which is intentional\u2028(Line Separator) and\u2029(Paragraph Separator) are NOT considered control characters bychar.IsControl()but can still be used for log injectionFor more robust sanitization, consider: