DetectionRule
A Guardian alert rule: a detection run on a schedule over the tenant's logs, raising alerts. Fields, YAML, HCL and CLI samples, and how apply treats it.
A Guardian alert rule: a detection run on a schedule over the tenant’s logs, raising alerts. Name exactly one of body, file, searchRef, countermeasure, sigmahq or ioc. body and file are the tenant’s own rule; the others run a rule Guardian already has, which can be tuned (level, threshold, suppressions, timing) but not edited. A rule whose detection does not compile, or names a log source Guardian has no table for, is stored turned off. The plan caps how many rules run at once. Deleting one that an AlertTrigger still uses is refused.
apiVersion |
vulnetix.com/v1 |
kind |
DetectionRule |
| Group | SIEM |
| Status | Served. The config engine plans and applies it. |
| Objects per tenant | many, keyed by metadata.name |
| Token and role | A vmt_ config management token that lists DetectionRule in its kinds. The owner or admin role writes; the member role reads only. A console session or an ApiKey is refused with kind_not_allowed, because the kind is written in Guardian with the caller’s own token. |
| Applied after | SavedSearch, MessagingConnection (deleted before them) |
| Terraform resource | vulnetix_detection_rule, imported by name (see Terraform and OpenTofu) |
| CLI | vulnetix config apply, CLI v3.165.1 and later. |
| Pinned schema | v1.15.0/DetectionRule.schema.json |
| Newest schema | latest/DetectionRule.schema.json |
| First released in | v1.3.0 |
| Print the built-in copy | vulnetix config schema DetectionRule |
Minimal
Only the fields the kind requires. From examples/DetectionRule/minimal.yaml.
YAML
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/DetectionRule.schema.json
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: sigmahq-aws-root-login
spec:
sigmahq: rules/cloud/aws/cloudtrail/aws_root_account_usage.yml
HCL (resource vulnetix_detection_rule)
# Only the arguments the kind requires. From config-schemas examples/DetectionRule/minimal.yaml.
resource "vulnetix_detection_rule" "sigmahq_aws_root_login" {
name = "sigmahq-aws-root-login"
sigmahq = "rules/cloud/aws/cloudtrail/aws_root_account_usage.yml"
}
CLI (applies it)
export VULNETIX_CONFIG_TOKEN=vmt_... # a config management token
vulnetix config apply -f - --manager gitops:acme/platform --auto-approve <<'EOF'
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/DetectionRule.schema.json
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: sigmahq-aws-root-login
spec:
sigmahq: rules/cloud/aws/cloudtrail/aws_root_account_usage.yml
EOF
Advanced
Every field the kind supports, with a when expression where it takes one. From examples/DetectionRule/advanced.yaml.
YAML
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/DetectionRule.schema.json
# A rule names exactly one of body, file, searchRef, countermeasure, sigmahq
# or ioc, so each form has its own document.
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: dynamic-dns
labels:
team: secops
mitre-tactic: command-and-control
annotations:
runbook: https://wiki.example.com/secops/dynamic-dns
spec:
displayName: Dynamic DNS lookup
language: sigma
body: |
title: Dynamic DNS lookup
id: 6f3e0c52-0d4b-4c8e-9a51-2a6b9a0e7d10
level: medium
logsource:
category: dns_query
detection:
selection:
QueryName|endswith:
- .duckdns.org
- .no-ip.org
condition: selection
enabled: true
level: medium
severityOverride: warning
intervalSec: 120
latenessSec: 300
suppressSec: 1800
maxAlertsPerRun: 50
threshold:
count: 5
windowSec: 600
by: [src_ip]
suppressions:
- field: QueryName
value: updates.duckdns.org
until: "2026-12-31"
- field: src_ip
value: 10.20.0.15
target:
connections: [secops-slack]
emails: [secops@example.com]
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: brute-force-file
spec:
displayName: SSH brute force
language: sigma
file: siem/rules/brute-force.yml
enabled: false
level: high
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: dns-search-rule
spec:
searchRef: dynamic-dns-lookups
level: low
intervalSec: 600
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: log4shell-countermeasure
spec:
countermeasure: 3b1f8a62-7d2e-4c41-9a0b-5e6f7c8d9e01
severityOverride: critical
intervalSec: 300
target:
connections: [secops-slack]
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: sigmahq-aws-root-login
spec:
sigmahq: rules/cloud/aws/cloudtrail/aws_root_account_usage.yml
level: high
suppressSec: 3600
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: indicator-domains
spec:
displayName: Indicator domain resolved
ioc: ioc-dns
maxAlertsPerRun: 100
latenessSec: 120
HCL (resource vulnetix_detection_rule)
# Every argument the kind supports. From config-schemas examples/DetectionRule/advanced.yaml.
resource "vulnetix_detection_rule" "dynamic_dns" {
name = "dynamic-dns"
body = "title: Dynamic DNS lookup\nid: 6f3e0c52-0d4b-4c8e-9a51-2a6b9a0e7d10\nlevel: medium\nlogsource:\n category: dns_query\ndetection:\n selection:\n QueryName|endswith:\n - .duckdns.org\n - .no-ip.org\n condition: selection\n"
display_name = "Dynamic DNS lookup"
enabled = true
interval_sec = 120
language = "sigma"
lateness_sec = 300
level = "medium"
max_alerts_per_run = 50
severity_override = "warning"
suppress_sec = 1800
suppressions = [
{
field = "QueryName"
until = "2026-12-31"
value = "updates.duckdns.org"
},
{
field = "src_ip"
value = "10.20.0.15"
},
]
target = {
connections = ["secops-slack"]
emails = ["secops@example.com"]
}
threshold = {
by = ["src_ip"]
count = 5
window_sec = 600
}
labels = {
mitre-tactic = "command-and-control"
team = "secops"
}
annotations = {
runbook = "https://wiki.example.com/secops/dynamic-dns"
}
}
resource "vulnetix_detection_rule" "brute_force_file" {
name = "brute-force-file"
display_name = "SSH brute force"
enabled = false
file = "siem/rules/brute-force.yml"
language = "sigma"
level = "high"
}
resource "vulnetix_detection_rule" "dns_search_rule" {
name = "dns-search-rule"
interval_sec = 600
level = "low"
search_ref = "dynamic-dns-lookups"
}
resource "vulnetix_detection_rule" "log4shell_countermeasure" {
name = "log4shell-countermeasure"
countermeasure = "3b1f8a62-7d2e-4c41-9a0b-5e6f7c8d9e01"
interval_sec = 300
severity_override = "critical"
target = {
connections = ["secops-slack"]
}
}
resource "vulnetix_detection_rule" "sigmahq_aws_root_login" {
name = "sigmahq-aws-root-login"
level = "high"
sigmahq = "rules/cloud/aws/cloudtrail/aws_root_account_usage.yml"
suppress_sec = 3600
}
resource "vulnetix_detection_rule" "indicator_domains" {
name = "indicator-domains"
display_name = "Indicator domain resolved"
ioc = "ioc-dns"
lateness_sec = 120
max_alerts_per_run = 100
}
CLI (applies it)
export VULNETIX_CONFIG_TOKEN=vmt_... # a config management token
vulnetix config apply -f - --manager gitops:acme/platform --auto-approve <<'EOF'
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/DetectionRule.schema.json
# A rule names exactly one of body, file, searchRef, countermeasure, sigmahq
# or ioc, so each form has its own document.
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: dynamic-dns
labels:
team: secops
mitre-tactic: command-and-control
annotations:
runbook: https://wiki.example.com/secops/dynamic-dns
spec:
displayName: Dynamic DNS lookup
language: sigma
body: |
title: Dynamic DNS lookup
id: 6f3e0c52-0d4b-4c8e-9a51-2a6b9a0e7d10
level: medium
logsource:
category: dns_query
detection:
selection:
QueryName|endswith:
- .duckdns.org
- .no-ip.org
condition: selection
enabled: true
level: medium
severityOverride: warning
intervalSec: 120
latenessSec: 300
suppressSec: 1800
maxAlertsPerRun: 50
threshold:
count: 5
windowSec: 600
by: [src_ip]
suppressions:
- field: QueryName
value: updates.duckdns.org
until: "2026-12-31"
- field: src_ip
value: 10.20.0.15
target:
connections: [secops-slack]
emails: [secops@example.com]
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: brute-force-file
spec:
displayName: SSH brute force
language: sigma
file: siem/rules/brute-force.yml
enabled: false
level: high
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: dns-search-rule
spec:
searchRef: dynamic-dns-lookups
level: low
intervalSec: 600
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: log4shell-countermeasure
spec:
countermeasure: 3b1f8a62-7d2e-4c41-9a0b-5e6f7c8d9e01
severityOverride: critical
intervalSec: 300
target:
connections: [secops-slack]
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: sigmahq-aws-root-login
spec:
sigmahq: rules/cloud/aws/cloudtrail/aws_root_account_usage.yml
level: high
suppressSec: 3600
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: indicator-domains
spec:
displayName: Indicator domain resolved
ioc: ioc-dns
maxAlertsPerRun: 100
latenessSec: 120
EOF
Fields
Paths use dots for nesting and [] for the items of a list. Required means required whenever the parent object is present. A default is what the server uses when the field is left out.
| Field | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
apiVersion |
string | yes | exactly vulnetix.com/v1 |
The document format version. Every kind in this release is vulnetix.com/v1. | |
kind |
string | yes | exactly DetectionRule |
Always DetectionRule. | |
metadata |
object | yes | no other keys | ||
metadata.name |
string | yes | pattern ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ |
The object’s name, unique per kind within a tenant. It is the natural key: plan, apply, export and the Terraform provider all address an object by kind and name. | |
metadata.org |
string | no | format uuid |
The tenant (organization UUID) the document belongs to. When set, plan and apply refuse to run against any other tenant. | |
metadata.labels |
object | no | map of string to string (length 0 to 256); keys match ^[a-z0-9]([a-z0-9._/-]{0,61}[a-z0-9])?$ |
Free key/value labels. Stored with the object, never interpreted by the server. | |
metadata.annotations |
object | no | map of string to string (length 0 to 4096) | Free key/value notes for people and tools, such as a ticket link. | |
spec |
object | yes | no other keys | ||
spec.displayName |
string | no | length 1 to 200 | The name the console shows. Defaults to the Sigma rule’s title, or the saved search’s, countermeasure’s or built-in rule’s name. | |
spec.language |
string | no | sigma, dsl |
The language of body or file. sigma: a Sigma rule, or several in one multi-document YAML with one correlation rule, which is the one that alerts. dsl: the Guardian search language. Guardian keeps a DSL rule only as a copy of a saved search today, so a dsl body or file has no direct write path yet; use searchRef until Guardian takes DSL rule text. | |
spec.body |
string | no | length 1 to 262144 | The rule text, at most 256 KiB. | |
spec.file |
string | no | length 1 to 1024 | A file holding the rule text, the same as body. | |
spec.searchRef |
string | no | pattern ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ |
The metadata.name of a SavedSearch whose text the rule runs. Its text is copied when the rule is created: editing the search later does not change the rule. | |
spec.countermeasure |
string | no | format uuid |
A Vulnetix countermeasure of kind SIGMA, by uuid: the virtual patching lane’s detection for a vulnerability. | |
spec.sigmahq |
string | no | pattern ^[A-Za-z0-9_./-]+\.ya?ml$ |
A rule from the SigmaHQ catalogue Guardian imports weekly, by its path in the SigmaHQ repository. Only rules that compile are offered. | |
spec.ioc |
string | no | ioc-dns, ioc-net |
A built-in indicator rule. ioc-dns: a domain on an indicator list resolved. ioc-net: a connection to an indicator address. | |
spec.enabled |
boolean | no | true |
Whether the rule runs. A rule that cannot compile is never on, whatever this says. | |
spec.level |
string | no | informational, low, medium, high, critical |
The Sigma level. Defaults to the rule’s own level, or medium. | |
spec.severityOverride |
string | no | info, notice, warning, critical |
The alert severity, instead of the one the level maps to (informational to info, low to notice, medium to warning, high and critical to critical). | |
spec.intervalSec |
integer | no | 60 |
60 to 3600 | Seconds between runs. |
spec.latenessSec |
integer | no | 300 |
0 to 3600 | How long a run waits for late rows before it reads a window, in seconds. |
spec.suppressSec |
integer | no | 900 |
0 to 604800 | Seconds during which a repeat of the same alert is merged into the open one instead of raising another. |
spec.maxAlertsPerRun |
integer | no | 50 |
1 to 1000 | Alerts one run may raise. Beyond it, one overflow alert says how many more matched. |
spec.threshold |
object | no | no other keys | Alert only when at least count rows match within windowSec, counted per the by fields. Wraps the detection as a Sigma event_count correlation. | |
spec.threshold.count |
integer | yes | 1 to 1000000 | ||
spec.threshold.windowSec |
integer | yes | 60 to 86400 | ||
spec.threshold.by |
array of string | no | 0 to 5 items; each item: length 1 to 80 | ||
spec.suppressions |
array of object | no | 0 to 50 items | Values that never alert. Each adds a filter on field equal to value to the rule’s detection until it lapses. | |
spec.suppressions[].field |
string | yes | pattern ^[A-Za-z0-9_.-]{1,80}$ |
||
spec.suppressions[].value |
string | yes | length 1 to 512 | ||
spec.suppressions[].until |
string | no | format date |
The last day it applies is the day before this date (UTC). Left out, it never lapses. | |
spec.target |
object | no | no other keys | Where the rule’s alerts are sent, beyond the Guardian alerts list. | |
spec.target.connections |
array of string | no | 0 to 10 items; items unique; each item: pattern ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ |
||
spec.target.emails |
array of string | no | 0 to 20 items; items unique; each item: format email; length 0 to 254 |
Rules
Conditions the schema enforces beyond each field’s own type. vulnetix config validate and the engine’s plan report a document that breaks one.
| On | Rule |
|---|---|
spec |
When : language is required. Otherwise: language is not allowed. |
spec |
Exactly one of body, file, searchRef, countermeasure, sigmahq or ioc is set. |
Applying this kind
- Command.
vulnetix config apply -f <file>; the CLI line above says from which release. Plan first withvulnetix config plan: it shows each object as create, update, delete or no change, with aplanHashthat apply then holds to. Apply writes in one transaction, except for the second phase below. - Manager. Each object records the manager that applied it (
terraform,consoleorgitops:<repo>).--prunedeletes only objects its own manager owns. See The manager and the config engine. - Order. Applied after
SavedSearchandMessagingConnection, so a document that refers to one bymetadata.namecan be in the same apply. Deleted before them. - Second phase. This kind is not written inside the apply transaction. The other kinds apply and commit first, then each DetectionRule is written on its own, in Guardian. One that fails reports
status.phase: Failedand amessage, and nothing is recorded for it. The others stay applied and the apply answers 207. Plan again to see what is left. A singlePUTfrom the Terraform provider answers 502external_failedinstead. - Status. A failed second-phase write reports phase
Failedwith amessage. The phases the engine reports are explained under status phases.
How apply treats it
Written in Guardian, in the second phase; see SIEM kinds.
- Levers, threshold, target, suppressions, level, display name and a Sigma
bodychange in place. - The source (
searchRef,countermeasure,sigmahq,ioc, or a body versus another form) cannot move. The plan blocks it: declare the new rule under a new name. sigmahqis resolved by path through Guardian’s catalogue.fileis refused, because the server never reads a path. Send the text asbody.- A
dslbody is refused, because Guardian keeps a DSL rule only as a copy of a saved search. UsesearchRef. - A rule Guardian cannot compile stays off whatever
enabledsays, soenabledis not a change for it and the plan warns with the compile error. displayNameandleveldefault to Guardian’s choice, so they are compared only when the document sets them.
Terraform and OpenTofu
The resource is vulnetix_detection_rule in the vulnetix/resolve provider. Its arguments are the spec in snake case, beside name, labels and annotations from metadata. The Minimal and Advanced HCL are above. The provider’s own reference is vulnetix_detection_rule.
Import an object that already exists by its name:
# Import an existing DetectionRule by its name (metadata.name).
terraform import vulnetix_detection_rule.sigmahq_aws_root_login sigmahq-aws-root-login
Install, authentication and how state is read: Terraform and OpenTofu.
More examples
examples/DetectionRule/rules.yaml from Vulnetix/config-schemas:
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/DetectionRule.schema.json
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: dynamic-dns
spec:
language: sigma
body: |
title: Dynamic DNS lookup
id: 6f3e0c52-0d4b-4c8e-9a51-2a6b9a0e7d10
level: medium
logsource:
category: dns_query
detection:
selection:
QueryName|endswith:
- .duckdns.org
- .no-ip.org
condition: selection
threshold: { count: 5, windowSec: 600, by: [src_ip] }
suppressions:
- field: QueryName
value: updates.duckdns.org
until: "2026-12-31"
target:
connections: [secops-slack]
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: brute-force-file
spec:
language: sigma
file: siem/rules/brute-force.yml
enabled: false
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: log4shell-countermeasure
spec:
countermeasure: 3b1f8a62-7d2e-4c41-9a0b-5e6f7c8d9e01
severityOverride: critical
intervalSec: 300
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: sigmahq-aws-root-login
spec:
sigmahq: rules/cloud/aws/cloudtrail/aws_root_account_usage.yml
level: high
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: indicator-domains
spec:
ioc: ioc-dns
maxAlertsPerRun: 100
---
apiVersion: vulnetix.com/v1
kind: DetectionRule
metadata:
name: dns-search-rule
spec:
searchRef: dynamic-dns-lookups
level: low
Check a file with vulnetix config validate -f <file>. See Validate documents. In an editor, start the file with the schema comment.