Severity by source
AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N
MITM network position required raises AC:H; attacker needs no credentials; limited confidentiality and integrity impact from traffic interception only.
Primary rating from Vendor (https://github.com/gofiber/fiber).
CVSS VectorVendor: https://github.com/gofiber/fiber
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N
Lifecycle Timeline
2DescriptionCVE.org
Summary
The helmet middleware in gofiber/fiber never sets the Strict-Transport-Security (HSTS) response header, even when HSTSMaxAge is explicitly configured, because the condition check at helmet.go:67 uses c.Protocol() - which returns the HTTP protocol version string (e.g., "HTTP/1.1", "HTTP/2.0") - instead of c.Scheme() - which returns the URL scheme ("http" or "https"). Since c.Protocol() never equals "https" in any real deployment, the HSTS header is permanently disabled, defeating the security protection.
Details
Root cause: middleware/helmet/helmet.go, line 67:
if c.Protocol() == "https" && cfg.HSTSMaxAge != 0 {c.Protocol() (defined at req.go:865-867) delegates to fasthttp.Request.Header.Protocol(), which returns the HTTP protocol version:
"HTTP/1.1"for HTTP/1.1 connections"HTTP/2.0"for HTTP/2 connections
The correct method is c.Scheme() (defined at req.go:844-862), which returns:
"http"for plain HTTP connections"https"for TLS connections
Since "HTTP/1.1" != "https" always evaluates to true, the entire HSTS block (lines 67-76) is dead code.
Note on test coverage: The existing helmet test (helmet_test.go) passes because it uses ctx.Request.Header.SetProtocol("https") to artificially force Protocol() to return "https". However, fasthttp.Request.Header.SetProtocol() sets the HTTP version field, and real HTTP requests never have protocol "https" - they have "HTTP/1.1" or "HTTP/2.0". The test is validating the wrong thing.
PoC
Clean-checkout maintainer-runnable recipe:
- Save the following as
middleware/helmet/poc_hsts_test.go:
package helmet
import (
"crypto/tls"
"net/http/httptest"
"testing"
"github.com/gofiber/fiber/v3"
)
func Test_PoC_HSTS_NeverSet(t *testing.T) {
app := fiber.New()
app.Use(New(Config{
HSTSMaxAge: 31536000,
}))
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("ok")
})
// Simulate HTTPS connection
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
req.TLS = &tls.ConnectionState{}
resp, _ := app.Test(req)
hsts := resp.Header.Get("Strict-Transport-Security")
if hsts == "" {
t.Log("BUG CONFIRMED: HSTS header not set. c.Protocol() returns 'HTTP/1.1', not 'https'")
t.Log("Fix: change c.Protocol() == 'https' to c.Scheme() == 'https' on line 67")
}
}- Run:
go test -run Test_PoC_HSTS_NeverSet -v ./middleware/helmet/
Expected vulnerable output:
=== RUN Test_PoC_HSTS_NeverSet
BUG CONFIRMED: HSTS header not set. c.Protocol() returns 'HTTP/1.1', not 'https'
Fix: change c.Protocol() == 'https' to c.Scheme() == 'https' on line 67
--- PASS: Test_PoC_HSTS_NeverSetExpected output after fix:
=== RUN Test_PoC_HSTS_NeverSet
--- PASS: Test_PoC_HSTS_NeverSet
(HSTS header is set: "max-age=31536000; includeSubDomains")Observed output from this environment (commit ee98695f):
=== RUN Test_PoC_HSTS_NeverSet
poc_hsts_test.go:39: HSTS header value: ""
poc_hsts_test.go:42: BUG CONFIRMED: HSTS header is NOT set even over TLS
poc_hsts_test.go:43: Root cause: helmet.go:67 uses c.Protocol() which returns HTTP version
poc_hsts_test.go:44: c.Protocol() returns 'HTTP/1.1' not 'https'
poc_hsts_test.go:45: Fix: use c.Scheme() == 'https' instead of c.Protocol() == 'https'
--- PASS: Test_PoC_HSTS_NeverSetNegative/control case: With HSTSMaxAge: 0 (default), HSTS is correctly not set (this is expected behavior, not a bug).
Cleanup: Remove poc_hsts_test.go after verification.
Impact
The HSTS header is never applied in production, leaving all users vulnerable to:
- SSL stripping attacks: An active network attacker can downgrade HTTPS connections to HTTP, intercepting traffic between the client and server.
- Protocol downgrade: Without HSTS, browsers will silently accept HTTP connections to the site, even if the site supports HTTPS.
- Cookie theft over HTTP: Session cookies without the
Secureflag will be sent over HTTP if the user is tricked into an HTTP connection.
This affects any application that:
- Uses the
helmetmiddleware - Configures
HSTSMaxAge > 0expecting HSTS protection - Serves traffic over HTTPS
The vulnerability requires an active MITM attacker on the network path, which is realistic in public Wi-Fi, corporate networks, and ISP-level scenarios.
Suggested remediation
In middleware/helmet/helmet.go, line 67, replace c.Protocol() with c.Scheme():
// Before (broken):
if c.Protocol() == "https" && cfg.HSTSMaxAge != 0 {
// After (fixed):
if c.Scheme() == "https" && cfg.HSTSMaxAge != 0 {Additionally, update the existing test to use a realistic TLS simulation instead of SetProtocol("https"):
// Before (artificial - sets HTTP version to "https" which never happens in practice):
ctx.Request.Header.SetProtocol("https")
// After (realistic - simulates TLS connection):
ctx.RequestCtx().Request.Header.SetProtocol("HTTP/1.1")
ctx.RequestCtx().TLS = &tls.ConnectionState{}Regression test: Add a test case that verifies HSTS is set when req.TLS is non-nil and HSTSMaxAge > 0, without using SetProtocol.
AnalysisAI
HSTS header delivery is permanently broken in gofiber/fiber's helmet middleware due to a logic error that compares the HTTP protocol version string ('HTTP/1.1', 'HTTP/2.0') against the literal string 'https' - a comparison that is always false in any real deployment, making the entire HSTS conditional block dead code. Applications using Fiber that configure HSTSMaxAge expecting HSTS protection receive none, silently eliminating a security control that operators believe is active. …
Unlock full vulnerability intelligence
- Risk assessment & exploitation conditions
- Attack chain visualization
- Remediation with exact patch versions
- Threat intelligence from 22 sources
- Personal watchlist & email alerts
Free forever · No credit card required
Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata
Vulnerability AssessmentAI
| Exploitation | Three simultaneous conditions are required: (1) the target Fiber application imports the helmet middleware and configures HSTSMaxAge to a non-zero value - applications using the default HSTSMaxAge: 0 are not affected because HSTS emission was never expected; (2) the application serves at least some traffic over HTTPS, since HTTP-only deployments have no HSTS expectation; and (3) the attacker achieves an active MITM position on the network path between the victim client and the server. … Additional conditions and limiting factors are described in the full assessment. |
| Risk Assessment | The provided CVSS score of 4.8 (AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N) reflects realistic attack difficulty accurately: exploitation requires an active MITM position (AC:H), substantially limiting opportunistic or automated exploitation. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in. |
| Exploit Scenario | An attacker sharing a network segment with a target user - for example, on public Wi-Fi or via ARP spoofing on a corporate LAN - deploys an SSL-stripping proxy to intercept the victim's initial connection to the Fiber application. Because helmet never emits a Strict-Transport-Security header despite being configured to do so, the victim's browser holds no HSTS policy for the domain and silently accepts the HTTP downgrade. … |
| Remediation | The definitive fix is a one-line change in middleware/helmet/helmet.go at line 67: replace c.Protocol() with c.Scheme() so the condition reads if c.Scheme() == 'https' && cfg.HSTSMaxAge != 0. … Detailed patch versions, workarounds, and compensating controls in full report. |
Threat intelligence, references, and detailed analysis are available after sign-in.
Same technique Information Disclosure
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-42356
GHSA-gv83-gqw6-9j2c