Skip to content

Commit

Permalink
✨ HSTS is now disabled for localhost requests by default
Browse files Browse the repository at this point in the history
  • Loading branch information
Marthijn van den Heuvel committed Aug 19, 2024
1 parent d45cdec commit a50c7be
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,82 @@ namespace Sidio.Web.Security.AspNetCore.Tests.Middleware;

public sealed class StrictTransportSecurityMiddlewareTests
{
private readonly Fixture _fixture = new();

[Fact]
public async Task InvokeAsync_WithDefaultOptions_ShouldSetStrictTransportSecurityHeader()
{
// arrange
var middleware = new StrictTransportSecurityMiddleware(
_ => Task.CompletedTask,
Options.Create(new StrictTransportSecurityHeaderOptions()),
Options.Create(new StrictTransportSecurityHeaderOptions { DisableForLocalhostRequests = true }),
NullLogger<StrictTransportSecurityMiddleware>.Instance);

var context = new DefaultHttpContext
{
Request =
{
Host = new HostString(_fixture.Create<string>())
}
};

// act
await middleware.InvokeAsync(context);

// assert
context.Response.Headers.Should().ContainKey("Strict-Transport-Security");
}

[Theory]
[InlineData("localhost")]
[InlineData("LoCalHoSt")]
[InlineData("127.0.0.1")]
[InlineData("::1")]

public async Task InvokeAsync_WithDisabledForLocalhostRequests_ShouldNotSetStrictTransportSecurityHeader(string host)
{
// arrange
var middleware = new StrictTransportSecurityMiddleware(
_ => Task.CompletedTask,
Options.Create(new StrictTransportSecurityHeaderOptions { DisableForLocalhostRequests = true }),
NullLogger<StrictTransportSecurityMiddleware>.Instance);

var context = new DefaultHttpContext
{
Request =
{
Host = new HostString(host)
}
};

// act
await middleware.InvokeAsync(context);

// assert
context.Response.Headers.Should().BeEmpty();
}

[Theory]
[InlineData("localhost")]
[InlineData("LoCalHoSt")]
[InlineData("127.0.0.1")]
[InlineData("::1")]

public async Task InvokeAsync_WithEnabledForLocalhostRequests_ShouldSetStrictTransportSecurityHeader(string host)
{
// arrange
var middleware = new StrictTransportSecurityMiddleware(
_ => Task.CompletedTask,
Options.Create(new StrictTransportSecurityHeaderOptions { DisableForLocalhostRequests = false }),
NullLogger<StrictTransportSecurityMiddleware>.Instance);

var context = new DefaultHttpContext();
var context = new DefaultHttpContext
{
Request =
{
Host = new HostString(host)
}
};

// act
await middleware.InvokeAsync(context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,23 @@ public Task InvokeAsync(HttpContext context)
throw new InvalidOperationException("The header is not set.");
}

if (!context.Response.AppendHeaderIfNotExists(Header))
if (ShouldAppendHeader(context))
{
_logger.LogWarning("The header `{HeaderName}` is already set", Header.Name);
if (!context.Response.AppendHeaderIfNotExists(Header))
{
_logger.LogWarning("The header `{HeaderName}` is already set", Header.Name);
}
}
else
{
_logger.LogDebug("The header `{HeaderName}` is not appended due to specific conditions", Header.Name);
}

return _next(context);
}

protected virtual bool ShouldAppendHeader(HttpContext context)
{
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ namespace Sidio.Web.Security.AspNetCore.Middleware;

internal sealed class StrictTransportSecurityMiddleware : HttpHeaderMiddleware
{
/// <summary>
/// The hosts that should be excluded from the HSTS header.
/// </summary>
private static readonly string[] ExcludedHosts = ["localhost", "127.0.0.1", "[::1]"];

private readonly IOptions<StrictTransportSecurityHeaderOptions> _options;

public StrictTransportSecurityMiddleware(
RequestDelegate next,
IOptions<StrictTransportSecurityHeaderOptions> options,
Expand All @@ -16,5 +23,24 @@ public StrictTransportSecurityMiddleware(
HttpHeaderFactory.Create<StrictTransportSecurityHeader>(options.Value.ToString()),
logger)
{
_options = options;
}

protected override bool ShouldAppendHeader(HttpContext context)
{
if (!_options.Value.DisableForLocalhostRequests)
{
return true;
}

for (var i = ExcludedHosts.Length - 1; i >= 0 ; i--)
{
if (string.Equals(context.Request.Host.Host, ExcludedHosts[i], StringComparison.OrdinalIgnoreCase))
{
return false;
}
}

return true;
}
}
1 change: 1 addition & 0 deletions src/Sidio.Web.Security.Examples.AspNetCore/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
app.UseStrictTransportSecurity(new StrictTransportSecurityHeaderOptions
{
MaxAge = 0,
DisableForLocalhostRequests = false, // disabled for testing purposes
});
app.UseContentSecurityPolicy(
(services, b) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ public sealed class StrictTransportSecurityHeaderOptions : IHttpHeaderOptions
/// </summary>
public bool Preload { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to disable localhost requests.
/// If set to true, localhost will be excluded from the HSTS header.
/// </summary>
public bool DisableForLocalhostRequests { get; set; } = true;

/// <inheritdoc />
public string Value => ToString();

Expand Down

0 comments on commit a50c7be

Please sign in to comment.