Detect CSP weaknesses individually (unsafe-inline, unsafe-eval, missing default-src/script-src, permissive sources on script-src or its default-src fallback) instead of a single catch-all "unsafe" code, and honour CSP3 fetch-directive fallback via EffectiveSources/WildcardSource helpers. Validate Permissions-Policy values: warn when a powerful feature (camera, microphone, geolocation, payment, sensors, …) is granted to all origins. Add a SameSite aggregate state on cookie audits so callers get the global ratio alongside per-cookie diagnostics.
82 lines
2.6 KiB
Go
82 lines
2.6 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"
|
|
"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
|
|
}
|