A Sigma rule that worked perfectly in its author's lab can still produce two hundred alerts a day on your network. Nothing is wrong with the rule. Its author simply never saw your software deployment tool, your backup agent, or the login script someone wrote in 2017 that still runs on every laptop. Tuning is the work of teaching a rule about the environment it actually runs in.

Done badly, tuning is also how detections quietly die. A filter that is too broad removes the noise and the attack together, and nobody notices because silence looks like success. This post covers how to tune a Sigma rule so the alert count falls without the rule going blind. If you are new to the format itself, Sigma rules explained walks through selections, modifiers and the condition line first.

Why a good rule gets noisy#

Most detection rules describe a behaviour, not a piece of malware. "PowerShell started with a hidden window" is something attackers do constantly, because it keeps a payload off the user's screen. It is also something management agents, installers and monitoring tools do constantly, for exactly the same reason. The rule cannot tell intent apart. Only context can: which program launched it, from which path, under which account, on which machines.

Rule authors know this, which is why a well-written Sigma rule carries a falsepositives list. The specification defines it simply as a list of known false positives that may occur. Read it before you tune. It often names the exact software that is flooding you, and it tells you the author expected this rule to need local work.

Find out what is actually firing#

Never tune from a single alert. Pull a week or two of matches and group them by the fields that describe context, then count. For a process rule on Windows, the useful columns are usually:

  • ParentImage, the full path of the program that started the process.
  • Image, the full path of the process itself.
  • User, and whether it is a service account or a person.
  • Host, and whether the matches cluster on servers, a single department, or everywhere.
  • CommandLine, collapsed to its stable parts if it carries random temporary names.

Often a handful of parent and path combinations account for nearly all of the volume. Those are your tuning candidates. Everything left over after you set them aside is what the rule is for, so look at that remainder closely before you write a single filter. If any of it looks unfamiliar, you may have found something real hiding inside the noise.

On Windows, check which event the rule actually reads, too. A process-creation rule might be fed by Sysmon or by Security event 4688, and the two record different fields. Our Windows Event ID lookup explains what each event records, and Windows event IDs explained covers why the same number can mean different things. That matters when a filter depends on a field one source does not carry.

Filter selections inside the rule#

The simplest way to tune is inside the rule itself. You add one or more selections that describe the legitimate activity, give them names starting with filter, and exclude them in the condition:

title: PowerShell Started With A Hidden Window
id: 4c1d8e2a-7b3f-4e9a-a1d6-0f2e9c5b8a71
status: test
logsource:
  category: process_creation
  product: windows
detection:
  selection:
    Image|endswith: '\powershell.exe'
    CommandLine|contains|windash: '-WindowStyle Hidden'
  filter_inventory_agent:
    ParentImage: 'C:\Program Files\ExampleCorp\Agent\agent.exe'
  filter_software_deploy:
    ParentImage: 'C:\Program Files\ExampleDeploy\deploy.exe'
    User|contains: 'svc_deploy'
  condition: selection and not 1 of filter_*
falsepositives:
  - Inventory and software deployment agents
level: medium

The condition selection and not 1 of filter_* reads as "match the behaviour, unless any filter matches". It is one of the example patterns in the Sigma specification, and the naming convention is what makes it work: every new exception is one more filter_ block, and the condition never changes.

The windash modifier in the example is worth a mention. Windows command-line tools often accept a flag with either - or /, and windash expands the value into those permutations (the specification also includes several Unicode dash characters). Without it, an attacker who types /WindowStyle walks past a rule that only looks for the hyphen.

One condition to avoid is all of them. The specification itself discourages it, because it ANDs together every search identifier in the rule. Add a filter_ block to a rule that uses it and the filter becomes one more thing that must match, which is the opposite of an exclusion. A rule written with named selections can be filtered later without rewriting its logic.

Make every filter as narrow as you can#

