feat(crowdstrike): fetch incrementally via a created_from watermark (v1.3.0)

search_detections had one input for everything, `filter`, so an alert rule
configured for incremental fetch had nowhere to put its watermark but
there — where it REPLACED the FQL with a bare timestamp instead of
narrowing it. The API rejects that outright (HTTP 400, "invalid query
filter in request"), and even had it not, the product/type scoping would
have been silently discarded with it. The only working configuration left
was a full fetch on every run.

created_from is that missing input, following the shape get_alerts already
uses on sentinelone: a timestamp of its own, declared as the command's
incremental_field so the alert-rule editor offers it. The script folds it
into the FQL as created_timestamp, which is where this API expresses a time
bound. filter now carries the scoping only — the two no longer compete.

Surrounding quotes on the value are stripped: FQL wants them, and an
operator filling the first-fetch date by hand may reasonably include them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-01 22:47:46 +02:00
parent 21b957be72
commit 643290b733
2 changed files with 15 additions and 4 deletions
@@ -34,6 +34,15 @@ def csv(v):
def main():
tok = token()
fql = INPUTS.get("filter") or "product:'epp'+type:'ldt'"
# The alert rule's incremental watermark lands in created_from, NOT in filter.
# This API carries its time bound inside the FQL, so pointing the watermark at
# filter would REPLACE the whole query with a bare timestamp rather than narrow
# it — which the API rejects outright, and which would also silently discard the
# product/type scoping above. Its own input lets the two coexist: filter says
# what to look for, created_from says since when.
since = str(INPUTS.get("created_from") or "").strip().strip("'\"")
if since:
fql += "+created_timestamp:>'" + since + "'"
limit = INPUTS.get("limit", 50)
params = [("filter", fql), ("limit", limit), ("sort", "created_timestamp.desc")]
found = call("GET", "/alerts/queries/alerts/v2", tok, params=params)