checker-http/checker/rules_wellknown.go

64 lines
2.2 KiB
Go

// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2026 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
package checker
import (
"context"
"fmt"
sdk "git.happydns.org/checker-sdk-go/checker"
)
func init() { RegisterRule(&securityTxtRule{}) }
// securityTxtRule reports whether /.well-known/security.txt is published
// (RFC 9116). Absence is an Info, not a Warn: many sites legitimately
// have no security disclosure pipeline, but it is now the expected place
// for researchers to look first.
type securityTxtRule struct{}
func (r *securityTxtRule) Name() string { return "http.security_txt" }
func (r *securityTxtRule) Description() string {
return "Reports whether /.well-known/security.txt (RFC 9116) is published."
}
func (r *securityTxtRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
data, errSt := loadHTTPData(ctx, obs)
if errSt != nil {
return []sdk.CheckState{*errSt}
}
wk, ok, err := LoadExtension[WellKnownData](data, ObservationKeyWellKnown)
if err != nil {
return []sdk.CheckState{{Status: sdk.StatusError, Code: "http.security_txt.decode_error", Message: err.Error()}}
}
if !ok {
return []sdk.CheckState{unknownState("http.security_txt.no_data", "Well-known collector did not run.")}
}
probe := wk.URIs["/.well-known/security.txt"]
switch {
case probe.StatusCode == 200 && probe.Bytes > 0:
return []sdk.CheckState{{
Status: sdk.StatusOK,
Code: "http.security_txt.ok",
Subject: data.Domain,
Message: fmt.Sprintf("/.well-known/security.txt is published (%d bytes).", probe.Bytes),
}}
case probe.StatusCode == 200:
return []sdk.CheckState{{
Status: sdk.StatusWarn,
Code: "http.security_txt.empty",
Subject: data.Domain,
Message: "/.well-known/security.txt responded 200 but is empty.",
}}
default:
return []sdk.CheckState{{
Status: sdk.StatusInfo,
Code: "http.security_txt.missing",
Subject: data.Domain,
Message: fmt.Sprintf("/.well-known/security.txt is not published (status %d).", probe.StatusCode),
Meta: map[string]any{"fix": "Publish /.well-known/security.txt per RFC 9116 (Contact:, Expires:, …)."},
}}
}
}