fix(api): sanitize request path in internal API key logs (SEC-29)

Strip CR/LF from Request.Path before logging invalid-key and disallowed-path warnings so CodeQL alerts 4 and 5 close without changing 401/403 behavior.
This commit is contained in:
Adam Moussa 2026-08-20 12:14:44 -04:00
parent 4cc550500a
commit 05d87b2e04
No known key found for this signature in database

View file

@ -14,6 +14,27 @@ public class InternalApiKeyMiddleware
"/api/files", "/api/files",
]; ];
private static string SanitizeForLog(string value)
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}
var sanitized = value.Replace("\r", "").Replace("\n", "");
var builder = new StringBuilder(sanitized.Length);
foreach (var c in sanitized)
{
if (!char.IsControl(c))
{
builder.Append(c);
}
}
return builder.ToString();
}
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly byte[] _apiKeyBytes; private readonly byte[] _apiKeyBytes;
private readonly ILogger<InternalApiKeyMiddleware> _logger; private readonly ILogger<InternalApiKeyMiddleware> _logger;
@ -32,20 +53,25 @@ public class InternalApiKeyMiddleware
context.Request.Headers.TryGetValue("X-Internal-Api-Key", out var providedKey) && context.Request.Headers.TryGetValue("X-Internal-Api-Key", out var providedKey) &&
!string.IsNullOrEmpty(providedKey.ToString())) !string.IsNullOrEmpty(providedKey.ToString()))
{ {
var path = context.Request.Path.Value ?? "";
var sanitizedPath = SanitizeForLog(path);
var providedBytes = Encoding.UTF8.GetBytes(providedKey.ToString()); var providedBytes = Encoding.UTF8.GetBytes(providedKey.ToString());
if (!CryptographicOperations.FixedTimeEquals(providedBytes, _apiKeyBytes)) if (!CryptographicOperations.FixedTimeEquals(providedBytes, _apiKeyBytes))
{ {
_logger.LogWarning("Invalid internal API key from {RemoteIp} on {Path}", _logger.LogWarning("Invalid internal API key from {RemoteIp} on {Path}",
context.Connection.RemoteIpAddress, context.Request.Path); context.Connection.RemoteIpAddress, sanitizedPathForLog);
context.Response.StatusCode = 401; context.Response.StatusCode = 401;
return; return;
} }
var path = context.Request.Path.Value ?? "";
if (!AllowedPathPrefixes.Any(prefix => path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))) if (!AllowedPathPrefixes.Any(prefix => path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
{ {
var sanitizedPathForLog = path
.Replace("\r", "")
.Replace("\n", "");
_logger.LogWarning("Internal API key used on disallowed path {Path} from {RemoteIp}", _logger.LogWarning("Internal API key used on disallowed path {Path} from {RemoteIp}",
context.Request.Path, context.Connection.RemoteIpAddress); sanitizedPathForLog, context.Connection.RemoteIpAddress);
context.Response.StatusCode = 403; context.Response.StatusCode = 403;
return; return;
} }