checker: flag the deprecated Public-Key-Pins (HPKP) header
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing

This commit is contained in:
nemunaire 2026-06-18 11:03:27 +09:00
commit 513a73f17f
3 changed files with 64 additions and 0 deletions

View file

@ -24,6 +24,8 @@ relies on TLS for transport.
| `http.x_content_type_options` | Verifies that responses set X-Content-Type-Options: nosniff. | WARNING | | `http.x_content_type_options` | Verifies that responses set X-Content-Type-Options: nosniff. | WARNING |
| `http.x_xss_protection` | Reports the legacy X-XSS-Protection header; warns on filtering mode (can introduce XSS), absent/`0` are fine, CSP is the real defense. | WARNING | | `http.x_xss_protection` | Reports the legacy X-XSS-Protection header; warns on filtering mode (can introduce XSS), absent/`0` are fine, CSP is the real defense. | WARNING |
| `http.expect_ct` | Flags the deprecated Expect-CT header (Certificate Transparency is now enforced by mainstream clients; Mozilla recommends removing it). | WARNING | | `http.expect_ct` | Flags the deprecated Expect-CT header (Certificate Transparency is now enforced by mainstream clients; Mozilla recommends removing it). | WARNING |
| `http.hpkp` | Flags the deprecated Public-Key-Pins (HPKP) header, which is unsupported by modern browsers; rely on Certificate Transparency and CAA records instead. | WARNING |
| `http.hpkp_report_only` | Flags the deprecated Public-Key-Pins-Report-Only (HPKP) header, which is unsupported by modern browsers; rely on Certificate Transparency and CAA records instead. | WARNING |
| `http.server_header` | Reports whether the Server header discloses the origin server software/version; a non-informative value is accepted. | INFO | | `http.server_header` | Reports whether the Server header discloses the origin server software/version; a non-informative value is accepted. | INFO |
| `http.x_powered_by` | Reports the X-Powered-By header, which discloses the web server technology stack and should be removed. | INFO | | `http.x_powered_by` | Reports the X-Powered-By header, which discloses the web server technology stack and should be removed. | INFO |
| `http.x_aspnet_version` | Reports the X-AspNet-Version header, which discloses the ASP.NET framework version and should be removed. | INFO | | `http.x_aspnet_version` | Reports the X-AspNet-Version header, which discloses the ASP.NET framework version and should be removed. | INFO |

View file

@ -112,6 +112,38 @@ func init() {
RegisterRule(disclosureHeaderRule("http.x_powered_by", "X-Powered-By", "the technologies used by the web server")) RegisterRule(disclosureHeaderRule("http.x_powered_by", "X-Powered-By", "the technologies used by the web server"))
RegisterRule(disclosureHeaderRule("http.x_aspnet_version", "X-AspNet-Version", "the ASP.NET framework version")) RegisterRule(disclosureHeaderRule("http.x_aspnet_version", "X-AspNet-Version", "the ASP.NET framework version"))
RegisterRule(disclosureHeaderRule("http.x_aspnetmvc_version", "X-AspNetMvc-Version", "the ASP.NET MVC version")) RegisterRule(disclosureHeaderRule("http.x_aspnetmvc_version", "X-AspNetMvc-Version", "the ASP.NET MVC version"))
RegisterRule(deprecatedHPKPRule("http.hpkp", "Public-Key-Pins"))
RegisterRule(deprecatedHPKPRule("http.hpkp_report_only", "Public-Key-Pins-Report-Only"))
}
// deprecatedHPKPRule builds a rule for the HTTP Public-Key-Pins (HPKP)
// headers. Key pinning was removed from Chromium in 2018 and is
// unsupported by all modern browsers; its operational brittleness made it
// a frequent cause of self-inflicted outages. Certificate Transparency
// and CAA records provide superior compromise detection, so any presence
// is reported as Warn ".deprecated" and absence is OK ".absent".
func deprecatedHPKPRule(code, header string) sdk.CheckRule {
return HeaderRule(HeaderRuleSpec{
Code: code,
Description: "Reports the presence of the deprecated " + header + " (HPKP) header, which is unsupported by modern browsers and should be removed.",
Header: header,
Inspect: func(_ string, _ HTTPProbe, _ sdk.CheckerOptions) []HeaderResult {
return []HeaderResult{{
Status: sdk.StatusWarn,
Suffix: "deprecated",
Message: header + " (HPKP) is deprecated. Key pinning was removed from Chromium in 2018 and is unsupported by modern browsers; rely on Certificate Transparency and CAA DNS records instead.",
Meta: map[string]any{"fix": "Remove the `" + header + "` header from your responses."},
}}
},
OnMissing: func(_ HTTPProbe, _ sdk.CheckerOptions) []HeaderResult {
return []HeaderResult{{
Status: sdk.StatusOK,
Suffix: "absent",
Message: header + " is not set, which is correct (HPKP is deprecated).",
}}
},
})
} }
// Information-disclosure headers -------------------------------------- // Information-disclosure headers --------------------------------------

View file

@ -353,6 +353,34 @@ func TestDisclosureHeaderRules(t *testing.T) {
} }
} }
func TestHPKPRules(t *testing.T) {
cases := []struct {
rule string
header string
}{
{"http.hpkp", "public-key-pins"},
{"http.hpkp_report_only", "public-key-pins-report-only"},
}
for _, c := range cases {
t.Run(c.rule, func(t *testing.T) {
// Absent → OK, since HPKP is deprecated.
states := runRule(t, ruleByName(t, c.rule), &HTTPData{Probes: []HTTPProbe{httpsProbe("a:443")}}, nil)
mustStatus(t, states, sdk.StatusOK)
if !hasCode(states, c.rule+".absent") {
t.Errorf("%s: missing absent code: %+v", c.rule, states)
}
// Present → Warn deprecated.
p := httpsProbe("a:443")
p.Headers[c.header] = `pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; max-age=5184000`
states = runRule(t, ruleByName(t, c.rule), &HTTPData{Probes: []HTTPProbe{p}}, nil)
mustStatus(t, states, sdk.StatusWarn)
if !hasCode(states, c.rule+".deprecated") {
t.Errorf("%s: missing deprecated code: %+v", c.rule, states)
}
})
}
}
func TestSecurityHeaders_NoHTTPS(t *testing.T) { func TestSecurityHeaders_NoHTTPS(t *testing.T) {
// Each header rule must emit Unknown when there are no successful HTTPS probes. // Each header rule must emit Unknown when there are no successful HTTPS probes.
rules := []sdk.CheckRule{ rules := []sdk.CheckRule{
@ -366,6 +394,8 @@ func TestSecurityHeaders_NoHTTPS(t *testing.T) {
ruleByName(t, "http.x_powered_by"), ruleByName(t, "http.x_powered_by"),
ruleByName(t, "http.x_aspnet_version"), ruleByName(t, "http.x_aspnet_version"),
ruleByName(t, "http.x_aspnetmvc_version"), ruleByName(t, "http.x_aspnetmvc_version"),
ruleByName(t, "http.hpkp"),
ruleByName(t, "http.hpkp_report_only"),
} }
data := &HTTPData{Probes: []HTTPProbe{httpProbe("a:80")}} data := &HTTPData{Probes: []HTTPProbe{httpProbe("a:80")}}
for _, r := range rules { for _, r := range rules {