From 2c021a00804fca7b6bafa122a99ce07d7a1b1265 Mon Sep 17 00:00:00 2001 From: phillip-stephens Date: Thu, 7 May 2026 14:09:09 +1200 Subject: [PATCH 1/2] error out if we hit a censys error that we can't recover from --- censys.go | 45 +++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/censys.go b/censys.go index 1de7c76..d3ee434 100644 --- a/censys.go +++ b/censys.go @@ -20,6 +20,7 @@ import ( "io" "net" "net/http" + "strings" log "github.com/sirupsen/logrus" ) @@ -103,39 +104,31 @@ func (a *CensysAnnotator) Annotate(ip net.IP) interface{} { log.Debugf("failed to close response body: %v", err) } }(res.Body) - if res.StatusCode != http.StatusOK { - log.Debugf("failed to annotate ip %s with censys: %s", ip.String(), res.Status) + body, _ := io.ReadAll(res.Body) + if res.StatusCode >= 400 && res.StatusCode < 500 { + // From https://docs.censys.com/reference/get-started#step-6-handle-http-response-codes + // 4XX errors are not transient and so we'll abort and report to the user + log.Fatalf("censys api returned an http status '%s' with message: '%s'. "+ + "Cannot continue to annotate, please check that you have sufficient API credits and your PAT is correct", res.Status, strings.TrimSpace(string(body))) + + } else if res.StatusCode != http.StatusOK { + // Should be a transient error, log and move on + log.Debugf("censys api returned an http %s status with message: '%s'. Skipping Censys annotation for this IP: %s", res.Status, strings.TrimSpace(string(body)), ip.String()) return nil } - body, _ := io.ReadAll(res.Body) - var result any + // We have a successful response, unmarshall it. + // Struct taken from v1.1 of Censys API docs + var result struct { + Result struct { + Resource any `json:"resource"` + } `json:"result"` + } err = json.Unmarshal(body, &result) if err != nil { log.Debugf("failed to parse censys response for ip %s: %v", ip.String(), err) return nil } - // By default, Censys' result is wrapped in a result[resource[real_data]]. We'll unwrap that here - dropResultCast, ok := result.(map[string]interface{}) - if !ok { - log.Debugf("failed to unwrap censys response for ip %s: %v", ip.String(), result) - return result - } - dropResult := dropResultCast["result"] - if dropResult == nil { - log.Debugf("failed to unwrap censys response for ip %s: %v", ip.String(), result) - return result - } - dropResourceCast, ok := dropResult.(map[string]interface{}) - if !ok { - log.Debugf("failed to unwrap censys response for ip %s: %v", ip.String(), result) - return result - } - dropResource := dropResourceCast["resource"] - if dropResource == nil { - log.Debugf("failed to unwrap censys response for ip %s: %v", ip.String(), result) - return result - } - return dropResource + return result.Result.Resource } func (a *CensysAnnotator) Close() error { From 32f1e8e5ae8eec15386ca1236ed20ec4ebba457e Mon Sep 17 00:00:00 2001 From: phillip-stephens Date: Thu, 7 May 2026 14:11:46 +1200 Subject: [PATCH 2/2] document new behavior of quitting on api credit exhaustion --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index afaa62d..7d881e1 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,8 @@ They offer a free tier that allows for a limited number of queries per month, wh > [!NOTE] > The free account (as of April 2026) allows a single concurrent request and 100 requests per month. -> Once you've used all your credits, the `censys:` annotation will be empty for any following IPs until your credits refill the following month. +> Once you've used all your credits, the Censys API will return an error upon further requests until your credits reset at the beginning of the next month or you purchase additional credits. +> ZAnnotate will quit with an error if this occurs to prevent silent errors, so if you have a large dataset to annotate, be mindful of your credit usage and prioritize only annotating the IPs you have credits for. > With the free account only offering a single concurrent request, you'll want to leave `--censys-threads=1` unless you pay for a higher tier. 1. Create an account at [Censys.io](https://censys.io) and get a Personal Access Token (PAT) from Personal Settings > Personal Access Tokens.