Skip to content

Commit 61e7f2f

Browse files
ervwalterclaude
andcommitted
🔒️ fix: improve proxy header security and clean up debugging code
- Change AllowedHosts delimiter from comma to semicolon - Remove custom host filtering in favor of built-in ForwardedHeadersOptions - Simplify host validation middleware by removing redundant checks - Clean up comments and remove debugging artifacts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 21e3c81 commit 61e7f2f

2 files changed

Lines changed: 22 additions & 30 deletions

File tree

.env.example

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ Withings__ClientId=your-withings-client-id
1717
Withings__ClientSecret=your-withings-client-secret
1818

1919
# Security Configuration
20-
# Comma-separated list of allowed host headers (for production)
20+
# Semicolon-separated list of allowed host headers (for production)
2121
# Use "*" to allow all hosts (not recommended for production)
22-
AllowedHosts=trendweight.com,www.trendweight.com,trendweight.io
22+
AllowedHosts=trendweight.com;www.trendweight.com;trendweight.io

apps/api/TrendWeight/Program.cs

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
11
using System.Net;
22
using System.Text.Json;
33
using System.Text.Json.Serialization;
4-
using Microsoft.AspNetCore.HostFiltering;
54
using Microsoft.AspNetCore.HttpOverrides;
65
using TrendWeight.Infrastructure.Extensions;
76
using TrendWeight.Infrastructure.Middleware;
87

98
var builder = WebApplication.CreateBuilder(args);
109

11-
// Disable ASP.NET Core's built-in host filtering since we have custom validation
12-
builder.Services.Configure<HostFilteringOptions>(options =>
13-
{
14-
options.AllowedHosts = new List<string> { "*" };
15-
});
16-
1710
// Add services to the container
1811
builder.Services.AddControllers()
1912
.AddJsonOptions(options =>
@@ -63,13 +56,20 @@
6356
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
6457

6558
// Clear default networks/proxies to trust headers from load balancers
66-
// Security Note: Host header validation is performed later in the pipeline
6759
options.KnownNetworks.Clear();
6860
options.KnownProxies.Clear();
6961

7062
// Limit proxy chain depth to prevent spoofing
7163
options.ForwardLimit = 2; // Allows for Cloudflare -> DigitalOcean chain
7264
options.RequireHeaderSymmetry = false;
65+
66+
// Configure allowed hosts for forwarded headers (semicolon-separated)
67+
// This validates the host header after forwarded headers are processed
68+
var allowedHosts = builder.Configuration["AllowedHosts"];
69+
if (!string.IsNullOrEmpty(allowedHosts) && allowedHosts != "*")
70+
{
71+
options.AllowedHosts = allowedHosts.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
72+
}
7373
});
7474

7575
var app = builder.Build();
@@ -81,32 +81,24 @@
8181
app.UseHttpLogging();
8282

8383
// Use forwarded headers from proxies
84+
// The ForwardedHeaders middleware also validates allowed hosts if configured
8485
app.UseForwardedHeaders();
8586

86-
// Validate host header for security (prevent host header injection)
87-
if (app.Environment.IsProduction())
87+
88+
// Validate host header after ForwardedHeaders middleware
89+
var allowedHosts = app.Configuration["AllowedHosts"];
90+
if (!string.IsNullOrEmpty(allowedHosts) && allowedHosts != "*")
8891
{
92+
var allowedHostList = allowedHosts.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
8993
app.Use(async (context, next) =>
9094
{
91-
var allowedHostsConfig = app.Configuration["AllowedHosts"];
92-
93-
if (!string.IsNullOrEmpty(allowedHostsConfig) && allowedHostsConfig != "*")
95+
var host = context.Request.Host.Host;
96+
if (!allowedHostList.Contains(host, StringComparer.OrdinalIgnoreCase))
9497
{
95-
var allowedHosts = allowedHostsConfig.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
96-
var requestHost = context.Request.Host.Host;
97-
98-
// Check both with and without port
99-
var hostMatches = allowedHosts.Any(h =>
100-
h.Equals(requestHost, StringComparison.OrdinalIgnoreCase) ||
101-
h.Equals(context.Request.Host.Value, StringComparison.OrdinalIgnoreCase));
102-
103-
if (!hostMatches)
104-
{
105-
app.Logger.LogWarning("Rejected request with invalid host header: {Host}", context.Request.Host.Value);
106-
context.Response.StatusCode = 400;
107-
await context.Response.WriteAsync("Bad Request: Invalid Host header");
108-
return;
109-
}
98+
app.Logger.LogWarning("Rejected request with invalid host: {Host}", host);
99+
context.Response.StatusCode = 400;
100+
await context.Response.WriteAsync("Invalid host header");
101+
return;
110102
}
111103
await next();
112104
});

0 commit comments

Comments
 (0)