From 74fcce5ae7d9c047d6cb7e879f08ddfd7674a30d Mon Sep 17 00:00:00 2001 From: Michael Cretzman Date: Fri, 3 Oct 2025 08:53:13 -0700 Subject: [PATCH 1/8] draft --- config/_default/menus/main.en.yaml | 9 +- .../workload_protection/secl_auth_guide.md | 138 ++++++++++++++++++ .../workload_security_rules/custom_rules.md | 2 +- 3 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 content/en/security/workload_protection/secl_auth_guide.md diff --git a/config/_default/menus/main.en.yaml b/config/_default/menus/main.en.yaml index b63e379a26435..4a73513c86339 100644 --- a/config/_default/menus/main.en.yaml +++ b/config/_default/menus/main.en.yaml @@ -6400,16 +6400,21 @@ menu: parent: workload_protection identifier: workload_protection_agent_expressions weight: 204 + - name: Getting Started with SECL Custom Rules + url: security/workload_protection/secl_auth_guide + parent: workload_protection_agent_expressions + identifier: workload_protection_secl_guide + weight: 1 - name: Linux Syntax url: security/workload_protection/linux_expressions parent: workload_protection_agent_expressions identifier: linux_expressions - weight: 1 + weight: 2 - name: Windows Syntax url: security/workload_protection/windows_expressions parent: workload_protection_agent_expressions identifier: windows_expressions - weight: 2 + weight: 3 - name: Coverage and Posture Management url: security/workload_protection/inventory parent: workload_protection diff --git a/content/en/security/workload_protection/secl_auth_guide.md b/content/en/security/workload_protection/secl_auth_guide.md new file mode 100644 index 0000000000000..565ee10c471c6 --- /dev/null +++ b/content/en/security/workload_protection/secl_auth_guide.md @@ -0,0 +1,138 @@ +--- +title: Getting Started with SECL Custom Rules +disable_toc: false +--- + +This guide shows you how to write effective SECL (Security Language) rules for Datadog Workload Protection. The guide includes the following: + +- How agent rules and detection rules fit together +- Practical workflow for authoring rules +- Checklist of best practices +- Set of drop-in example expressions for Linux, Windows, and cross-platform use cases +- Recommendations for tagging and noise reduction +- Safe rollout plan so your team can ship low-noise, high-signal detections with confidence + +## SECL overview + +Datadog SECL is a custom domain-specific language used to create agent expressions and policies within Datadog Workload Protection. SECL allows security teams to define real-time threat detection rules by specifying conditions, operators, and patterns that security agents can monitor across hosts, containers, applications, and cloud infrastructure. + +## How SECL rules fit together + +Think of SECL as a local filter: it runs inside the Agent on each host, watching kernel and OS events. When an event matches your SECL expression, the Agent raises a detection. + +Datadog threat detection rules act as backend logic: they combine one or more Agent rules (using `@agent.rule_id`), add thresholds, suppress noise, and decide how alerts are routed. + +In summary, the Agent rule finds raw behavior and the detection rule turns it into a usable signal. + +
This guide describes how to create rule expressions manually, but Workload Protection also provides the Assisted rule creator wizard to walk you through creating the Agent and detections rules together. See Create the custom Agent and detection rules together.
+ +### Agent expression syntax + +The standard format of a SECL expression is: + +{{< code-block lang="javascript" >}} +. [ .] ... +{{< /code-block >}} + +Using this format, an example rule for a Linux system looks like this: + +{{< code-block lang="javascript" >}} + +open.file.path == "/etc/shadow" && process.file.path not in ["/usr/sbin/vipw"] + +{{< /code-block >}} + +## Quickstart + +Here's a summary of the process: + +1. Go to Workload Protection [Policies][1]. +2. Click **New Policy** to create a policy or open an existing policy. +3. In the policy, click **Add Agent Rule**, and then click **Manual rule creator**. +4. In **Define the agent expression**, enter your expression using the following steps. +5. Select **Linux** or **Windows**. Most expression fields are OS-specific. +6. Choose a trigger (event type). Choose a trigger that aligns with the behavior you want to catch, but not everything that could happen. + - Examples: `exec`, `open`, `connect`, `create`, `dns`, etc. +7. Anchor the trigger on one or two stable fields. Example: + - Linux: `exec.file.path`, `process.ancestors.file.name`. + - Windows: `file.path`, `exec.args`, `open_key.registry.key_path`. +8. Add context to the trigger. Examples: + - Parent ancestry: `process.ancestors.*` + - User info: `process.user`, `process.uid` + - Containers: `container.*` + - Time: `process.created_at > 5s` +9. Insert safe exceptions. Examples: `not in [...]`, `!~`, `allowlists`. +10. Name and save the rule. +11. Test the rule: + - To test Agent events, open the rule and click **View Events**. The [Agent Events explorer][2] opens and displays the Agent events that match the expression. + - To test detection rules, open Signals: Check if the rule fires too often. +12. Ship it: + - Tag it for a targeted environment + - Reference in a detection rule + - Tune signal volume with suppressions + + +### Tips + +High-Signal Attributes + +Process lineage: process.ancestors.file.name → Use this to separate benign system actions from suspicious spawns. + +File path: open.file.path → Match sensitive files; combine with not in [...] to allow normal tools. + +Network: network.destination.ip in CIDR → Use allowlists of corp ranges to suppress noise. + +Container scope: container.id, container.image → Restrict detections to workloads that matter. + +Timing: process.created_at > 5s → Useful for catching odd behavior like “read secrets right after start.” + +Operators & Matchers + +Exact match (==) → Fastest, lowest noise. Always prefer. + +List membership (in [...]) → Best for allowlists or controlled sets of values. + +Glob match (~"/path/*") → Use for path families; safer and faster than regex. + +Regex (=~) → Only when globs/lists can’t solve it. Keep as narrow as possible. + +Negation (not in [...], !~) → Use to carve out exceptions explicitly (e.g., trusted tools). + +CIDR operators (in CIDR, not in CIDR) → Use for network boundaries. + +👉 Rule of thumb: Start with == or in [...]. Reach for regex only as a last resort. + +## Rule authoring checklist and style guide + + + + +## Common building blocks + + +## Avoid common mistakes + + + +## Examples + + +### Linux examples + + +### Windows examples + + +### Cross-platform example + + + + +## Rollout strategy + + +## Recommended tagging + + +[1]: https://app.datadoghq.com/security/workload-protection/policies +[2]: https://app.datadoghq.com/security/workload-protection/agent-events \ No newline at end of file diff --git a/content/en/security/workload_protection/workload_security_rules/custom_rules.md b/content/en/security/workload_protection/workload_security_rules/custom_rules.md index 88878e60cc3e5..8988b02919ea8 100644 --- a/content/en/security/workload_protection/workload_security_rules/custom_rules.md +++ b/content/en/security/workload_protection/workload_security_rules/custom_rules.md @@ -89,7 +89,7 @@ To use the Assisted rule creator: 1. Go to [Agent Configuration][3]. 2. Create or open a policy. -3. In **Actions**, select **Assisted rule creator**. +3. In **Add Agent Rule**, select **Assisted rule creator**. 4. Define the detection. To monitor your resource effectively, you have the following detection type options: - To detect nonstandard and suspicious changes to files, select **File integrity monitoring (FIM)**. - To track and analyze system software processes for malicious behavior or policy violations, select **Process activity monitoring**. From f90d181771c2011b185ef1810bb55b844c5fa20b Mon Sep 17 00:00:00 2001 From: Michael Cretzman Date: Tue, 7 Oct 2025 14:19:51 -0700 Subject: [PATCH 2/8] early draft and notes --- .../workload_protection/secl_auth_guide.md | 75 +++++++++++++------ 1 file changed, 53 insertions(+), 22 deletions(-) diff --git a/content/en/security/workload_protection/secl_auth_guide.md b/content/en/security/workload_protection/secl_auth_guide.md index 565ee10c471c6..ac0e2bc6d400c 100644 --- a/content/en/security/workload_protection/secl_auth_guide.md +++ b/content/en/security/workload_protection/secl_auth_guide.md @@ -63,30 +63,46 @@ Here's a summary of the process: - Time: `process.created_at > 5s` 9. Insert safe exceptions. Examples: `not in [...]`, `!~`, `allowlists`. 10. Name and save the rule. -11. Test the rule: - - To test Agent events, open the rule and click **View Events**. The [Agent Events explorer][2] opens and displays the Agent events that match the expression. - - To test detection rules, open Signals: Check if the rule fires too often. -12. Ship it: - - Tag it for a targeted environment - - Reference in a detection rule - - Tune signal volume with suppressions +### Test the rule -### Tips +To view Agent events that match the expression, view the rule details, and then click **View Events**. The [Agent Events explorer][2] opens and displays the Agent events that match the expression. In **Agent Events**, you can see the raw telemetry: every kernel/OS event that matched the SECL rule before suppression or aggregation. -High-Signal Attributes +When viewing raw Agent events, do the following: +1. Confirm the rule is firing on the intended activity. +2. Measure the scope and frequency across hosts/environments. +3. Decide if this is the expected baseline or a true threat? +4. Act: Suppress/allowlist if benign, escalate/contain if suspicious. + +To test detection rules, view the rule details, and then click **[Number] detection rules found**. The Detection Rules explorer opens and displays the backend logic layer: the detection rule, linked signals, metadata, JSON definition, tags, and version history. -Process lineage: process.ancestors.file.name → Use this to separate benign system actions from suspicious spawns. +When viewing the backend logic for a detection rule, do the following: -File path: open.file.path → Match sensitive files; combine with not in [...] to allow normal tools. +1. Confirm that the rule name, description, and JSON (net_util_in_container) clarify what the rule is supposed to catch. +2. Measure whether the rule is noisy or precise by reviewing the aggregate metrics (query histogram, signals over time, affected hosts).. +3. Decide if the detected activity is expected (baseline) or suspicious (threat). +4. Act: Suppress/allowlist if benign, escalate/contain if suspicious. -Network: network.destination.ip in CIDR → Use allowlists of corp ranges to suppress noise. +## Rule authoring tips -Container scope: container.id, container.image → Restrict detections to workloads that matter. +- Always set os: filter in the rule YAML when using OS-specific fields: + + {{< code-block lang="yaml" disable_copy="true" collapsible="true" >}} + id: my_linux_rule + expression: exec.file.path == "/usr/bin/passwd" + filters: + - os == "linux" + {{< /code-block >}} +- Prefer exact matches over regex. Use glob-style `~"/path/*"` or `**` for subfolders. +- Use `in [...]` lists instead of long `OR` chains. It makes expressions faster and easier to read. +- Anchor on ancestry to reduce noise. Use `process.ancestors.file.name`. +- Treat regex as last resort. Use globs or `in` where possible. +- Use durations (for example, `> 5s`, `10m`, `2h`) to target narrow execution windows. +- Name rules by behavior, with a format that follows *What + Who + Context*. +- Tag generously: `team`, `app`, `env`, `MITRE`, `severity`. -Timing: process.created_at > 5s → Useful for catching odd behavior like “read secrets right after start.” -Operators & Matchers +## Operators and matchers Exact match (==) → Fastest, lowest noise. Always prefer. @@ -102,21 +118,36 @@ CIDR operators (in CIDR, not in CIDR) → Use for network boundaries. 👉 Rule of thumb: Start with == or in [...]. Reach for regex only as a last resort. -## Rule authoring checklist and style guide - - - +## Avoid common mistakes -## Common building blocks +| Pattern | Explanation | +| ------------------------- | -------------------------------------------- | +| `proc.args contains "rm"` | Too broad. Matches a lot of valid use cases. | +| `fd.name contains "/"` | Matches nearly every file I/O event. | +| `container.id != ""` | Useful only if scoped with a more specific field. | -## Avoid common mistakes +## Rule authoring framework +1. Define the attack vector: + - **Process Execution:** Unusual binaries, suspicious args. + - **File Access:** Unauthorized file reads/writes. + - **Network Activity:** Suspicious connections. + - **System Calls:** Abnormal kernel activity. +2. Use platform-specific fields that provide meaningful context: + - Linux context: + {{< code-block lang="secl" disable_copy="true" collapsible="true" >}} + # High-value fields + process.name and process.args and process.user and process.pid + file.path and file.name and file.permissions + network.destination.ip and network.destination.port + {{< /code-block >}} + - Windows context: +3. ## Examples - ### Linux examples From 3a8179a12ec6f26953ba2e3a2a6567e895c3ee31 Mon Sep 17 00:00:00 2001 From: Michael Cretzman Date: Thu, 9 Oct 2025 11:25:56 -0700 Subject: [PATCH 3/8] title change --- config/_default/menus/main.en.yaml | 2 +- .../workload_protection/secl_auth_guide.md | 173 ++++++++++++------ 2 files changed, 118 insertions(+), 57 deletions(-) diff --git a/config/_default/menus/main.en.yaml b/config/_default/menus/main.en.yaml index 4a73513c86339..8e0f0f7d2e722 100644 --- a/config/_default/menus/main.en.yaml +++ b/config/_default/menus/main.en.yaml @@ -6400,7 +6400,7 @@ menu: parent: workload_protection identifier: workload_protection_agent_expressions weight: 204 - - name: Getting Started with SECL Custom Rules + - name: Writing Custom Rule Expressions url: security/workload_protection/secl_auth_guide parent: workload_protection_agent_expressions identifier: workload_protection_secl_guide diff --git a/content/en/security/workload_protection/secl_auth_guide.md b/content/en/security/workload_protection/secl_auth_guide.md index ac0e2bc6d400c..a64b244e20c26 100644 --- a/content/en/security/workload_protection/secl_auth_guide.md +++ b/content/en/security/workload_protection/secl_auth_guide.md @@ -1,16 +1,9 @@ --- -title: Getting Started with SECL Custom Rules +title: Writing custom rule expressions disable_toc: false --- -This guide shows you how to write effective SECL (Security Language) rules for Datadog Workload Protection. The guide includes the following: - -- How agent rules and detection rules fit together -- Practical workflow for authoring rules -- Checklist of best practices -- Set of drop-in example expressions for Linux, Windows, and cross-platform use cases -- Recommendations for tagging and noise reduction -- Safe rollout plan so your team can ship low-noise, high-signal detections with confidence +This guide shows you how to write effective SECL (Security Language) rules for Datadog Workload Protection. ## SECL overview @@ -85,84 +78,152 @@ When viewing the backend logic for a detection rule, do the following: ## Rule authoring tips -- Always set os: filter in the rule YAML when using OS-specific fields: - - {{< code-block lang="yaml" disable_copy="true" collapsible="true" >}} - id: my_linux_rule - expression: exec.file.path == "/usr/bin/passwd" - filters: - - os == "linux" - {{< /code-block >}} -- Prefer exact matches over regex. Use glob-style `~"/path/*"` or `**` for subfolders. -- Use `in [...]` lists instead of long `OR` chains. It makes expressions faster and easier to read. +- Always set the operating system (OS). Filter in the rule YAML when using OS-specific fields: + + {{< code-block lang="yaml" disable_copy="true" collapsible="true" >}} + id: my_linux_rule + expression: exec.file.path == "/usr/bin/passwd" + filters: + - os == "linux" + {{< /code-block >}} - Anchor on ancestry to reduce noise. Use `process.ancestors.file.name`. -- Treat regex as last resort. Use globs or `in` where possible. - Use durations (for example, `> 5s`, `10m`, `2h`) to target narrow execution windows. +- Use exact match (`==`) whenever possible as it results in lowest noise. +- List membership (`in [...]`) is best for allowlists or controlled sets of values. +- Use a glob match (`~"/path/*"`) for path families as it is safer and faster than regex. +- Use regex (`=~`) only when globs/lists can't be used. Keep the regex expression as narrow as possible. As a rule of thumb, start with `==` or `in [...]`. Reach for regex only as a last resort. +- Use negation (`not in [...]`, `!~`) to carve out exceptions explicitly (for example, trusted tools). +- use CIDR operators (`in CIDR`, `not in CIDR`) for network boundaries. - Name rules by behavior, with a format that follows *What + Who + Context*. - Tag generously: `team`, `app`, `env`, `MITRE`, `severity`. -## Operators and matchers +### Avoid common mistakes -Exact match (==) → Fastest, lowest noise. Always prefer. +| Pattern | Explanation | +| ------------------------- | -------------------------------------------- | +| `proc.args contains "rm"` | Too broad. Matches a lot of valid use cases. | +| `fd.name contains "/"` | Matches nearly every file I/O event. | +| `container.id != ""` | Useful only if scoped with a more specific field. | -List membership (in [...]) → Best for allowlists or controlled sets of values. +## Common building blocks -Glob match (~"/path/*") → Use for path families; safer and faster than regex. +### Triggers -Regex (=~) → Only when globs/lists can’t solve it. Keep as narrow as possible. +- Linux: `exec`, `open`, `dns`, `connect`, `chmod`, `unlink`, `load_module`. +- Windows: `exec`, `create`, `write`, `rename`, `set_key_value`, `create_key`. + +### High-signal fields -Negation (not in [...], !~) → Use to carve out exceptions explicitly (e.g., trusted tools). +| Category | Field Examples | +| -------------- | --------------------------------------------------------- | +| **Process** | `exec.file.path`, `process.ancestors.*`, `process.user` | +| **File** | `open.file.path`, `*.rights/mode`, package metadata | +| **Network** | `network.destination.ip`, `dns.question.name`, `in CIDR` | +| **Containers** | `container.id`, `container.created_at`, `Kubernetes tags` | +| **Time** | `process.created_at > 5s` | -CIDR operators (in CIDR, not in CIDR) → Use for network boundaries. -👉 Rule of thumb: Start with == or in [...]. Reach for regex only as a last resort. +### Operators and matchers -## Avoid common mistakes +| Type | Syntax | +| --------------------- | ----------------------------- | +| Equality / Comparison | `==`, `!=`, `>`, `<=` | +| List membership | `in [...]`, `not in [...]` | +| Logical | `&&`, \` (backtick) | +| Pattern matching | `=~`, `!~`, glob `~"/path/*"` | +| CIDR | `in CIDR`, `not in CIDR` | -| Pattern | Explanation | -| ------------------------- | -------------------------------------------- | -| `proc.args contains "rm"` | Too broad. Matches a lot of valid use cases. | -| `fd.name contains "/"` | Matches nearly every file I/O event. | -| `container.id != ""` | Useful only if scoped with a more specific field. | +## Example library -## Rule authoring framework - -1. Define the attack vector: - - **Process Execution:** Unusual binaries, suspicious args. - - **File Access:** Unauthorized file reads/writes. - - **Network Activity:** Suspicious connections. - - **System Calls:** Abnormal kernel activity. -2. Use platform-specific fields that provide meaningful context: - - Linux context: - {{< code-block lang="secl" disable_copy="true" collapsible="true" >}} - # High-value fields - process.name and process.args and process.user and process.pid - file.path and file.name and file.permissions - network.destination.ip and network.destination.port - {{< /code-block >}} +### Linux - - Windows context: -3. +#### Access to sensitive files (allowlist safe tools) -## Examples +{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +open.file.path in ["/etc/shadow", "/etc/sudoers"] && +process.file.path not in ["/usr/sbin/vipw", "/usr/sbin/visudo"] +{{< /code-block >}} -### Linux examples +#### Nginx or PHP spawning bash +{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +exec.file.path == "/usr/bin/bash" && +( + process.ancestors.file.name == "nginx" || + process.ancestors.file.name =~ "php*" +) +{{< /code-block >}} -### Windows examples +#### Suspicious IMDS access from container +{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +connect && +network.destination.ip in ["169.254.169.254"] && +container.id != "" +{{< /code-block >}} -### Cross-platform example +#### Kernel module loads outside maintenance window +{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +load_module && +process.user != "root" && +process.ancestors.file.name not in ["modprobe", "insmod"] +{{< /code-block >}} + +#### Sensitive file read shortly after start + +{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +open.file.path == "/etc/secret" && +process.file.name == "java" && +process.created_at > 5s +{{< /code-block >}} + +#### Outbound to non-corporate IPs (CIDR allowlist) + +{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +connect && +network.destination.ip not in [10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12] +{{< /code-block >}} + +### Windows +#### Registry persistence via run key +{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +set_key_value && +open_key.registry.key_path =~ "*\\Software\\Microsoft\\Windows\\CurrentVersion\\Run*" +{{< /code-block >}} + +#### Unsigned Binary Launching PowerShell + +{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +exec.file.path =~ "*\\WindowsPowerShell\\v1.0\\powershell.exe" && +process.parent.file.path !~ "*\\Program Files*" && +process.user_sid != "S-1-5-18" +{{< /code-block >}} + +### Cross-platform + +#### Crypto-miner indicators + +{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +exec.args_flags in ["cpu-priority", "donate-level", ~"randomx-1gb-pages"] || +exec.args in [~"*stratum+tcp*", ~"*nicehash*", ~"*yespower*"] +{{< /code-block >}} ## Rollout strategy +| Phase | Action | +| ------------ | ------------------------------------------------------------ | +| **Draft** | Author rule with YAML metadata and proper filters. | +| **Simulate** | Use Agent in read-only/simulation mode. | +| **Validate** | Run in staging workloads. Validate behavior with real events. | +| **Tune** | Add suppressions, `not in`, `container.image`, etc. | +| **Activate** | Ship to a subset of workloads (tagged via policy). | +| **Scale** | Reference in backend detection rules for full rollout. | -## Recommended tagging [1]: https://app.datadoghq.com/security/workload-protection/policies From 55b564fe2b619529ece9b1e521193a75ec20f5db Mon Sep 17 00:00:00 2001 From: Michael Cretzman Date: Fri, 10 Oct 2025 09:29:08 -0700 Subject: [PATCH 4/8] improving guide content --- .../security/workload_protection/secl_auth_guide.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/content/en/security/workload_protection/secl_auth_guide.md b/content/en/security/workload_protection/secl_auth_guide.md index a64b244e20c26..636fcc7dfe6ba 100644 --- a/content/en/security/workload_protection/secl_auth_guide.md +++ b/content/en/security/workload_protection/secl_auth_guide.md @@ -64,8 +64,9 @@ To view Agent events that match the expression, view the rule details, and then When viewing raw Agent events, do the following: 1. Confirm the rule is firing on the intended activity. 2. Measure the scope and frequency across hosts/environments. -3. Decide if this is the expected baseline or a true threat? -4. Act: Suppress/allowlist if benign, escalate/contain if suspicious. +3. Decide if this is the expected baseline or a true threat. +4. If events are benign, add a suppression or update your expression with allowlist conditions. +5. If events are suspicious, escalate by passing the finding into your incident process, or take action to stop or isolate the suspicious workload. To test detection rules, view the rule details, and then click **[Number] detection rules found**. The Detection Rules explorer opens and displays the backend logic layer: the detection rule, linked signals, metadata, JSON definition, tags, and version history. @@ -74,7 +75,7 @@ When viewing the backend logic for a detection rule, do the following: 1. Confirm that the rule name, description, and JSON (net_util_in_container) clarify what the rule is supposed to catch. 2. Measure whether the rule is noisy or precise by reviewing the aggregate metrics (query histogram, signals over time, affected hosts).. 3. Decide if the detected activity is expected (baseline) or suspicious (threat). -4. Act: Suppress/allowlist if benign, escalate/contain if suspicious. +4. Suppress/allowlist if benign, escalate/contain if suspicious. ## Rule authoring tips @@ -93,7 +94,7 @@ When viewing the backend logic for a detection rule, do the following: - Use a glob match (`~"/path/*"`) for path families as it is safer and faster than regex. - Use regex (`=~`) only when globs/lists can't be used. Keep the regex expression as narrow as possible. As a rule of thumb, start with `==` or `in [...]`. Reach for regex only as a last resort. - Use negation (`not in [...]`, `!~`) to carve out exceptions explicitly (for example, trusted tools). -- use CIDR operators (`in CIDR`, `not in CIDR`) for network boundaries. +- Use CIDR operators (`in CIDR`, `not in CIDR`) for network boundaries. - Name rules by behavior, with a format that follows *What + Who + Context*. - Tag generously: `team`, `app`, `env`, `MITRE`, `severity`. @@ -130,7 +131,7 @@ When viewing the backend logic for a detection rule, do the following: | --------------------- | ----------------------------- | | Equality / Comparison | `==`, `!=`, `>`, `<=` | | List membership | `in [...]`, `not in [...]` | -| Logical | `&&`, \` (backtick) | +| Logical | `&&`, `\|\|`, `!` | | Pattern matching | `=~`, `!~`, glob `~"/path/*"` | | CIDR | `in CIDR`, `not in CIDR` | @@ -224,7 +225,7 @@ exec.args in [~"*stratum+tcp*", ~"*nicehash*", ~"*yespower*"] | **Activate** | Ship to a subset of workloads (tagged via policy). | | **Scale** | Reference in backend detection rules for full rollout. | - +After validatation and deployment, continue monitoring rule performance to keep false positives low and coverage strong as workloads evolve. [1]: https://app.datadoghq.com/security/workload-protection/policies [2]: https://app.datadoghq.com/security/workload-protection/agent-events \ No newline at end of file From 66d2071ef5e3ba0eb044010edb93b45bf880cf80 Mon Sep 17 00:00:00 2001 From: Michael Cretzman Date: Fri, 10 Oct 2025 12:47:59 -0700 Subject: [PATCH 5/8] linking - linking to guide from WP Guides - removing old guide as its content is basically covered in new guide --- .../workload_protection/guide/_index.md | 2 +- .../guide/custom-rules-guidelines.md | 51 ------------------- .../workload_protection/secl_auth_guide.md | 3 ++ 3 files changed, 4 insertions(+), 52 deletions(-) delete mode 100644 content/en/security/workload_protection/guide/custom-rules-guidelines.md diff --git a/content/en/security/workload_protection/guide/_index.md b/content/en/security/workload_protection/guide/_index.md index f1fe61ef649cd..c84b1d2cd7563 100644 --- a/content/en/security/workload_protection/guide/_index.md +++ b/content/en/security/workload_protection/guide/_index.md @@ -7,6 +7,6 @@ disable_toc: true {{< whatsnext desc="Use these end-to-end guides to accomplish specific goals:" >}} {{< nextlink href="/security/workload_protection/guide/active-protection" >}}Proactively block crypto mining threats with Active Protection{{< /nextlink >}} {{< nextlink href="/security/workload_protection/guide/tuning-rules" >}}Best Practices for Fine-Tuning Workload Protection Security Signals{{< /nextlink >}} - {{< nextlink href="/security/workload_protection/guide/custom-rules-guidelines" >}}Guidelines for Writing Custom Rules{{< /nextlink >}} + {{< nextlink href="/security/workload_protection/secl_auth_guide" >}}Writing custom rule expressions{{< /nextlink >}} {{< nextlink href="/security/workload_protection/guide/ebpf-free-agent" >}}Threat Detection for Linux Without eBPF Support{{< /nextlink >}} {{< /whatsnext >}} diff --git a/content/en/security/workload_protection/guide/custom-rules-guidelines.md b/content/en/security/workload_protection/guide/custom-rules-guidelines.md deleted file mode 100644 index 009ef8c5c4af5..0000000000000 --- a/content/en/security/workload_protection/guide/custom-rules-guidelines.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: Guidelines for Writing Custom Workload Protection Rules -aliases: - - /security/cloud_security_management/guide/custom-rules-guidelines -further_reading: -- link: "/security/workload_protection/workload_security_rules" - tag: "Documentation" - text: "Managing Workload Protection Rules" -- link: "/security/workload_protection/agent_expressions" - tag: "Documentation" - text: "Agent Expression Syntax" ---- - -At some point, you may want to write your own [custom Workload Protection Agent rules][1]. When writing your own rules, there are a few strategies you can use to optimize for efficiency. - -## Attributes - -To ensure that your policy is evaluated in-kernel for maximum efficiency, always use one of the following attributes for rules on process or file activity: - -- `Agent Version >= 7.27` -- `process.file.name` -- `process.file.path` -- `[event_type].file.name` -- `[event_type].file.path` - -**Note**: Possible values for `[event_type]` include `open` or `exec`. - -## Wildcards - -Use wildcards (`*`) carefully. For example, never use `open.file.path =~ "*/myfile"`. If you must use wildcards prefixing directories, at least two levels are required: `open.file.path =~ "*/mydir/myfile")`. - -**Note**: You must append a tilde (`~`) to the [operator][2] when using wildcards. - -## Approvers and discarders - -Workload Protection uses the concept of approvers and discarders to filter out events that should not trigger any rules in a policy. Approvers and discarders allow or deny events at the policy level only. They do not act on individual rules. - -Approvers act as an allow-list at the kernel level in the Datadog Agent. For example, the opening of a specific file could be an approver on the event `open`, whereas `open` events on files without approvers would be filtered out. Similarly, discarders act as a deny-list in the Agent. Discarders intentionally filter out events that can never match a rule. The Agent learns which events to filter out with discarders during runtime. - -Approvers and discarders are generated based on your entire policy. Due to this, if a single rule does not make use of approvers for a given event (for example, `open` or `exec`), approvers cannot be used for that event for the entire policy, making every rule that uses that event less efficient. - -For example, if you used explicit filenames to evaluate `open` events for every rule but one (`open.file.path == "/etc/shadow"`, `open.file.path == "/etc/secret"` and so on.), and used a wildcard in that one event (`open.file.path == "/etc/*"`), the `open` event would not generate an approver, but may generate discarders during runtime. - -Approvers are generally more powerful and preferred. Using approvers, the Agent can process only what it needs to see rather than dynamically learning what to filter out. - -## Further Reading - -{{< partial name="whats-next/whats-next.html" >}} - -[1]: /security/workload_protection/workload_security_rules -[2]: /security/workload_protection/agent_expressions/#operators \ No newline at end of file diff --git a/content/en/security/workload_protection/secl_auth_guide.md b/content/en/security/workload_protection/secl_auth_guide.md index 636fcc7dfe6ba..432604867b55e 100644 --- a/content/en/security/workload_protection/secl_auth_guide.md +++ b/content/en/security/workload_protection/secl_auth_guide.md @@ -1,6 +1,9 @@ --- title: Writing custom rule expressions disable_toc: false +aliases: + - /security/cloud_security_management/guide/custom-rules-guidelines + - /security/workload_protection/guide/custom-rules-guidelines --- This guide shows you how to write effective SECL (Security Language) rules for Datadog Workload Protection. From cc7b9c7b6ec8c2501d0a0987721dee5f485dec21 Mon Sep 17 00:00:00 2001 From: Michael Cretzman Date: Tue, 14 Oct 2025 15:00:06 -0700 Subject: [PATCH 6/8] incorp tech review --- .../workload_protection/secl_auth_guide.md | 46 +++---------------- 1 file changed, 7 insertions(+), 39 deletions(-) diff --git a/content/en/security/workload_protection/secl_auth_guide.md b/content/en/security/workload_protection/secl_auth_guide.md index 432604867b55e..56bd586a74f0e 100644 --- a/content/en/security/workload_protection/secl_auth_guide.md +++ b/content/en/security/workload_protection/secl_auth_guide.md @@ -57,7 +57,7 @@ Here's a summary of the process: - User info: `process.user`, `process.uid` - Containers: `container.*` - Time: `process.created_at > 5s` -9. Insert safe exceptions. Examples: `not in [...]`, `!~`, `allowlists`. +9. Insert safe exceptions. Examples: `not in [...]`, `!~`. In SECL, you can implement "allowlists" (the conditions that are included or excluded from detection) using ordinary operators like `not in`, `!~`, or conditional exclusions. 10. Name and save the rule. ### Test the rule @@ -82,14 +82,7 @@ When viewing the backend logic for a detection rule, do the following: ## Rule authoring tips -- Always set the operating system (OS). Filter in the rule YAML when using OS-specific fields: - - {{< code-block lang="yaml" disable_copy="true" collapsible="true" >}} - id: my_linux_rule - expression: exec.file.path == "/usr/bin/passwd" - filters: - - os == "linux" - {{< /code-block >}} +- Always set the operating system (OS). - Anchor on ancestry to reduce noise. Use `process.ancestors.file.name`. - Use durations (for example, `> 5s`, `10m`, `2h`) to target narrow execution windows. - Use exact match (`==`) whenever possible as it results in lowest noise. @@ -106,38 +99,13 @@ When viewing the backend logic for a detection rule, do the following: | Pattern | Explanation | | ------------------------- | -------------------------------------------- | -| `proc.args contains "rm"` | Too broad. Matches a lot of valid use cases. | +| `open.file.path == "/etc/passwd"`, `exec.comm != ""` | Too broad. Matches a lot of valid use cases. | | `fd.name contains "/"` | Matches nearly every file I/O event. | | `container.id != ""` | Useful only if scoped with a more specific field. | ## Common building blocks -### Triggers - -- Linux: `exec`, `open`, `dns`, `connect`, `chmod`, `unlink`, `load_module`. -- Windows: `exec`, `create`, `write`, `rename`, `set_key_value`, `create_key`. - -### High-signal fields - -| Category | Field Examples | -| -------------- | --------------------------------------------------------- | -| **Process** | `exec.file.path`, `process.ancestors.*`, `process.user` | -| **File** | `open.file.path`, `*.rights/mode`, package metadata | -| **Network** | `network.destination.ip`, `dns.question.name`, `in CIDR` | -| **Containers** | `container.id`, `container.created_at`, `Kubernetes tags` | -| **Time** | `process.created_at > 5s` | - - -### Operators and matchers - -| Type | Syntax | -| --------------------- | ----------------------------- | -| Equality / Comparison | `==`, `!=`, `>`, `<=` | -| List membership | `in [...]`, `not in [...]` | -| Logical | `&&`, `\|\|`, `!` | -| Pattern matching | `=~`, `!~`, glob `~"/path/*"` | -| CIDR | `in CIDR`, `not in CIDR` | - +For information and examples of common building blocks like operators, patterns, regular expressions, duration, and platform-specific syntax, see [Creating Agent Rule Expressions][3]. ## Example library @@ -222,13 +190,13 @@ exec.args in [~"*stratum+tcp*", ~"*nicehash*", ~"*yespower*"] | Phase | Action | | ------------ | ------------------------------------------------------------ | | **Draft** | Author rule with YAML metadata and proper filters. | -| **Simulate** | Use Agent in read-only/simulation mode. | +| **Simulate** | Use Agent in read-only mode. | | **Validate** | Run in staging workloads. Validate behavior with real events. | | **Tune** | Add suppressions, `not in`, `container.image`, etc. | -| **Activate** | Ship to a subset of workloads (tagged via policy). | | **Scale** | Reference in backend detection rules for full rollout. | After validatation and deployment, continue monitoring rule performance to keep false positives low and coverage strong as workloads evolve. [1]: https://app.datadoghq.com/security/workload-protection/policies -[2]: https://app.datadoghq.com/security/workload-protection/agent-events \ No newline at end of file +[2]: https://app.datadoghq.com/security/workload-protection/agent-events +[3]: /security/workload_protection/agent_expressions \ No newline at end of file From 8492743df0246f39b6575c7c5f9a821ea6f4f1a0 Mon Sep 17 00:00:00 2001 From: Michael Cretzman <58786311+michaelcretzman@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:59:07 -0700 Subject: [PATCH 7/8] Apply suggestions from code review incorp peer review Co-authored-by: Brett Blue <84536271+brett0000FF@users.noreply.github.com> --- .../en/security/workload_protection/secl_auth_guide.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/en/security/workload_protection/secl_auth_guide.md b/content/en/security/workload_protection/secl_auth_guide.md index 56bd586a74f0e..b3d03ac8c663b 100644 --- a/content/en/security/workload_protection/secl_auth_guide.md +++ b/content/en/security/workload_protection/secl_auth_guide.md @@ -1,5 +1,5 @@ --- -title: Writing custom rule expressions +title: Writing Custom Rule Expressions disable_toc: false aliases: - /security/cloud_security_management/guide/custom-rules-guidelines @@ -10,7 +10,7 @@ This guide shows you how to write effective SECL (Security Language) rules for D ## SECL overview -Datadog SECL is a custom domain-specific language used to create agent expressions and policies within Datadog Workload Protection. SECL allows security teams to define real-time threat detection rules by specifying conditions, operators, and patterns that security agents can monitor across hosts, containers, applications, and cloud infrastructure. +Datadog SECL is a custom domain-specific language used to create Agent expressions and policies within Datadog Workload Protection. SECL allows security teams to define real-time threat detection rules by specifying conditions, operators, and patterns that security agents can monitor across hosts, containers, applications, and cloud infrastructure. ## How SECL rules fit together @@ -43,7 +43,7 @@ open.file.path == "/etc/shadow" && process.file.path not in ["/usr/sbin/vipw"] Here's a summary of the process: 1. Go to Workload Protection [Policies][1]. -2. Click **New Policy** to create a policy or open an existing policy. +2. Click **New Policy** to create a policy, or select an existing policy from the list to open it. 3. In the policy, click **Add Agent Rule**, and then click **Manual rule creator**. 4. In **Define the agent expression**, enter your expression using the following steps. 5. Select **Linux** or **Windows**. Most expression fields are OS-specific. @@ -85,7 +85,7 @@ When viewing the backend logic for a detection rule, do the following: - Always set the operating system (OS). - Anchor on ancestry to reduce noise. Use `process.ancestors.file.name`. - Use durations (for example, `> 5s`, `10m`, `2h`) to target narrow execution windows. -- Use exact match (`==`) whenever possible as it results in lowest noise. +- Use exact match (`==`) whenever possible as it results in the lowest noise. - List membership (`in [...]`) is best for allowlists or controlled sets of values. - Use a glob match (`~"/path/*"`) for path families as it is safer and faster than regex. - Use regex (`=~`) only when globs/lists can't be used. Keep the regex expression as narrow as possible. As a rule of thumb, start with `==` or `in [...]`. Reach for regex only as a last resort. From ae0f891e3f23904cb8c8f3f3a1c5cf2783bc13b8 Mon Sep 17 00:00:00 2001 From: Michael Cretzman Date: Wed, 15 Oct 2025 12:30:00 -0700 Subject: [PATCH 8/8] correcting code block lang should all be plaintext --- .../workload_protection/secl_auth_guide.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/content/en/security/workload_protection/secl_auth_guide.md b/content/en/security/workload_protection/secl_auth_guide.md index b3d03ac8c663b..be7e9e97ba8cc 100644 --- a/content/en/security/workload_protection/secl_auth_guide.md +++ b/content/en/security/workload_protection/secl_auth_guide.md @@ -26,13 +26,13 @@ In summary, the Agent rule finds raw behavior and the detection rule turns it in The standard format of a SECL expression is: -{{< code-block lang="javascript" >}} +{{< code-block lang="plaintext" >}} . [ .] ... {{< /code-block >}} Using this format, an example rule for a Linux system looks like this: -{{< code-block lang="javascript" >}} +{{< code-block lang="plaintext" >}} open.file.path == "/etc/shadow" && process.file.path not in ["/usr/sbin/vipw"] @@ -113,14 +113,14 @@ For information and examples of common building blocks like operators, patterns, #### Access to sensitive files (allowlist safe tools) -{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +{{< code-block lang="plaintext" disable_copy="true" collapsible="true" >}} open.file.path in ["/etc/shadow", "/etc/sudoers"] && process.file.path not in ["/usr/sbin/vipw", "/usr/sbin/visudo"] {{< /code-block >}} #### Nginx or PHP spawning bash -{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +{{< code-block lang="plaintext" disable_copy="true" collapsible="true" >}} exec.file.path == "/usr/bin/bash" && ( process.ancestors.file.name == "nginx" || @@ -130,7 +130,7 @@ exec.file.path == "/usr/bin/bash" && #### Suspicious IMDS access from container -{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +{{< code-block lang="plaintext" disable_copy="true" collapsible="true" >}} connect && network.destination.ip in ["169.254.169.254"] && container.id != "" @@ -138,7 +138,7 @@ container.id != "" #### Kernel module loads outside maintenance window -{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +{{< code-block lang="plaintext" disable_copy="true" collapsible="true" >}} load_module && process.user != "root" && process.ancestors.file.name not in ["modprobe", "insmod"] @@ -146,7 +146,7 @@ process.ancestors.file.name not in ["modprobe", "insmod"] #### Sensitive file read shortly after start -{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +{{< code-block lang="plaintext" disable_copy="true" collapsible="true" >}} open.file.path == "/etc/secret" && process.file.name == "java" && process.created_at > 5s @@ -154,7 +154,7 @@ process.created_at > 5s #### Outbound to non-corporate IPs (CIDR allowlist) -{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +{{< code-block lang="plaintext" disable_copy="true" collapsible="true" >}} connect && network.destination.ip not in [10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12] {{< /code-block >}} @@ -163,14 +163,14 @@ network.destination.ip not in [10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12] #### Registry persistence via run key -{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +{{< code-block lang="plaintext" disable_copy="true" collapsible="true" >}} set_key_value && open_key.registry.key_path =~ "*\\Software\\Microsoft\\Windows\\CurrentVersion\\Run*" {{< /code-block >}} #### Unsigned Binary Launching PowerShell -{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +{{< code-block lang="plaintext" disable_copy="true" collapsible="true" >}} exec.file.path =~ "*\\WindowsPowerShell\\v1.0\\powershell.exe" && process.parent.file.path !~ "*\\Program Files*" && process.user_sid != "S-1-5-18" @@ -180,7 +180,7 @@ process.user_sid != "S-1-5-18" #### Crypto-miner indicators -{{< code-block lang="bash" disable_copy="true" collapsible="true" >}} +{{< code-block lang="plaintext" disable_copy="true" collapsible="true" >}} exec.args_flags in ["cpu-priority", "donate-level", ~"randomx-1gb-pages"] || exec.args in [~"*stratum+tcp*", ~"*nicehash*", ~"*yespower*"] {{< /code-block >}}