ExfilGuardian
Backend

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-002

16 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

FieldTypeRequiredDescription
idstringYesUnique identifier (convention: PROTO-TYPE-NNN)
namestringYesHuman-readable name
descriptionstringYesWhat the rule detects
severityenumYesLow, Medium, High, Critical
categoryenumYesThreat category (see below)
protocolenumYesdns, http, tls, icmp, tcp, udp, any
enabledboolNoDefault true. Set false to disable without deleting
tagslistNoFreeform tags for organization
referenceslistNoMITRE ATT&CK technique IDs
conditionslistYesAll must match (AND logic)

Threat Categories (16)

CategoryDescription
DnsTunnelingDNS used as data transport (iodine, dnscat2)
DnsExfiltrationData encoded in DNS queries
DgaDomainAlgorithmically generated domain names
DnsFloodAbnormal DNS query rates
HttpExfiltrationData exfiltration via HTTP uploads
HttpCovertChannelCovert data in HTTP headers/chunked transfer
TlsMaliciousFingerprintKnown-bad JA3 fingerprints
TlsCertAnomalySelf-signed, expired, or mismatched certificates
TlsToRawIpTLS connection to a bare IP (no SNI)
IcmpCovertChannelData hidden in ICMP payloads
IcmpLargePayloadICMP payloads exceeding normal size
C2BeaconingRegular interval connections (command and control)
DataExfiltrationVolumeExcessive outbound data transfer
UnusualProcessNetworkUnexpected process making network connections
SuspiciousNewDestinationConnection to a never-before-seen destination
BaselineDeviationStatistical deviation from learned baseline

Condition System

Operators (12)

OperatorTypeDescriptionExample
eqStringExact equalityprotocol eq "Tcp"
neqStringNot equalprocess.name neq "chrome.exe"
gtNumericGreater than (casts to f64)flow.byte_count gt "1000000"
gteNumericGreater or equalflow.packet_count gte "100"
ltNumericLess thanflow.avg_interval_ms lt "1000"
lteNumericLess or equalflow.outbound_ratio lte "0.5"
containsStringSubstring matchprocess.name contains "python"
not_containsStringNo substring matchnet.dst_ip not_contains "192.168"
regexRegexRegex pattern matchsubdomain_pattern regex "^[A-Za-z0-9+/=]{20,}"
existsBooleanField is non-emptyflow.has_dns exists ""
inListValue in comma-separated listnet.dst_port in "53,5353,853"
not_inListValue NOT in listnet.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.*):

FieldTypeDescription
flow.packet_countu64Total packets in flow
flow.byte_countu64Total bytes in flow
flow.outbound_bytesu64Outbound bytes only
flow.outbound_ratiof64Outbound / total bytes (0.0 - 1.0)
flow.avg_packet_sizef64Mean packet size (bytes)
flow.avg_interval_msf64Mean inter-packet interval
flow.interval_stddev_msf64Standard deviation of intervals
flow.duration_msu64Flow duration
flow.throughput_bpsf64Bytes per second
flow.packet_ratef64Packets per second
flow.has_dnsboolFlow contains DNS traffic
flow.has_tlsboolFlow contains TLS traffic
flow.has_icmpboolFlow contains ICMP traffic

Network context (net.*):

FieldTypeDescription
net.src_ipIpAddrSource IP address
net.dst_ipIpAddrDestination IP address
net.src_portu16Source port
net.dst_portu16Destination port
net.protocolenumTcp, Udp, Icmp, Other

Process context (process.*):

FieldTypeDescription
process.namestringProcess executable name
process.idu32Process 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:

SeverityConfidence
Low0.40
Medium0.60
High0.75
Critical0.90

Existing Rules Reference

DNS Rules

IDNameSeverityConditions
DNS-DGA-001Domain Generation Algorithm DetectionCriticaldomain_entropy > 3.5 AND consonant_ratio > 0.7 AND domain_length > 12
DNS-EXFIL-001DNS Exfiltration - Large PayloadHighquery_size > 512
DNS-EXFIL-002DNS Exfiltration - Encoded Data PatternMediumsubdomain_pattern matches base64 regex
DNS-TUN-001DNS Tunneling - High Entropy SubdomainHighsubdomain_entropy > 4.0 AND query_length > 50
DNS-TUN-002DNS Tunneling - Excessive TXT QueriesMediumquery_type == "TXT" AND frequency_per_minute > 30

HTTP Rules

IDNameSeverityConditions
HTTP-HDR-001Suspicious Custom HeadersMediumcustom_header_count > 5 AND header_entropy > 4.0
HTTP-EXFIL-001HTTP Large Upload to Unknown HostHighmethod == "POST" AND content_length > 1048576 AND host_in_baseline == "false"
HTTP-EXFIL-002HTTP Chunked Upload ExfiltrationMediumtransfer_encoding == "chunked" AND destination_external == "true"

TLS Rules

IDNameSeverityConditions
TLS-CERT-001Self-Signed Certificate to External HostMediumcert_self_signed == "true" AND destination_external == "true"
TLS-CERT-002TLS SNI MismatchHighsni_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: false are 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: 30

Adding a New Rule

  1. Create a .yaml file in signatures/<protocol>/ (or add to an existing file)
  2. Use the YAML format above with a unique id
  3. Reference available fields from the table above
  4. The rule is picked up automatically within reload_interval_s seconds
  5. 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"

On this page