Signatures
YAML-based detection rules, format, operators, fields, and hot-reload
Overview
Detection rules live in signatures/ as YAML files, organized by protocol. The engine hot-reloads rules at runtime; no restart needed.
signatures/
├── dns/
│ ├── tunneling.yaml # DNS-TUN-001, DNS-TUN-002
│ ├── exfiltration.yaml # DNS-EXFIL-001, DNS-EXFIL-002
│ └── dga.yaml # DNS-DGA-001
├── http/
│ ├── upload_exfil.yaml # HTTP-EXFIL-001, HTTP-EXFIL-002
│ └── suspicious_headers.yaml # HTTP-HDR-001
└── tls/
└── suspicious_cert.yaml # TLS-CERT-001, TLS-CERT-00216 rules ship by default across five directories (dns/, flow/, icmp/, tls/, http/). Operators can add, modify, or remove rules without restarting the server.
Rule Format
rules:
- id: DNS-TUN-001
name: "DNS Tunneling - High Entropy Subdomain"
description: "Detects high-entropy subdomains characteristic of DNS tunneling"
severity: High
category: DnsTunneling
protocol: dns
enabled: true
tags: [dns, tunneling, entropy]
references: [T1071.004]
conditions:
- field: subdomain_entropy
operator: gt
value: "4.0"
- field: query_length
operator: gt
value: "50"Rule Fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier (convention: PROTO-TYPE-NNN) |
name | string | Yes | Human-readable name |
description | string | Yes | What the rule detects |
severity | enum | Yes | Low, Medium, High, Critical |
category | enum | Yes | Threat category (see below) |
protocol | enum | Yes | dns, http, tls, icmp, tcp, udp, any |
enabled | bool | No | Default true. Set false to disable without deleting |
tags | list | No | Freeform tags for organization |
references | list | No | MITRE ATT&CK technique IDs |
conditions | list | Yes | All must match (AND logic) |
Threat Categories (16)
| Category | Description |
|---|---|
DnsTunneling | DNS used as data transport (iodine, dnscat2) |
DnsExfiltration | Data encoded in DNS queries |
DgaDomain | Algorithmically generated domain names |
DnsFlood | Abnormal DNS query rates |
HttpExfiltration | Data exfiltration via HTTP uploads |
HttpCovertChannel | Covert data in HTTP headers/chunked transfer |
TlsMaliciousFingerprint | Known-bad JA3 fingerprints |
TlsCertAnomaly | Self-signed, expired, or mismatched certificates |
TlsToRawIp | TLS connection to a bare IP (no SNI) |
IcmpCovertChannel | Data hidden in ICMP payloads |
IcmpLargePayload | ICMP payloads exceeding normal size |
C2Beaconing | Regular interval connections (command and control) |
DataExfiltrationVolume | Excessive outbound data transfer |
UnusualProcessNetwork | Unexpected process making network connections |
SuspiciousNewDestination | Connection to a never-before-seen destination |
BaselineDeviation | Statistical deviation from learned baseline |
Condition System
Operators (12)
| Operator | Type | Description | Example |
|---|---|---|---|
eq | String | Exact equality | protocol eq "Tcp" |
neq | String | Not equal | process.name neq "chrome.exe" |
gt | Numeric | Greater than (casts to f64) | flow.byte_count gt "1000000" |
gte | Numeric | Greater or equal | flow.packet_count gte "100" |
lt | Numeric | Less than | flow.avg_interval_ms lt "1000" |
lte | Numeric | Less or equal | flow.outbound_ratio lte "0.5" |
contains | String | Substring match | process.name contains "python" |
not_contains | String | No substring match | net.dst_ip not_contains "192.168" |
regex | Regex | Regex pattern match | subdomain_pattern regex "^[A-Za-z0-9+/=]{20,}" |
exists | Boolean | Field is non-empty | flow.has_dns exists "" |
in | List | Value in comma-separated list | net.dst_port in "53,5353,853" |
not_in | List | Value NOT in list | net.protocol not_in "Tcp,Udp" |
All condition values are strings. Numeric operators parse both field and value to f64 for comparison.
Available Fields (23)
These fields are populated by extract_fields() in analysis/src/server/signatures/engine.rs and available to all rules:
Flow metrics (flow.*):
| Field | Type | Description |
|---|---|---|
flow.packet_count | u64 | Total packets in flow |
flow.byte_count | u64 | Total bytes in flow |
flow.outbound_bytes | u64 | Outbound bytes only |
flow.outbound_ratio | f64 | Outbound / total bytes (0.0 - 1.0) |
flow.avg_packet_size | f64 | Mean packet size (bytes) |
flow.avg_interval_ms | f64 | Mean inter-packet interval |
flow.interval_stddev_ms | f64 | Standard deviation of intervals |
flow.duration_ms | u64 | Flow duration |
flow.throughput_bps | f64 | Bytes per second |
flow.packet_rate | f64 | Packets per second |
flow.has_dns | bool | Flow contains DNS traffic |
flow.has_tls | bool | Flow contains TLS traffic |
flow.has_icmp | bool | Flow contains ICMP traffic |
Network context (net.*):
| Field | Type | Description |
|---|---|---|
net.src_ip | IpAddr | Source IP address |
net.dst_ip | IpAddr | Destination IP address |
net.src_port | u16 | Source port |
net.dst_port | u16 | Destination port |
net.protocol | enum | Tcp, Udp, Icmp, Other |
Process context (process.*):
| Field | Type | Description |
|---|---|---|
process.name | string | Process executable name |
process.id | u32 | Process ID (PID) |
Condition Logic
All conditions within a rule use AND logic: every condition must match for the rule to fire. To express OR logic, create separate rules.
If a field referenced in a condition is not present in the flow data, that condition evaluates to false (the rule does not match).
Confidence Mapping
When a rule matches, the engine generates a Detection with confidence based on severity:
| Severity | Confidence |
|---|---|
| Low | 0.40 |
| Medium | 0.60 |
| High | 0.75 |
| Critical | 0.90 |
Existing Rules Reference
DNS Rules
| ID | Name | Severity | Conditions |
|---|---|---|---|
DNS-DGA-001 | Domain Generation Algorithm Detection | Critical | domain_entropy > 3.5 AND consonant_ratio > 0.7 AND domain_length > 12 |
DNS-EXFIL-001 | DNS Exfiltration - Large Payload | High | query_size > 512 |
DNS-EXFIL-002 | DNS Exfiltration - Encoded Data Pattern | Medium | subdomain_pattern matches base64 regex |
DNS-TUN-001 | DNS Tunneling - High Entropy Subdomain | High | subdomain_entropy > 4.0 AND query_length > 50 |
DNS-TUN-002 | DNS Tunneling - Excessive TXT Queries | Medium | query_type == "TXT" AND frequency_per_minute > 30 |
HTTP Rules
| ID | Name | Severity | Conditions |
|---|---|---|---|
HTTP-HDR-001 | Suspicious Custom Headers | Medium | custom_header_count > 5 AND header_entropy > 4.0 |
HTTP-EXFIL-001 | HTTP Large Upload to Unknown Host | High | method == "POST" AND content_length > 1048576 AND host_in_baseline == "false" |
HTTP-EXFIL-002 | HTTP Chunked Upload Exfiltration | Medium | transfer_encoding == "chunked" AND destination_external == "true" |
TLS Rules
| ID | Name | Severity | Conditions |
|---|---|---|---|
TLS-CERT-001 | Self-Signed Certificate to External Host | Medium | cert_self_signed == "true" AND destination_external == "true" |
TLS-CERT-002 | TLS SNI Mismatch | High | sni_cert_match == "false" |
Hot-Reload
The signature loader watches the signatures/ directory for changes. When a YAML file is added, modified, or removed, the rule set is recompiled behind an RwLock without interrupting the detection pipeline.
- Rules with
enabled: falseare filtered out during loading - Duplicate rule IDs trigger a warning log
- Invalid YAML files are skipped with an error log
Configuration
In config.yaml:
analysis:
server:
signatures:
enabled: true
rules_dir: "signatures"
reload_interval_s: 30Adding a New Rule
- Create a
.yamlfile insignatures/<protocol>/(or add to an existing file) - Use the YAML format above with a unique
id - Reference available fields from the table above
- The rule is picked up automatically within
reload_interval_sseconds - Verify with server logs:
Loaded N signature rules
Example: ICMP Covert Channel
rules:
- id: ICMP-COV-001
name: "ICMP Covert Channel - Large Payload"
description: "Detects ICMP payloads exceeding normal size"
severity: High
category: IcmpCovertChannel
protocol: icmp
enabled: true
conditions:
- field: flow.has_icmp
operator: eq
value: "true"
- field: flow.avg_packet_size
operator: gt
value: "128"
- field: flow.packet_count
operator: gt
value: "10"Example: C2 Beaconing
rules:
- id: C2-BEACON-001
name: "Regular Interval Beaconing"
description: "Detects regular communication intervals characteristic of C2"
severity: High
category: C2Beaconing
protocol: any
enabled: true
conditions:
- field: flow.interval_stddev_ms
operator: lt
value: "500"
- field: flow.packet_count
operator: gt
value: "20"
- field: flow.outbound_ratio
operator: gt
value: "0.6"