rapid7-insightidr 1.14.9: a user_authentications answer that fits
Riposte gives a tool result 8000 characters; two full summaries ran to 11903 and were cut at 3903, the asset list half gone. The answer now keeps only the fields an authentication question is answered from (destination and source asset and address, domain, result, logon type, service) and lists up to 40 new destination assets in full: about 2.5 KB for 1000 events. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -268,6 +268,39 @@ def _norm_account(v):
|
|||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
# The fields an authentication question is answered from, matched by name so a
|
||||||
|
# source that prefixes or nests them still lands here. Everything else — event
|
||||||
|
# ids, timestamps, SIDs, the vendor payload — is left out: Riposte gives one
|
||||||
|
# tool result 8000 characters, and the first version of this answer, two full
|
||||||
|
# summaries, was cut at 3903 of 11903 with the asset list half gone.
|
||||||
|
_KEY_FIELDS = ("destination_asset", "source_asset", "destination_domain", "result", "logon_type", "service")
|
||||||
|
|
||||||
|
|
||||||
|
def _compact(events, top_assets):
|
||||||
|
"""Counts for the key fields only; destination assets listed in full."""
|
||||||
|
counts = {}
|
||||||
|
first_ts, last_ts = None, None
|
||||||
|
for e in events:
|
||||||
|
ts = e.get("timestamp")
|
||||||
|
if isinstance(ts, (int, float)):
|
||||||
|
first_ts = ts if first_ts is None or ts < first_ts else first_ts
|
||||||
|
last_ts = ts if last_ts is None or ts > last_ts else last_ts
|
||||||
|
for k, v in _fields(e).items():
|
||||||
|
kl = k.lower()
|
||||||
|
if any(f in kl for f in _KEY_FIELDS) and "sid" not in kl:
|
||||||
|
counts.setdefault(k, {})
|
||||||
|
key = str(v)[:120]
|
||||||
|
counts[k][key] = counts[k].get(key, 0) + 1
|
||||||
|
out = {"count": len(events),
|
||||||
|
"first_event": _iso(first_ts) if first_ts is not None else None,
|
||||||
|
"last_event": _iso(last_ts) if last_ts is not None else None}
|
||||||
|
for k, vals in sorted(counts.items()):
|
||||||
|
ranked = sorted(vals.items(), key=lambda x: -x[1])
|
||||||
|
n = top_assets if "destination_asset" in k.lower() and "address" not in k.lower() else 5
|
||||||
|
out[k] = {"distinct": len(vals), "top": [[v, c] for v, c in ranked[:n]]}
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _is_users(fields, user):
|
def _is_users(fields, user):
|
||||||
"""The event is this user's: an account or user field holds the account.
|
"""The event is this user's: an account or user field holds the account.
|
||||||
|
|
||||||
@@ -331,8 +364,8 @@ def run():
|
|||||||
"user_matches": len(events),
|
"user_matches": len(events),
|
||||||
"truncated": len(matched) >= limit,
|
"truncated": len(matched) >= limit,
|
||||||
"window": "%s to %s" % (_iso(start_time), _iso(end_time)),
|
"window": "%s to %s" % (_iso(start_time), _iso(end_time)),
|
||||||
"all": _summarize(events, limit),
|
"all": _compact(events, 10),
|
||||||
"new_authentications": _summarize(new, limit),
|
"new_authentications": _compact(new, 40),
|
||||||
"note": ("all and new_authentications hold only this user's events (an account field equals "
|
"note": ("all and new_authentications hold only this user's events (an account field equals "
|
||||||
"the user): keyword_matches counts every line the text appeared in, other accounts "
|
"the user): keyword_matches counts every line the text appeared in, other accounts "
|
||||||
"included. new_authentications holds the events Rapid7 marked as a first "
|
"included. new_authentications holds the events Rapid7 marked as a first "
|
||||||
|
|||||||
Reference in New Issue
Block a user