feat(crowdstrike): set a detection's verdict (1.5.0)
Falcon records a verdict as a tag — true_positive, false_positive or ignored, which the console's Resolution column reads — and update_status only takes new, in_progress, reopened and closed, so resolve_detection had no way to record one. Its new verdict input adds the tag after removing the other two, add_tag never replacing one; status and verdict are drop-downs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WhDQSYZd1mFtRsXK4ixtyB
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
id: crowdstrike
|
id: crowdstrike
|
||||||
name: CrowdStrike Falcon
|
name: CrowdStrike Falcon
|
||||||
version: 1.4.0
|
version: 1.5.0
|
||||||
description: "CrowdStrike Falcon (OAuth2 API) — full IR coverage: device/IOC/process enrichment, detections & cases, host groups, Real Time Response, ML/IOA exclusions, quarantine, Spotlight/CVE, ODS scans, CSPM, users, IOA rules, CNAPP, and Fusion workflows."
|
description: "CrowdStrike Falcon (OAuth2 API) — full IR coverage: device/IOC/process enrichment, detections & cases, host groups, Real Time Response, ML/IOA exclusions, quarantine, Spotlight/CVE, ODS scans, CSPM, users, IOA rules, CNAPP, and Fusion workflows."
|
||||||
changelog: "1.4.0 — New device_count command: counts devices matching filters without listing them. The device query already answers with meta.pagination.total — the number of matches, independent of the page asked for — so device_count asks for a one-ID page and reads it, where search-device resolves every matching device in full through /devices/entities/devices/v2 to reach the same figure, and only filters on four properties. Filters cover sixteen device FQL properties (hostname, device id, local and external IP, platform, OS version, role, containment status, sensor version, AD domain/OU/site, serial, tags, host groups, RFM), add first_seen/last_seen bounds, and leave a raw FQL `filter` escape hatch for the rest. Several values on one property become an OR group, parenthesised so it binds before the AND that joins it to the others — the reference client omits those parentheses and leaves the precedence to the server. Values are quoted with the single quote escaped, so a hostname carrying an apostrophe cannot end the literal early and change what the query counts. The estate `total` comes back next to `count` so the figure reads as a proportion, and a filter too broad to mean anything shows as matching everything rather than passing for an answer. | 1.3.0 — search_detections takes a created_from timestamp, folded into the FQL as created_timestamp, and declares it as the ingestion watermark input. An alert rule can now fetch incrementally instead of re-reading the whole window every run: this API expresses its time bound inside the FQL, so the watermark used to have nowhere to go but the filter input, where it replaced the query rather than narrowing it. filter now carries the scoping only. 1.2.4 — Fixed two silent defects in the search_detections OCSF mapper: severity was a ternary that resolved to nothing, so every alert kept the alert rule's default severity instead of Falcon's — it is now a plain path to severity_name, whose words the engine reads natively; and no entry fed the OCSF `time` field, so detected_at was never stamped and MTTD measured the collection delay as zero. 1.2.3 — Documentation wording cleanup. 1.2.2 — Corrected the search_detections OCSF mapper against the Falcon Raptor alert schema: domain is device.hostinfo.domain, dropped non-existent fields, added scenario/alleged_filetype/cid. 1.2.1 — Marked search_detections as an ingestion source (ingest hints: results_path=resources, dedup_key=composite_id) so the alert-rule editor pre-fills it. 1.2.0 — Added a pre-built OCSF mapper for search_detections (Falcon unified alerts → OCSF: device, finding, MITRE, actor/parent process, file and IOC). 1.1.1 — Added test_connection for the instance Test button. 1.1.0 — Expanded to 81 commands (host groups, cases, RTR files/scripts/responders, ML/IOA exclusions, quarantine, Spotlight host-by-vuln/CVE, ODS scans, CSPM, users, IOA rules, CNAPP, identity/mobile detection resolve, and workflows). 1.0.0 — Initial release: device/detection enrichment, Spotlight, IOC management, contain/lift, and core RTR."
|
changelog: "1.5.0 — resolve_detection sets the verdict. Falcon has no verdict field on an alert: the console's Resolution column is read from the tags true_positive, false_positive and ignored, and the status only takes new, in_progress, reopened and closed — so no value of the old status box could record a verdict. The new verdict input adds the tag, after removing the other two so an alert never carries two resolutions, and status and verdict are now drop-downs. 1.4.0 — New device_count command: counts devices matching filters without listing them. The device query already answers with meta.pagination.total — the number of matches, independent of the page asked for — so device_count asks for a one-ID page and reads it, where search-device resolves every matching device in full through /devices/entities/devices/v2 to reach the same figure, and only filters on four properties. Filters cover sixteen device FQL properties (hostname, device id, local and external IP, platform, OS version, role, containment status, sensor version, AD domain/OU/site, serial, tags, host groups, RFM), add first_seen/last_seen bounds, and leave a raw FQL `filter` escape hatch for the rest. Several values on one property become an OR group, parenthesised so it binds before the AND that joins it to the others — the reference client omits those parentheses and leaves the precedence to the server. Values are quoted with the single quote escaped, so a hostname carrying an apostrophe cannot end the literal early and change what the query counts. The estate `total` comes back next to `count` so the figure reads as a proportion, and a filter too broad to mean anything shows as matching everything rather than passing for an answer. | 1.3.0 — search_detections takes a created_from timestamp, folded into the FQL as created_timestamp, and declares it as the ingestion watermark input. An alert rule can now fetch incrementally instead of re-reading the whole window every run: this API expresses its time bound inside the FQL, so the watermark used to have nowhere to go but the filter input, where it replaced the query rather than narrowing it. filter now carries the scoping only. 1.2.4 — Fixed two silent defects in the search_detections OCSF mapper: severity was a ternary that resolved to nothing, so every alert kept the alert rule's default severity instead of Falcon's — it is now a plain path to severity_name, whose words the engine reads natively; and no entry fed the OCSF `time` field, so detected_at was never stamped and MTTD measured the collection delay as zero. 1.2.3 — Documentation wording cleanup. 1.2.2 — Corrected the search_detections OCSF mapper against the Falcon Raptor alert schema: domain is device.hostinfo.domain, dropped non-existent fields, added scenario/alleged_filetype/cid. 1.2.1 — Marked search_detections as an ingestion source (ingest hints: results_path=resources, dedup_key=composite_id) so the alert-rule editor pre-fills it. 1.2.0 — Added a pre-built OCSF mapper for search_detections (Falcon unified alerts → OCSF: device, finding, MITRE, actor/parent process, file and IOC). 1.1.1 — Added test_connection for the instance Test button. 1.1.0 — Expanded to 81 commands (host groups, cases, RTR files/scripts/responders, ML/IOA exclusions, quarantine, Spotlight host-by-vuln/CVE, ODS scans, CSPM, users, IOA rules, CNAPP, identity/mobile detection resolve, and workflows). 1.0.0 — Initial release: device/detection enrichment, Spotlight, IOC management, contain/lift, and core RTR."
|
||||||
category: endpoint
|
category: endpoint
|
||||||
|
|
||||||
# Per-instance configuration. Scripts obtain an OAuth2 bearer token from
|
# Per-instance configuration. Scripts obtain an OAuth2 bearer token from
|
||||||
@@ -171,12 +171,19 @@ commands:
|
|||||||
|
|
||||||
- id: resolve_detection
|
- id: resolve_detection
|
||||||
name: crowdstrike-resolve-detection
|
name: crowdstrike-resolve-detection
|
||||||
description: "Update detections/alerts: change status, append a comment, and/or assign to an analyst."
|
description: "Update detections/alerts: change status, set the verdict, append a comment, and/or assign to an analyst."
|
||||||
risk: destructive
|
risk: destructive
|
||||||
inputs_schema:
|
inputs_schema:
|
||||||
properties:
|
properties:
|
||||||
ids: { type: string, description: "Comma-separated alert composite IDs." }
|
ids: { type: string, description: "Comma-separated alert composite IDs." }
|
||||||
status: { type: string, description: "New status: new, in_progress, closed, or reopened." }
|
status:
|
||||||
|
type: string
|
||||||
|
description: "New status. closed ends the triage; set verdict alongside it so the console shows a resolution."
|
||||||
|
enum: [new, in_progress, reopened, closed]
|
||||||
|
verdict:
|
||||||
|
type: string
|
||||||
|
description: "Resolution shown in the console's Resolution column. Falcon carries it as a tag on the alert, and tags add up: setting a verdict removes the other two first."
|
||||||
|
enum: [true_positive, false_positive, ignored]
|
||||||
comment: { type: string, description: "Optional comment to append." }
|
comment: { type: string, description: "Optional comment to append." }
|
||||||
assign_to_uuid: { type: string, description: "Optional analyst UUID to assign the detection to." }
|
assign_to_uuid: { type: string, description: "Optional analyst UUID to assign the detection to." }
|
||||||
required: [ids]
|
required: [ids]
|
||||||
|
|||||||
@@ -31,16 +31,38 @@ def csv(v):
|
|||||||
return [x.strip() for x in str(v or "").split(",") if x.strip()]
|
return [x.strip() for x in str(v or "").split(",") if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
VERDICTS = ("true_positive", "false_positive", "ignored")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
tok = token()
|
tok = token()
|
||||||
|
ids = csv(INPUTS.get("ids"))
|
||||||
|
verdict = str(INPUTS.get("verdict") or "").strip()
|
||||||
|
if verdict and verdict not in VERDICTS:
|
||||||
|
raise ValueError("verdict must be one of " + ", ".join(VERDICTS) + ", got " + verdict)
|
||||||
action_params = []
|
action_params = []
|
||||||
|
if verdict:
|
||||||
|
# The console's Resolution is read from these tags, and add_tag never
|
||||||
|
# replaces one: drop the other two first, or a false positive turned
|
||||||
|
# true positive would show both. Removing a tag the alert does not
|
||||||
|
# carry is not an error worth stopping for — the add below is the call
|
||||||
|
# that has to land.
|
||||||
|
for other in VERDICTS:
|
||||||
|
if other == verdict:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
call("PATCH", "/alerts/entities/alerts/v3", tok,
|
||||||
|
body={"action_parameters": [{"name": "remove_tag", "value": other}], "composite_ids": ids})
|
||||||
|
except urllib.error.HTTPError:
|
||||||
|
pass
|
||||||
|
action_params.append({"name": "add_tag", "value": verdict})
|
||||||
if INPUTS.get("status"):
|
if INPUTS.get("status"):
|
||||||
action_params.append({"name": "update_status", "value": str(INPUTS["status"])})
|
action_params.append({"name": "update_status", "value": str(INPUTS["status"])})
|
||||||
if INPUTS.get("comment"):
|
if INPUTS.get("comment"):
|
||||||
action_params.append({"name": "append_comment", "value": str(INPUTS["comment"])})
|
action_params.append({"name": "append_comment", "value": str(INPUTS["comment"])})
|
||||||
if INPUTS.get("assign_to_uuid"):
|
if INPUTS.get("assign_to_uuid"):
|
||||||
action_params.append({"name": "assign_to_uuid", "value": str(INPUTS["assign_to_uuid"])})
|
action_params.append({"name": "assign_to_uuid", "value": str(INPUTS["assign_to_uuid"])})
|
||||||
body = {"action_parameters": action_params, "composite_ids": csv(INPUTS.get("ids"))}
|
body = {"action_parameters": action_params, "composite_ids": ids}
|
||||||
result = call("PATCH", "/alerts/entities/alerts/v3", tok, body=body)
|
result = call("PATCH", "/alerts/entities/alerts/v3", tok, body=body)
|
||||||
print(json.dumps(result))
|
print(json.dumps(result))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user