{"record":{"id":"3e65bf0daaf1f646","repo":"crowdsecurity/crowdsec","slug":"source-is-nil-or-not-an-ip","errorCode":null,"errorMessage":"source is nil or not an IP","messagePattern":"source is nil or not an IP","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/appsec/challenge_alert.go","lineNumber":92,"sourceCode":"\tkeys := make([]string, 0, len(m))\n\tfor k := range m {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\n\tmeta := make(models.Meta, 0, len(keys))\n\tfor _, k := range keys {\n\t\tmeta = append(meta, &models.MetaItems0{Key: k, Value: m[k]})\n\t}\n\n\treturn meta\n}\n\n// GeoIPEnrichSource fills a models.Source (IP scope) with GeoIP data — ASN,\n// country, coordinates, range — from the shared exprhelpers GeoIP databases.\nfunc GeoIPEnrichSource(src *models.Source) error {\n\tif src == nil || src.Scope == nil || *src.Scope != types.Ip {\n\t\treturn errors.New(\"source is nil or not an IP\")\n\t}\n\n\tasndata, err := exprhelpers.GeoIPASNEnrich(src.IP)\n\tif err != nil {\n\t\treturn err\n\t} else if asndata != nil {\n\t\trecord := asndata.(*geoip2.ASN)\n\t\tsrc.AsName = record.AutonomousSystemOrganization\n\t\tsrc.AsNumber = fmt.Sprintf(\"%d\", record.AutonomousSystemNumber)\n\t}\n\n\tcityData, err := exprhelpers.GeoIPEnrich(src.IP)\n\tif err != nil {\n\t\treturn err\n\t} else if cityData != nil {\n\t\trecord := cityData.(*geoip2.City)\n\t\tsrc.Cn = record.Country.IsoCode\n\t\tsrc.Latitude = float32(record.Location.Latitude)","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/appsec/challenge_alert.go#L74-L110","documentation":"GeoIPEnrichSource populates a models.Source with ASN/country/coordinates GeoIP data, but only makes sense for IP-scoped sources. It refuses to run when the source pointer is nil or the source's Scope is not types.Ip. This is a defensive guard against enriching non-IP entities (e.g. range or user scopes).","triggerScenarios":"Calling GeoIPEnrichSource(src) with src == nil; or with a *models.Source whose Scope is nil; or whose *Scope is anything other than types.Ip (e.g. 'range'). Reached via AppsecEventGeneration or buildChallengeAlert when the appsec event carries a malformed/absent source.","commonSituations":"Custom appsec code paths building a models.Source by hand and forgetting to set Scope to Ip; a parser/evaluator emitting a challenge alert for an event that never had an IP source; refactors that changed the Source struct and left Scope unset.","solutions":["Set src.Scope to a pointer to types.Ip before calling GeoIPEnrichSource","Ensure the models.Source is fully constructed (non-nil) before enrichment","Guard the call site: skip GeoIP enrichment when the source scope is not IP"],"exampleFix":"// before\nsrc := &models.Source{IP: \"1.2.3.4\"}\nerr := GeoIPEnrichSource(src)\n// after\nipScope := types.Ip\nsrc := &models.Source{IP: \"1.2.3.4\", Scope: &ipScope}\nerr := GeoIPEnrichSource(src)","handlingStrategy":"validation","validationCode":"if src == nil || src.Scope == nil || *src.Scope != types.Ip {\n    // skip enrichment or construct a proper IP-scoped source\n    return nil\n}\nif err := GeoIPEnrichSource(src); err != nil { ... }","typeGuard":"func isIPSource(src *models.Source) bool {\n    return src != nil && src.Scope != nil && *src.Scope == types.Ip\n}","tryCatchPattern":"if err := GeoIPEnrichSource(src); err != nil {\n    log.Warnf(\"geoip enrichment skipped: %s\", err)\n}","preventionTips":["Always set Scope to types.Ip when building models.Source from an IP address","Use a constructor helper for models.Source so Scope is never forgotten","Only call enrichment on alert sources known to be IP-scoped"],"tags":["go","geoip","appsec","nil-pointer"],"backgroundTag":"null-argument","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}