Skip to content

Commit 8a12ae0

Browse files
authored
Add Deduplicate and Rate Limit action reference and example (#27)
Documents the two families of built-in coordination actions that give workflows shared, distributed state: Deduplicate (atomic once-only claim on a key) and Rate Limit (pace a workflow against a shared budget). Both are inline actions with no Foundry app, config_id, or credential. Adds an authoring reference covering all six Deduplicate activities and all four Rate Limit activities, including the scope gotcha (the platform stores definition and cid, which the console labels "Workflow" and "CID"), the atomic claim and metadata handoff, and the save-time validation the builder runs. Adds a worked tutorial example that deduplicates third-party NG-SIEM detections into a single case, registered in the reference index and example catalog. Action IDs and version constraints were confirmed against a live tenant with action_search.py, and the example passes validate.py at all tiers including server-side API validation. The actions are enabled in all commercial CIDs (US-1, US-2, EU-1 by default; other environments by request).
1 parent cedf818 commit 8a12ae0

5 files changed

Lines changed: 346 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
1010

1111
- US-3 cloud region to credential setup: a `[us-3]` profile example (`https://api.us-3.crowdstrike.com`) in the setup skill's multi-cloud block and in the README region notes, alongside a `[us-gov-1]` example that was also missing. The auth module already accepts any `base_url`, so this documents the host rather than changing behavior.
1212
- Throttling reference in the execution skill: explains that a workflow stuck "in progress" may be throttled (Fusion paces an action past a volume limit, auto-retrying up to 6 hours) rather than failed, how to recognize it on the execution detail view, and when sustained throttling signals a workflow-design issue.
13+
- Deduplicate and Rate Limit action reference plus a worked tutorial example. Covers all six Deduplicate activities and all four Rate Limit activities: the `definition`/`cid` scope values (which the console labels "Workflow" and "CID"), the atomic claim, metadata handoff, and the save-time validation the builder runs. The example deduplicates third-party NG-SIEM detections into a single case. Action IDs were confirmed against a live tenant; the example passes validation at all tiers.
1314

1415
## [1.0.1] - 2026-08-07
1516

skills/authoring/SKILL.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ metadata:
5353
> only for actions the table does not cover.
5454
> 3. Run `trigger_search.py` to confirm the trigger type.
5555
> 4. Run `validate.py` on every YAML file before presenting it.
56-
> 5. **Re-run `validate.py` on the FINAL file; resolve every ERROR before finishing.** A file that still errors is not done. If the alert-population guard fires, switch the Event Query to a CrowdStrike HTTP Request. Never hand off a file that fails validation.
56+
> 5. **Re-run `validate.py` on the FINAL file; resolve every ERROR before finishing.** A file that still errors is not done. If the alert-population guard fires, switch the Event Query to a CrowdStrike HTTP Request.
5757
>
5858
> **MUST NOT:**
5959
> - Author a workflow for a Foundry-app-shaped request (see action 0) — redirect to foundry-skills.
@@ -253,7 +253,7 @@ force an immediate refresh so newly shipped action types are never hidden.
253253
| "The template has `PLACEHOLDER_RAN_006`, I'll copy it." | NEVER. Templates are structural guides. Substitute a real value before saving. |
254254
| "Validation can wait until deploy." | NO. Validate after authoring — `validate.py` catches PLACEHOLDERs, bad IDs, and schema errors locally. |
255255
| "This action has a `class`, version_constraint is optional." | WRONG. Class-based actions REQUIRE `version_constraint`. Missing it fails import. |
256-
| "I'll use `~1` everywhere for version_constraint." | NO. The value is `~<major>` of the action's `semantic_version` (`~0` when it declares none): `1.0.4``~1`, `0.0.100` (Charlotte AI, most Store plugins) `~0`. Read it from `--details`. |
256+
| "I'll use `~1` everywhere for version_constraint." | NO. The value is `~<major>` of the action's `semantic_version` (`~0` when it declares none): `1.0.4``~1`, `0.0.100``~0`. Read it from `--details`. |
257257
| "I'll make up a `config_id` for this Okta action." | NEVER. It's CID-specific (exists only once configured in the console). Ask the user (AskUserQuestion) — even non-interactively. Sequential/all-zeros/repeated-char UUIDs are still fabricated and fail at runtime; can't get a real one? STOP. See `references/best-practices.md`. |
258258
| "I'll set `definition_id: VIRUSTOTAL_..._ID` on this HTTP action." | NEVER. An `Inline.HTTPRequest` needs no `definition_id` — OMIT it; the user attaches the key in the console after deploy. A placeholder is a broken ref `validate.py` flags. |
259259
| "The Send email field is called Recipients, so I'll use `recipients:`." | WRONG. The property KEY is `to:` (a list); `recipients:` is rejected. Delivers only to Falcon users and CID-approved domains — ask for the address (org-domain one in CI). |
@@ -282,6 +282,7 @@ the task actually calls for them:
282282
| Run a CQL/FQL event query in a step — inputs, outputs | `references/event-query-action.md` |
283283
| Decide Event Query vs a source-of-truth API (alerts, cases, current state) | `references/event-query-vs-api.md` |
284284
| Summarize/classify with Charlotte AI LLM — compound ID, `~0`, decode output | `references/charlotte-ai-action.md` |
285+
| Deduplicate or rate-limit a workflow — scopes, keys | `references/deduplicate-ratelimit.md` |
285286
| Operational guidance, limits, gotchas before production | `references/best-practices.md` |
286287

287288
**Advanced (rarely needed):**

skills/authoring/examples/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ network containment, notifications, and more.
1414
| `notifications/` | 2 | Alert routing, Slack notifications, and human-approved endpoint containment. |
1515
| `ngsiem/` | 1 | Falcon Next-Gen SIEM duplicate-detection management. |
1616
| `response-actions/` | 6 | Palo Alto Networks NGFW integration for network-level response (DAG tags, EDLs). |
17-
| `tutorials/` | 8 | "Introduction to..." playbooks teaching specific Fusion concepts. |
17+
| `tutorials/` | 9 | "Introduction to..." playbooks teaching specific Fusion concepts. |
1818

1919
## Workflows
2020

@@ -59,6 +59,7 @@ network containment, notifications, and more.
5959
- [Introduction to the Python script action: build a lookup file from an external feed](tutorials/intro-python-sslbl-lookup.yaml) — an `Inline.Python` action fetches and reshapes the abuse.ch SSL blocklist, and its `output_stdout` feeds a lookup file
6060
- [Introduction to Receive Email trigger: How to create a lookup file from an email attachment](tutorials/intro-receive-email-trigger.yaml)
6161
- [Introduction to variables: How to append to an array](tutorials/intro-variables-append-array.yaml)
62+
- [Introduction to deduplication: How to deduplicate third-party detections](tutorials/intro-deduplicate-third-party-detections.yaml) — the **Deduplicate** action family suppresses duplicate NG-SIEM third-party (Palo Alto) detections: a sha1 key over a 24h window creates one case and comments duplicates onto it. See `references/deduplicate-ratelimit.md`. **Pre-GA** — action IDs are from a console export, not yet live-verified in our tenant
6263
- [CrowdStrike HTTP Request: query the Falcon Alerts API and email the result](tutorials/crowdstrike-http-request-falcon-api.yaml) — the canonical **CrowdStrike HTTP Request** shape (Falcon platform API, distinct from a Cloud HTTP Request): absolute region host, query params in `request_query` (not the URL), required OAuth credential (`UseExisting` + `config_id`). Built and executed live end-to-end.
6364

6465
## How to use these examples
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Source: CrowdStrike-authored tutorial workflow "Introduction to
2+
# deduplication: How to Deduplicate Third Party Detections", exported from the
3+
# Falcon console. Demonstrates the Deduplicate action family suppressing
4+
# duplicate third-party (Palo Alto) NG-SIEM detections into a single case.
5+
#
6+
# Availability: The Deduplicate and Rate Limit actions are enabled in all
7+
# commercial CIDs, and are available in US-1, US-2, and EU-1 by default (other
8+
# environments by request). The action IDs and version_constraints below were
9+
# confirmed against a live tenant with action_search.py, and this file passes
10+
# validate.py at all tiers, including server-side API validation.
11+
#
12+
# Pattern: NG-SIEM third-party detection (Palo Alto) -> Deduplicate on a sha1 key
13+
# built from detection type + source/dest IPs, 24h window -> if new, create a
14+
# case and record its ID as the entry's metadata -> if duplicate, wait for that
15+
# metadata and comment on the original case. See
16+
# references/deduplicate-ratelimit.md.
17+
# This is an exported workflow. Editing this file is not recommended.
18+
19+
name: 'Introduction to deduplication: How to Deduplicate Third Party Detections'
20+
description: Learn how to leverage the deduplication action to deduplicate third party detections from Palo Alto Networks
21+
disconnected_nodes:
22+
- '{"id":"notes_b5eee5d5-1646-4562-9e25-f933d06a9ed0","position":{"x":304.24687139282736,"y":526.301954879066},"node_type":"notes","comment":"Dedups for a period of one day"}'
23+
trigger:
24+
next:
25+
- data39trigger_detection_thirdparty_sourcevendors39_existsone__v_v__34paloalto34
26+
event: Investigatable/THIRDPARTY
27+
name: Detection > NG-SIEM Third Party Detection
28+
type: Signal
29+
version_constraint: ~1
30+
actions:
31+
AddCommentToCase:
32+
id: a16f4fdd1b244b0bfeecd47e25dbe0e0
33+
default_name: Add Comment to Case
34+
name: Add Comment to Case
35+
properties:
36+
case_id: ${data['WaitForDeduplicateEntryMetadata.metadata']}
37+
comment: 'Detection ID: ${data[''Trigger.Detection.DetectionID'']} is a duplicate.'
38+
version_constraint: ~1
39+
CreateANewCase:
40+
id: 4918bf9d85ecc06388eca16543bdbbdc
41+
default_name: Create a new Case
42+
name: Create a new Case
43+
next:
44+
- SetDeduplicateEntryMetadata
45+
properties:
46+
description: |-
47+
Name: ${data['Trigger.Detection.Name']}
48+
Description: ${data['Trigger.Detection.Description']}
49+
detections:
50+
- ${Trigger.Detection.DetectionID}
51+
name: Detection ${data['Trigger.Detection.Name']}
52+
severity_level: 3
53+
status: new
54+
version_constraint: ~1
55+
Deduplicate:
56+
id: f6f68f316170550b2777aec3dc3c85e1
57+
default_name: Deduplicate
58+
name: Deduplicate
59+
next:
60+
- duplicate_is_equal_to_false
61+
properties:
62+
key: |-
63+
${cs.hash.sha1(data['Trigger.Detection.ThirdParty.DetectionType'] +
64+
data['Trigger.Detection.ThirdParty.SourceIPs'].join(",") +
65+
data['Trigger.Detection.ThirdParty.DestinationIPs'].join(","))}
66+
period: 86400
67+
scope: definition
68+
version_constraint: ~2
69+
SetDeduplicateEntryMetadata:
70+
id: 7cd6f7bde9eef6a98d851d8270e4f1f4
71+
default_name: Set Deduplicate Entry Metadata
72+
name: Set Deduplicate Entry Metadata
73+
properties:
74+
key: ${data['Deduplicate.key']}
75+
metadata: ${data['CreateANewCase.id']}
76+
scope: definition
77+
version_constraint: ~1
78+
WaitForDeduplicateEntryMetadata:
79+
id: 7bddab2fa0d5c5c90fdb49e0f3eef380
80+
default_name: Wait for Deduplicate Entry Metadata
81+
name: Wait for Deduplicate Entry Metadata
82+
next:
83+
- AddCommentToCase
84+
properties:
85+
key: ${data['Deduplicate.key']}
86+
scope: definition
87+
version_constraint: ~1
88+
conditions:
89+
data39trigger_detection_thirdparty_sourcevendors39_existsone__v_v__34paloalto34:
90+
next:
91+
- Deduplicate
92+
cel_expression: data['Trigger.Detection.ThirdParty.SourceVendors'].existsOne(_, v, v == "Paloalto")
93+
display:
94+
- data[&#39;Trigger.Detection.ThirdParty.SourceVendors&#39;].existsOne(_, v, v == &#34;Paloalto&#34;)
95+
name: If Vendor is Palo Alto
96+
duplicate_is_equal_to_false:
97+
next:
98+
- CreateANewCase
99+
expression: Deduplicate.duplicate:false
100+
display:
101+
- Duplicate is equal to False
102+
else:
103+
- WaitForDeduplicateEntryMetadata

0 commit comments

Comments
 (0)