rapid7-insightidr 1.14.8: user_authentications counts the user's own logons

The keyword search matches the account name anywhere in a line, so it brought
other accounts' logons: on a real run the only "new" event reported was
another user's failed password. Only events whose account/user field is the
user (DOMAIN\\user and user@domain normalized) are summarized now, with
keyword_matches and user_matches both reported. A first authentication is
recognized as Rapid7 flags it, a boolean new_authentication field, as well as
the word. The event limit cannot be set below 10000 (the agent passed 1000).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
This commit is contained in:
2026-10-12 07:53:57 +00:00
parent 3bfcebdf21
commit fb560cc937
3 changed files with 54 additions and 11 deletions
@@ -255,6 +255,42 @@ def _resolve_name(name):
NEW_MARKERS = ("new_authentication",)
_TRUE = ("true", "1", "yes")
def _norm_account(v):
"""jdoe, DOMAIN\\jdoe, jdoe@domain.tld -> jdoe."""
v = str(v or "").strip().lower()
if "\\" in v:
v = v.rsplit("\\", 1)[1]
if "@" in v:
v = v.split("@", 1)[0]
return v
def _is_users(fields, user):
"""The event is this user's: an account or user field holds the account.
The keyword search matches the text anywhere in the line, so it also brings
other people's logons — on a real run the only "new" event it found was
another account's. Only the account fields decide."""
want = _norm_account(user)
for k, v in fields.items():
kl = k.lower()
if ("account" in kl or "user" in kl) and "sid" not in kl and _norm_account(v) == want:
return True
return False
def _is_new(fields):
"""Rapid7 marks a first-time authentication either with a boolean field
(new_authentication: true) or with that word as a value."""
for k, v in fields.items():
if "new_authentication" in k.lower() and str(v).strip().lower() in _TRUE:
return True
if str(v).strip().lower() in NEW_MARKERS:
return True
return False
def run():
@@ -278,24 +314,29 @@ def run():
name = str(inp.get("log_set_name") or "Asset Authentication").strip()
log_set_id = _resolve_name(name)
try:
limit = int(inp.get("limit") or 2000)
limit = max(int(inp.get("limit") or 10000), 10000)
except (TypeError, ValueError):
limit = 2000
limit = 10000
query = 'where("%s")' % user
first = request("GET", "log_search/query/logsets/" + urllib.parse.quote(log_set_id, safe=""),
params={"query": query, "from": start_time, "to": end_time})
events = _identify(_collect(first, limit), "", None)
new = [e for e in events
if any(str(v).lower() in NEW_MARKERS for v in _fields(e).values())]
matched = _identify(_collect(first, limit), "", None)
events = [e for e in matched if _is_users(_fields(e), user)]
new = [e for e in events if _is_new(_fields(e))]
print(json.dumps({
"user": user,
"log_set": name,
"query": query,
"keyword_matches": len(matched),
"user_matches": len(events),
"truncated": len(matched) >= limit,
"window": "%s to %s" % (_iso(start_time), _iso(end_time)),
"all": _summarize(events, limit),
"new_authentications": _summarize(new, limit),
"note": ("new_authentications holds only the events Rapid7 marked new_authentication: their "
"destination assets are the new assets a behaviour alert counts."),
"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 "
"included. new_authentications holds the events Rapid7 marked as a first "
"authentication: their destination assets are the new assets a behaviour alert counts."),
}))