fix(rapid7-insightidr): do not send a bare UUID to /alerts/ops/rrns (v1.4.1)
The run died with HTTP 400 and eight "The provided RRN: [...] is invalid", every one a plain UUID. Two mistakes compounded. The alert list under an investigation is not consistent about where the RRN sits. On the ABA alert this work was built against, `id` held the RRN. On others it holds a bare UUID. The code read `id` first and `rrn` only as a fallback, so it picked the UUID whenever both were present. Worse, the failure was total rather than partial. /alerts/ops/rrns validates the whole list and rejects the entire request when one entry is malformed, so a single bad identifier costs every alert in that batch — and with one batch, the whole run. An investigation feed that stops dead because one alert is shaped differently is not an acceptable failure mode. So: read `rrn` first, and drop anything that is not RRN-shaped before the batch is sent, counting it as alert_ids_without_rrn in the run metadata. The investigation still arrives, its retrievable alerts still arrive, and what could not be fetched is a number an operator can see rather than an absence nobody would notice. Both commands that walk this list are fixed; the selector is shared and was checked against the two real shapes plus the empty and null cases. This turns a total failure into a partial one. It does NOT establish that those eight alerts are reachable at all: if they carry no RRN anywhere, they are not in the Alert Triage system and no field_ids or batch call will produce them. That needs the raw shape of one, which the next run of list_investigation_alerts on the failing investigation will show. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,27 @@ DEFAULT_BUDGET_S = 60
|
||||
# plain investigation feed. Only the command changes.
|
||||
|
||||
|
||||
def _alert_rrn(a):
|
||||
"""The alert's RRN, or None when this alert has none.
|
||||
|
||||
The investigation alert list is not consistent about where the RRN sits.
|
||||
Some entries carry it in `id` (rrn:alerts:eu:<org>:alert:1:<hex>); others put
|
||||
a plain UUID there and the RRN, if any, in `rrn`. `rrn` is therefore read
|
||||
first, and anything that is not RRN-shaped is dropped rather than sent on.
|
||||
|
||||
That last part is not defensive habit. /alerts/ops/rrns validates the whole
|
||||
list and rejects the entire request when one entry is malformed, so a single
|
||||
UUID slipping through costs every alert in the batch — which is exactly how
|
||||
this failed: HTTP 400, eight invalid RRNs, zero alerts fetched, the whole run
|
||||
lost. Dropping them here means the investigation still arrives, with its
|
||||
unretrievable alerts counted rather than silently missing.
|
||||
"""
|
||||
for v in (a.get("rrn"), a.get("id")):
|
||||
if isinstance(v, str) and v.startswith("rrn:"):
|
||||
return v
|
||||
return None
|
||||
|
||||
|
||||
def run():
|
||||
_, _, multi = _cfg()
|
||||
inp = _inputs()
|
||||
@@ -92,7 +113,7 @@ def run():
|
||||
# guards. An investigation whose alerts cannot be read keeps its place in the
|
||||
# output with an alerts_error: losing the whole run over one of them, or
|
||||
# dropping it silently, would both be worse.
|
||||
rrns_by_inv, order, truncated = {}, [], False
|
||||
rrns_by_inv, order, truncated, skipped = {}, [], False, 0
|
||||
for inv in investigations:
|
||||
inv_rrn = inv.get("rrn") or ""
|
||||
if not inv_rrn:
|
||||
@@ -108,11 +129,13 @@ def run():
|
||||
continue
|
||||
got = []
|
||||
for a in (page.get("data") or []):
|
||||
rrn = a.get("id") or a.get("rrn")
|
||||
if rrn:
|
||||
got.append(rrn)
|
||||
if rrn not in order:
|
||||
order.append(rrn)
|
||||
rrn = _alert_rrn(a)
|
||||
if rrn is None:
|
||||
skipped += 1
|
||||
continue
|
||||
got.append(rrn)
|
||||
if rrn not in order:
|
||||
order.append(rrn)
|
||||
rrns_by_inv[inv_rrn] = got
|
||||
|
||||
# The alerts themselves, batched, then indexed so each investigation can be
|
||||
@@ -168,6 +191,7 @@ def run():
|
||||
"investigations_returned": len(investigations),
|
||||
"alert_rrns_collected": len(order),
|
||||
"alerts_fetched": fetched,
|
||||
"alert_ids_without_rrn": skipped,
|
||||
"truncated": truncated,
|
||||
"elapsed_seconds": round(time.monotonic() - started, 1),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user