{"record":{"id":"4d217bcbc606c365","repo":"crowdsecurity/crowdsec","slug":"ip-not-found","errorCode":null,"errorMessage":"ip not found","messagePattern":"ip not found","errorType":"http","errorClass":null,"httpStatus":404,"severity":"info","filePath":"pkg/cticlient/client.go","lineNumber":25,"sourceCode":"\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/crowdsecurity/crowdsec/pkg/apiclient/useragent\"\n\tlog \"github.com/sirupsen/logrus\"\n)\n\nconst (\n\tCTIBaseUrl    = \"https://cti.api.crowdsec.net/v2\"\n\tsmokeEndpoint = \"/smoke\"\n\tfireEndpoint  = \"/fire\"\n)\n\nvar (\n\tErrUnauthorized  = errors.New(\"unauthorized\")\n\tErrLimit         = errors.New(\"request quota exceeded, please reduce your request rate\")\n\tErrNotFound      = errors.New(\"ip not found\")\n\tErrDisabled      = errors.New(\"cti is disabled\")\n\tErrUnknown       = errors.New(\"unknown error\")\n\tdefaultUserAgent = useragent.Default()\n)\n\ntype CrowdsecCTIClient struct {\n\thttpClient *http.Client\n\tapiKey     string\n\tLogger     *log.Entry\n\tUserAgent  string\n}\n\nfunc (c *CrowdsecCTIClient) doRequest(ctx context.Context, method string, endpoint string, params map[string]string) ([]byte, error) {\n\turl := CTIBaseUrl + endpoint\n\tif len(params) > 0 {\n\t\turl += \"?\"\n\t\tfor k, v := range params {\n\t\t\turl += fmt.Sprintf(\"%s=%s&\", k, v)","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/cticlient/client.go#L7-L43","documentation":"ErrNotFound is returned when the CTI API responds HTTP 404 for the requested IP. GetIPInfo catches it and converts it into a benign empty &SmokeItem{}, so callers usually never see it; it exists to distinguish 'unknown IP' from real errors.","triggerScenarios":"GetIPInfo called with an IP the CTI database has no record of (smokeEndpoint/<ip> returns 404).","commonSituations":"Looking up private/reserved addresses, fresh IPs never observed by CrowdSec, or IPv6 ranges outside CTI coverage.","solutions":["No fix needed: GetIPInfo already returns an empty SmokeItem for this case","If calling doRequest directly, handle errors.Is(err, ErrNotFound) as an empty result rather than a failure","Validate/skip private and reserved IPs before calling CTI"],"exampleFix":"// before\nif err != nil { return err }\n// after\nif errors.Is(err, cticlient.ErrNotFound) {\n    return &cticlient.SmokeItem{}, nil\n}","handlingStrategy":"fallback","validationCode":"// skip private/reserved IPs before querying CTI\nif ip := net.ParseIP(s); ip == nil || ip.IsPrivate() || ip.IsLoopback() { return nil }","typeGuard":"func isPublicIP(s string) bool { ip := net.ParseIP(s); return ip != nil && !ip.IsPrivate() && !ip.IsLoopback() }","tryCatchPattern":"resp, err := ctiClient.GetIPInfo(ip)\nif errors.Is(err, cticlient.ErrNotFound) {\n    resp = &cticlient.SmokeItem{} // empty result, not an error\n}","preventionTips":["Treat 404 as 'no data', never as a failure that breaks enrichment","Pre-filter private, loopback and link-local addresses","Assume any public IP may legitimately be absent from the CTI DB"],"tags":["api","http","cti","not-found"],"backgroundTag":"resource-not-found","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}