// 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(&honeypotRule{}) } // honeypotRule reports any honeypot path that returned a non-404/410 // response, signalling a potentially exposed sensitive endpoint. type honeypotRule struct{} func (r *honeypotRule) Name() string { return "http.honeypot" } func (r *honeypotRule) Description() string { return "Reports sensitive paths (/.env, /.git/config, /actuator/env, …) that are reachable from the internet." } func (r *honeypotRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState { data, errSt := loadHTTPData(ctx, obs) if errSt != nil { return []sdk.CheckState{*errSt} } hp, ok, err := LoadExtension[HoneypotData](data, ObservationKeyHoneypot) if err != nil { return []sdk.CheckState{{Status: sdk.StatusError, Code: "http.honeypot.decode_error", Message: err.Error()}} } if !ok { return []sdk.CheckState{unknownState("http.honeypot.no_data", "Honeypot collector did not run.")} } var states []sdk.CheckState successfulProbes := 0 for path, probe := range hp.Probes { if probe.Error != "" || probe.StatusCode == 0 { continue } successfulProbes++ st := sdk.CheckState{ Subject: path, Meta: map[string]any{"url": probe.URL, "status_code": probe.StatusCode}, } switch { case probe.StatusCode == 200 || (probe.StatusCode >= 301 && probe.StatusCode <= 308): st.Status = sdk.StatusWarn st.Code = "http.honeypot.exposed" st.Message = fmt.Sprintf("%s is accessible (HTTP %d, %d bytes).", path, probe.StatusCode, probe.Bytes) if probe.Critical { st.Status = sdk.StatusCrit st.Code = "http.honeypot.critical_exposed" } case probe.StatusCode == 401 || probe.StatusCode == 403: st.Status = sdk.StatusInfo st.Code = "http.honeypot.protected" st.Message = fmt.Sprintf("%s exists but is access-controlled (HTTP %d).", path, probe.StatusCode) default: continue } states = append(states, st) } if successfulProbes == 0 { return []sdk.CheckState{unknownState("http.honeypot.no_response", "All honeypot probes failed or timed out.")} } if len(states) == 0 { return []sdk.CheckState{{ Status: sdk.StatusOK, Code: "http.honeypot.clean", Subject: data.Domain, Message: "No sensitive honeypot paths are reachable.", }} } return states }