rapid7-insightidr 1.14.3: the log window is computed from the alert's time
query_log_set takes anchor_time with hours_before / minutes_after and computes the window itself, and returns it as text: on a real run the agent computed "24 hours before 2026-10-11T00:04Z" as ending on the 10th and reported a day-old window. Static passes the alert's first seen time instead of dates, and lists domain controllers apart from the new assets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
This commit is contained in:
@@ -52,11 +52,11 @@ instructions: |
|
||||
where("jdoe"). If it returns 0 events, try once more
|
||||
case-insensitively: where(/jdoe/i). Always parentheses; no groupby or
|
||||
calculate.
|
||||
- Window. A behaviour (UBA) alert aggregates activity over hours — its
|
||||
count of "new assets" keeps growing after it is raised — so take the 24
|
||||
hours before its first seen time, up to 30 minutes after its last seen
|
||||
time. Other alerts: 30 minutes on each side. First and last seen can be
|
||||
inverted in the data: take the earlier as the start.
|
||||
- Window: never compute dates yourself. Pass anchor_time = the alert's
|
||||
first seen time exactly as the data gives it, and the window is
|
||||
computed for you. A behaviour (UBA) alert aggregates hours of activity:
|
||||
hours_before 24, minutes_after 30 (the defaults). Other alerts:
|
||||
hours_before 0.5. Report the "window" the answer returns, not your own.
|
||||
- "Invalid Query Syntax": report the exact query and error, no variants.
|
||||
- Keep at most 6 calls for logs.
|
||||
5. ALWAYS call search_investigation(actor_user_name=<user>) once, whatever
|
||||
@@ -64,8 +64,10 @@ instructions: |
|
||||
start_time / end_time, not time_range; empty, it covers 28 days.
|
||||
|
||||
In your answer, list the hosts and IPs the logs show for the user — that is
|
||||
what an analyst needs from a "new assets" alert — and say which log set and
|
||||
query produced them.
|
||||
what an analyst needs from a "new assets" alert — and say which log set,
|
||||
query and window produced them. Domain controllers (names with "dc") are
|
||||
where every logon is checked: list them apart, they are not the "new
|
||||
assets" the alert is about.
|
||||
|
||||
commands:
|
||||
- list_investigation_alerts
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -66,8 +66,8 @@ def _annotate(a):
|
||||
a["next"] = ("Behaviour (UBA) alert with no RRN: get_alert, get_alert_actors, get_alert_evidence and "
|
||||
"get_alert_process_trees answer 404 for it — do not call them. Its evidence is in the "
|
||||
"authentication logs: query_log_set with log_set_name \"Asset Authentication\" and "
|
||||
"where(\"<user>\"), over the 24 hours before the alert's first seen time — a behaviour "
|
||||
"alert aggregates hours of activity.")
|
||||
"where(\"<user>\") and anchor_time = this alert's first seen time: the 24 hours before it "
|
||||
"are queried — a behaviour alert aggregates hours of activity.")
|
||||
|
||||
|
||||
try:
|
||||
|
||||
@@ -55,6 +55,18 @@ def _to_ms(v):
|
||||
return None
|
||||
|
||||
|
||||
def _num(v, default):
|
||||
try:
|
||||
return float(v) if v not in (None, "") else default
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
def _iso(ms):
|
||||
"""The window as it was queried, in the form the agent reports it."""
|
||||
return datetime.datetime.fromtimestamp(ms / 1000, tz=datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
|
||||
def request(method, url, params=None):
|
||||
base, headers = _cfg()
|
||||
full = url if url.startswith("http") else base + url.lstrip("/")
|
||||
@@ -156,6 +168,16 @@ def run():
|
||||
end_time = _to_ms(inp.get("end_time"))
|
||||
if inp.get("time_range"):
|
||||
start_time, end_time = _rel_ms_range(inp.get("time_range"))
|
||||
# An agent copies a timestamp right and gets date arithmetic wrong: on a real
|
||||
# run "24 hours before 2026-10-11T00:04Z" came out ending on the 10th, and
|
||||
# the events it found were a day too early. Given the alert's own time, the
|
||||
# window is computed here.
|
||||
anchor = _to_ms(inp.get("anchor_time"))
|
||||
if anchor is not None:
|
||||
before = _num(inp.get("hours_before"), 24.0)
|
||||
after = _num(inp.get("minutes_after"), 30.0)
|
||||
start_time = int(anchor - before * 3600 * 1000)
|
||||
end_time = int(anchor + after * 60 * 1000)
|
||||
# Log Search wants both bounds. A scheduled run hands over start_time alone
|
||||
# (the watermark), and the very first run may hand over nothing at all.
|
||||
now_ms = int(time.time() * 1000)
|
||||
@@ -188,7 +210,8 @@ def run():
|
||||
first = request("GET", "log_search/query/logsets/" + urllib.parse.quote(log_set_id, safe=""), params=params)
|
||||
# No fallback log id: a log set spans several logs and every event names its own.
|
||||
events = _identify(_collect(first, limit), "", inp.get("detection_name"))
|
||||
print(json.dumps({"events": events, "count": len(events), "from": start_time, "to": end_time}))
|
||||
print(json.dumps({"events": events, "count": len(events), "from": start_time, "to": end_time,
|
||||
"window": "%s to %s" % (_iso(start_time), _iso(end_time))}))
|
||||
|
||||
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user