Forge Detection Language
Every Qvasir detection is written in FDL, the Forge Detection Language — human-readable YAML, versioned in git, bound to the real fields of a real source. No canonical field taxonomy in the middle, no translation layer to go stale, no opaque rule store between you and your own logic. This page shows you actual production rules, not a syntax summary.
Why not Sigma
Sigma is a genuinely good thing for this industry, and Qvasir was built on it for its first year. The reasons we left are architectural rather than a complaint about the project or its community.
Sigma rules match on canonical field names and rely on a per-backend pipeline to translate those into whatever your data actually calls things. That translation is where the truth goes missing: a rule can be valid, compile cleanly, and match nothing — because the field it assumes is one your source never populates. The failure is silent, and silence looks exactly like safety.
FDL binds a detection to the fields a specific source actually carries, validated against a schema measured from your own events. If a field is not there, you find out while writing the rule.
Four more, in short:
The honest counter-argument to all of this is portability, and it deserves a straight answer. Publishing and distributing rule packages is a good idea and we intend to do it.
But a shared rule only holds if the data underneath has the same shape — and that is a claim about your telemetry that somebody has to check. Qvasir checks it on arrival. A rule that cannot work against your sources is reported as a visibility gap rather than deployed to sit silent.
Today there is no Sigma import path. If you have an existing Sigma library and that matters to your evaluation, tell us — it is a decision we are actively weighing, and the interesting version of it uses your measured schema to say which of your existing rules would actually fire here.
A rule, top to bottom
This is a real production rule from a demonstration environment, abridged only by removing empty optional blocks. Nothing has been simplified for the page.
fdl_version: 1.7.0
title: Remote Process Creation via WMIC
name: windows_remote_process_creation_via_wmic
version: 1.2.2
author: demo.user
level: high
confidence: high
tags:
- attack.execution
- attack.t1047
source:
logsource_name: Microsoft - Windows Security Event Log Workstation
ocsf_class: Process Activity
cluster_id: f6144d24-…
event_group: event_id_4688
entities:
- {field: event_data.SubjectUserName, type: user, role: actor}
- {field: event_data.ParentProcessName, type: file, role: actor}
- {field: computer, type: host, role: observer}
- {field: event_data.NewProcessName, type: file, role: context}
detect:
selection:
event_id: 4688
event_data.NewProcessName|endswith|ci: \wmic.exe
event_data.CommandLine|contains|all|ci|windash:
- '/node:'
- process call create
filter_system_account:
event_data.SubjectUserName|endswith: $
filter_known_parent:
event_data.ParentProcessName|endswith|in:
tunable(safe_parent_processes)
condition: selection and not filter_system_account
and not filter_known_parent
risk:
base: 45
modifiers:
- {l2: {entity_has_prior_tp: true}, multiply: 1.5}
falsepositives:
- Legitimate remote administration scripts used by system
administrators or management tools (e.g. SCCM, PDQ Deploy)
may use `wmic.exe` for remote process creation.
source binds the rule to one measured event shape.
Not "Windows logs" in the abstract — a specific logsource and a specific
cluster of events within it, whose field schema has been measured from real
traffic. This is the line that makes portability a checkable claim rather
than an assumption.entities is what correlation and scoring are built on.
Each declaration says which field carries which kind of thing, and what part
it plays. The engine resolves events to those entities and reasons about
them over time.wmic.exe and /node:
and process call create. Remote execution, not local
use.windash handles an evasion class declaratively.
Attackers substitute dash characters — /node:,
-node:, en-dashes — to slip past literal matching. One modifier
covers the equivalence class, rather than a hand-maintained list of variants
that someone has to remember to extend.tunable(...) is where deployment-specific values live.
The rule ships with an empty allowlist; your admin tooling goes in the
tunable, not into a fork of the rule. The logic and the local truth stay
separable, which is what makes the rule updatable.Correlation
Obfuscated PowerShell followed by a service installed from a user-writable path is a well-worn persistence pattern. Individually each half is ordinary; together, within ten minutes on one host, they are worth waking someone for. Here are both halves and the rule that joins them.
# ── member 1 ─────────────────────────────
name: powershell_encoded_command_parameter_binding
level: medium
confidence: high
tags: [attack.defense_evasion, attack.t1027.010,
attack.execution, attack.t1059.001]
entities:
- {field: user_name, type: user, role: actor}
- {field: computer, type: host, role: observer}
detect:
selection_event: {event_id: 4103}
selection_encoded_param:
event_data.Payload|re|ci: parameterbinding.*?name\s*=\s*
['"]?\b(e|en|enc|encodedcommand)\b['"]?
filter_system_principals:
user_name|in|ci: [SYSTEM, LOCAL SERVICE, NETWORK SERVICE]
filter_computer_accounts:
user_name|endswith: $
condition: selection_event and selection_encoded_param
and not (filter_computer_accounts
or filter_system_principals
or filter_whitelisted_users)
# ── member 2 ─────────────────────────────
name: windows_service_installed_from_suspicious_location
level: high
confidence: medium
entities:
- {field: host, type: host, role: observer}
- {field: event_data.ServiceName, type: service, role: target}
- {field: event_data.ServiceFileName, type: file, role: target}
detect:
selection_event: {event_id: 4697}
selection_path:
event_data.ServiceFileName|contains|ci:
- C:\Users\
- C:\ProgramData\
- \AppData\
- \Temp\
- C:\PerfLogs\
condition: selection_event and selection_path
and not filter_known_good
risk:
base: 45
modifiers:
- {l1: {service_name_entropy|gt: 4.5}, add: 20}
- {l1: {event_data.SubjectUserName|endswith: $}, add: -15}
computer, the other calls it host — different
sources, different vendors, different names. Neither was changed to
accommodate the other.-EncodedCommand,
-enc, -en and -e are all the same
thing to the interpreter and all different to a naive string match.
(e|en|enc|encodedcommand) covers the family.service_name_entropy is computed, not logged.
Shannon entropy over the service name, available to the rule as if it were a
field. Randomly-generated service names are a real signal and no source
emits that measurement.fdl_version: 1.7.0
title: 'Attack Chain: Obfuscated PowerShell to Suspicious
Service Installation'
version: 1.1.0
level: critical
confidence: high
detect: null # nothing to match — this rule joins signals
correlate:
type: temporal_ordered
rules:
- powershell_encoded_command_parameter_binding
- windows_service_installed_from_suspicious_location
group_by_entity: [host]
timespan: 10m
signal_mode: correlate_only
suppress:
window: 6h
per: [host]
falsepositives:
- RMM agents running in a user context (ConnectWise, NinjaOne,
Datto) that stage via encoded PowerShell and register a
service in the same session.
- Package managers and their wrappers — Chocolatey, winget
scripts, vendor bootstrap installers — which legitimately
combine encoded PowerShell with a service install inside
the correlation window.
- In-house deployment scripts that drop a service executable
into ProgramData or a user-writable path.
- 'Not expected from SCCM/Intune: the PowerShell member
filters SYSTEM, LOCAL SERVICE, NETWORK SERVICE and machine
accounts, so platform-driven deployment does not reach
this chain.'
group_by_entity: [host] — not a field name. This
is the part worth pausing on. The chain does not say "join where
computer equals host." It groups by the host
entity, and the engine resolves each member's declared field to an
identity before correlating. Two rules written months apart against different
vendors correlate correctly with no mapping table and nothing to keep in
sync.SYSTEM as its subject whenever the service install follows a
privilege escalation. A user-keyed window would have missed exactly the case
this rule exists for — the attacker who escalated. The host is the entity
that genuinely spans both stages.Status
Every rule declares the fdl_version it targets, so a rule and the
grammar it was written against never drift apart silently. Breaking changes take
a version bump and a migration note.
FDL is young and has not yet been through the hands of many detection engineers other than ours. We would rather say that than imply a stability we have not earned yet.
Once the grammar has survived contact with pilot deployments, the full specification goes out publicly — versioned, with its changelog. Your detection logic should not depend on a document only we can read.
Rules shown are real production detections from a demonstration environment, abridged by removing empty optional blocks and truncating internal identifiers. Author metadata has been replaced with a placeholder. Nothing in the detection logic has been altered or simplified.
The interesting question is not whether the grammar reads well. It is what a rule written in it would have caught in your estate last month.