Fix CodeQL security vulnerabilities in CORS, JWT, logging, and GitHub Actions#53
Fix CodeQL security vulnerabilities in CORS, JWT, logging, and GitHub Actions#53PhantomDave merged 3 commits intomainfrom
Conversation
Co-authored-by: PhantomDave <34485699+PhantomDave@users.noreply.github.com>
Co-authored-by: PhantomDave <34485699+PhantomDave@users.noreply.github.com>
There was a problem hiding this comment.
Pull Request Overview
This PR addresses five CodeQL security vulnerabilities related to authentication, logging, and deployment workflows. The changes remove the wildcard CORS origin, make JWT HTTPS validation environment-aware, replace console logging with structured logging, add specific exception handling, and fix token exposure in GitHub Actions.
Key changes:
- Removed wildcard
"*"from CORS allowed origins - Made JWT
RequireHttpsMetadataenvironment-dependent (enabled in production) - Replaced
Console.WriteLinewithILogger<FileImportService>for secure, structured logging
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
PhantomDave.BankTracking.Api/Services/FileImportService.cs |
Injected ILogger<FileImportService>, replaced Console.WriteLine with structured logging, added specific exception types (DecoderFallbackException, Exception) instead of empty catch blocks |
PhantomDave.BankTracking.Api/Program.cs |
Removed wildcard "*" from CORS origins, made RequireHttpsMetadata conditional on environment (false in dev, true in production) |
.github/workflows/deploy-tailscale.yml |
Fixed token exposure by piping GITHUB_TOKEN to stdin locally instead of echoing in remote shell |
Comments suppressed due to low confidence (7)
PhantomDave.BankTracking.Api/Program.cs:88
- The CORS policy is hardcoded to
localhostorigins and will apply to all environments including production. In production, this will block legitimate requests from the actual production frontend URL. Consider making CORS origins configurable viaappsettings.jsonor environment variables, and usingbuilder.Environment.IsDevelopment()to conditionally allow localhost origins only in development:
var allowedOrigins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>()
?? (builder.Environment.IsDevelopment()
? new[] { "http://localhost:4200", "http://localhost:5095" }
: Array.Empty<string>()); builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
policy
.WithOrigins("http://localhost:4200", "http://localhost:5095")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials());
});
PhantomDave.BankTracking.Api/Services/FileImportService.cs:176
- This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
foreach (var keyword in headerKeywords)
{
if (lowerText.Contains(keyword))
{
score += 10;
break;
}
}
PhantomDave.BankTracking.Api/Services/FileImportService.cs:263
- Inefficient use of 'ContainsKey' and indexer.
if (input.ColumnMappings.TryGetValue("Date", out var dateColumn) && row.ContainsKey(dateColumn))
PhantomDave.BankTracking.Api/Services/FileImportService.cs:272
- Inefficient use of 'ContainsKey' and indexer.
if (input.ColumnMappings.TryGetValue("Amount", out var amountColumn) && row.ContainsKey(amountColumn))
PhantomDave.BankTracking.Api/Services/FileImportService.cs:280
- Inefficient use of 'ContainsKey' and indexer.
if (input.ColumnMappings.TryGetValue("Description", out var descriptionColumn) && row.ContainsKey(descriptionColumn))
PhantomDave.BankTracking.Api/Services/FileImportService.cs:270
- These 'if' statements can be combined.
if (input.ColumnMappings.TryGetValue("Date", out var dateColumn) && row.ContainsKey(dateColumn))
{
if (DateTime.TryParseExact(row[dateColumn], input.DateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date))
{
// Ensure UTC kind to satisfy Npgsql 'timestamp with time zone' requirement
record.Date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
}
}
PhantomDave.BankTracking.Api/Services/FileImportService.cs:278
- These 'if' statements can be combined.
if (input.ColumnMappings.TryGetValue("Amount", out var amountColumn) && row.ContainsKey(amountColumn))
{
if (decimal.TryParse(row[amountColumn], NumberStyles.Any, CultureInfo.InvariantCulture, out var amount))
{
record.Amount = amount;
}
}
| _logger.LogDebug("Processed {ProcessedRows} / {TotalRows}, Failed: {FailedRows}", | ||
| records.Count + failedCount, parsedData.Rows.Count, failedCount); | ||
| } | ||
|
|
There was a problem hiding this comment.
The debug log is being written inside the loop for every row, which could generate excessive log output. This should be moved outside the foreach loop to log the final count only once after all rows have been processed.
| _logger.LogDebug("Processed {ProcessedRows} / {TotalRows}, Failed: {FailedRows}", | |
| records.Count + failedCount, parsedData.Rows.Count, failedCount); | |
| } | |
| } | |
| _logger.LogDebug("Processed {ProcessedRows} / {TotalRows}, Failed: {FailedRows}", | |
| records.Count + failedCount, parsedData.Rows.Count, failedCount); |
| @@ -239,8 +242,9 @@ private static Encoding DetectEncoding(Stream stream) | |||
| Encoding.UTF8.GetString(buffer, 0, bytesRead); | |||
There was a problem hiding this comment.
Encoding.UTF8.GetString by default uses DecoderFallback.ReplacementFallback, which replaces invalid bytes with '�' instead of throwing DecoderFallbackException. The catch block will never be reached. To properly detect UTF-8, you need to create a UTF-8 decoder with DecoderFallback.ExceptionFallback:
var utf8 = Encoding.GetEncoding("UTF-8", new EncoderExceptionFallback(), new DecoderExceptionFallback());
utf8.GetString(buffer, 0, bytesRead);| Encoding.UTF8.GetString(buffer, 0, bytesRead); | |
| var strictUtf8 = Encoding.GetEncoding("UTF-8", new EncoderExceptionFallback(), new DecoderExceptionFallback()); | |
| strictUtf8.GetString(buffer, 0, bytesRead); |
| // WARNING: RequireHttpsMetadata should be true in production environments | ||
| // Set to false only for local development |
There was a problem hiding this comment.
The comment says "Set to false only for local development" but the code builder.Environment.IsDevelopment() == false evaluates to true in production (not development) and false in development. Consider rephrasing the comment to be clearer:
// RequireHttpsMetadata is disabled in development and enabled in production| // WARNING: RequireHttpsMetadata should be true in production environments | |
| // Set to false only for local development | |
| // RequireHttpsMetadata is disabled in development and enabled in production | |
| // (i.e., set to false in development, true in production) |
| options.RequireHttpsMetadata = false; // enable HTTPS in production | ||
| // WARNING: RequireHttpsMetadata should be true in production environments | ||
| // Set to false only for local development | ||
| options.RequireHttpsMetadata = builder.Environment.IsDevelopment() == false; |
There was a problem hiding this comment.
The expression 'A == false' can be simplified to '!A'.
| options.RequireHttpsMetadata = builder.Environment.IsDevelopment() == false; | |
| options.RequireHttpsMetadata = !builder.Environment.IsDevelopment(); |
| catch (Exception ex) | ||
| { | ||
| failedCount++; | ||
| _logger.LogWarning(ex, "Failed to parse row {RowIndex} during import", records.Count + failedCount); | ||
| } |
There was a problem hiding this comment.
Generic catch clause.
| catch (Exception ex) | |
| { | |
| failedCount++; | |
| _logger.LogWarning(ex, "Failed to parse row {RowIndex} during import", records.Count + failedCount); | |
| } | |
| catch (FormatException ex) | |
| { | |
| failedCount++; | |
| _logger.LogWarning(ex, "Failed to parse row {RowIndex} due to format error during import", records.Count + failedCount); | |
| } | |
| catch (KeyNotFoundException ex) | |
| { | |
| failedCount++; | |
| _logger.LogWarning(ex, "Failed to parse row {RowIndex} due to missing column during import", records.Count + failedCount); | |
| } | |
| catch (InvalidCastException ex) | |
| { | |
| failedCount++; | |
| _logger.LogWarning(ex, "Failed to parse row {RowIndex} due to invalid cast during import", records.Count + failedCount); | |
| } |
CodeQL identified five security vulnerabilities across authentication, logging, and deployment workflows.
Changes
"*"from allowed origins that permitted any site to make authenticated requestsRequireHttpsMetadatanow respects environment—disabled only in developmentConsole.WriteLinewithILogger<FileImportService>to prevent information disclosureExample
Before:
After:
CodeQL analysis: 0 alerts remaining.
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.