A filter is a promise that anything matching it is safe. The question to ask of every filter is: could an attacker make their activity match this too? A few habits keep the answer close to no.

  • Match the full path, not the file name. ParentImage|endswith: '\agent.exe' excludes any program called agent.exe, including one an attacker drops in a user's Downloads folder. The full path under C:\Program Files requires administrative rights to write to on a standard Windows install.
  • Combine fields. Parent path plus service account is much harder to imitate than either alone. Every field you add narrows what the filter forgives.
  • Be wary of filtering on the command line. The command line is the field an attacker controls most completely. A filter like CommandLine|contains: 'inventory' hands them a password: include that word and the alert disappears.
  • Remember that matching is case-insensitive. Sigma treats plain values as case-insensitive strings by default, so a filter catches every capitalisation of a path. Use the cased modifier only when case genuinely matters.
  • Prefer excluding hosts last. Filtering out a whole server group is tempting when it generates the noise, but it also makes those servers a blind spot for this behaviour from anyone.

Keep local exceptions out of shared rules#

Editing a rule you pulled from a shared repository creates a maintenance problem. The next time you update from upstream, you either lose your filters or have to merge them by hand, rule by rule. And when one piece of software trips twenty rules, you end up writing the same filter twenty times.

Sigma 2 added a separate document type for this: the Sigma filter, sometimes called a meta filter. Its purpose, in the specification's words, is to apply the same tuning across many rules for environment-specific false positives. A filter document has its own title, a logsource, and a filter section listing the IDs of the rules it applies to, plus a selection and condition describing the activity to suppress:

title: Filter Inventory Agent
description: Our inventory agent runs hidden PowerShell during scans.
logsource:
  category: process_creation
  product: windows
filter:
  rules:
    - 4c1d8e2a-7b3f-4e9a-a1d6-0f2e9c5b8a71
  selection:
    ParentImage: 'C:\Program Files\ExampleCorp\Agent\agent.exe'
  condition: selection

The upstream rule stays untouched and updates cleanly, and your exceptions live in one place you can review. Filter documents have no level or status, since they only modify other rules. Because they reference rules by id, this approach is one more reason every rule you run should carry a stable one. Support for filter documents depends on your conversion tooling, so confirm that your backend applies them before you rely on them.

When volume is the problem, not the match#

Some rules are noisy because a single event is meaningless while many of them together are not. One failed login is a typo. Two hundred against one host in an hour is something else. Filtering cannot express that, but Sigma correlation rules can.

A correlation rule refers to one or more ordinary rules and adds a relationship. The specification defines four types: event_count (how many matching events), value_count (how many distinct values of a field, such as distinct user names tried), temporal (several rules matching close together) and temporal_ordered (the same, in a set order). Each takes a group-by, a timespan and, for the counting types, a threshold such as gte: 100.

The base rule can then drop to informational, which the specification says should not raise an alert on its own, or low, which it describes as rarely an incident by itself. The correlation carries the alert. One caveat from the specification: some backends can only evaluate fixed time windows, so a burst that straddles two windows can be missed. Set thresholds with some margin.

Record what you changed#

Every filter is a decision someone will need to revisit, often after the person who made it has moved on. Record it where the next analyst will look:

  • Add the excluded software to falsepositives, so the rule documents its own exceptions.
  • Bump modified to the date of the change, in YYYY-MM-DD form.
  • Note why each filter is safe, in a YAML comment or your change history: who owns that software, and why an attacker cannot easily imitate it.
  • Review filters on a schedule. Software gets uninstalled, paths change, and a filter for a product you no longer run is pure blind spot.

If you can, keep a sample of the malicious behaviour the rule exists to catch and confirm it still matches after each round of tuning. A tuned rule that no longer fires on its own test case has been tuned into silence.

Check the rule still loads#

Tuning is mostly editing the detection block, and that is where small mistakes hide. Rename filter_deploy to filter_software_deploy in one place but not the other, and the condition points at something that no longer exists. Mistype |endswith as |endwith and, depending on the backend, the rule may fail to convert or the filter may never match what you meant.

Our Sigma linter catches that class of problem in your browser: a condition naming a selection that is not defined, a selection the condition never uses, and modifiers that are not in the specification's list. It understands patterns like 1 of filter_*, and it checks detection rules rather than filter or correlation documents. It checks structure only. Whether your filter is safe is still the judgement call above, and no linter can make it for you.

Try it

Lint a tuned Sigma rule

Paste a rule after editing its filters and see missing fields, unknown modifiers and broken condition wiring before your pipeline does. The rule never leaves your browser.

Open the Sigma Linter →