// 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" "strings" sdk "git.happydns.org/checker-sdk-go/checker" ) func init() { RegisterRule(&cookieFlagsRule{}) } // cookieFlagsRule audits Set-Cookie attributes on HTTPS responses: every // cookie should be Secure and HttpOnly, and SameSite should be set. type cookieFlagsRule struct{} func (r *cookieFlagsRule) Name() string { return "http.cookie_flags" } func (r *cookieFlagsRule) Description() string { return "Verifies that cookies set over HTTPS use the Secure, HttpOnly and SameSite attributes." } func (r *cookieFlagsRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState { data, errSt := loadHTTPData(ctx, obs) if errSt != nil { return []sdk.CheckState{*errSt} } probes := successfulHTTPSProbes(data.Probes) if len(probes) == 0 { return []sdk.CheckState{unknownState("http.cookie_flags.no_https", "No successful HTTPS probe to evaluate.")} } var states []sdk.CheckState totalCookies := 0 samesiteMissing := 0 for _, p := range probes { for _, c := range p.Cookies { totalCookies++ var issues []string if !c.Secure { issues = append(issues, "missing Secure") } if !c.HttpOnly { issues = append(issues, "missing HttpOnly") } if c.SameSite == "" { issues = append(issues, "missing SameSite") samesiteMissing++ } else if strings.EqualFold(c.SameSite, "None") && !c.Secure { issues = append(issues, "SameSite=None requires Secure") } if len(issues) > 0 { states = append(states, sdk.CheckState{ Status: sdk.StatusWarn, Code: "http.cookie_flags.weak", Subject: fmt.Sprintf("%s :: %s", p.Address, c.Name), Message: fmt.Sprintf("Cookie %q on %s: %s", c.Name, p.Address, strings.Join(issues, ", ")), }) } } } if totalCookies == 0 { return []sdk.CheckState{passState("http.cookie_flags.none", "No cookies were set on the inspected responses.")} } if samesiteMissing > 0 { // Aggregate alongside per-cookie diagnostics so callers see the // global ratio at a glance — mirrors what Mozilla Observatory // reports as a single cookies test outcome. states = append(states, sdk.CheckState{ Status: sdk.StatusWarn, Code: "http.cookie_flags.samesite_missing", Message: fmt.Sprintf("%d of %d cookies do not set SameSite.", samesiteMissing, totalCookies), }) } if len(states) == 0 { return []sdk.CheckState{passState("http.cookie_flags.ok", fmt.Sprintf("All %d cookies have proper Secure/HttpOnly/SameSite flags.", totalCookies))} } return states }