# Writing Sigma Rules

> Author and maintain Sigma detection rules — structure, logsource taxonomy, detection logic with modifiers, false-positive filtering, backend conversion with pySigma, and offline validation with Hayabusa or Chainsaw. Use when translating threat intel into vendor-agnostic detection logic, building a detection-as-code pipeline around Sigma, reviewing or tuning existing Sigma rules, or converting rul…

- **Type:** Skill
- **Install:** `agentstack add skill-evilfreelancer-secs-writing-sigma-rules`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [EvilFreelancer](https://agentstack.voostack.com/s/evilfreelancer)
- **Installs:** 0
- **Category:** [AI & ML](https://agentstack.voostack.com/c/ai-and-ml)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [EvilFreelancer](https://github.com/EvilFreelancer)
- **Source:** https://github.com/EvilFreelancer/secs/tree/main/.agents/skills/writing-sigma-rules

## Install

```sh
agentstack add skill-evilfreelancer-secs-writing-sigma-rules
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Writing Sigma Rules

Sigma is the common language for detection logic -- write once, convert to any
SIEM. The value is portability and reviewability, but only if the rule is
precise: a Sigma rule that matches everything is worse than no rule, because it
consumes analyst time and trains the team to ignore alerts. The work is
specificity without brittleness.

## When to Use

- Translating a TTP, threat intel report, or incident finding into a portable detection rule
- Writing vendor-agnostic detection logic that converts to multiple SIEM backends
- Building or maintaining a detection-as-code pipeline centered on Sigma
- Reviewing or tuning existing Sigma rules for precision and false-positive reduction
- Converting Sigma rules between backends (Splunk, Elastic, Sentinel, CrowdStrike, Chronicle)
- Contributing rules upstream to SigmaHQ or maintaining a private rule repository

## When NOT to Use

- **Broader detection engineering including YARA, Suricata, and the full detection lifecycle** -- use `engineering-detections`
- **The signature belongs on files or memory, not log telemetry** -- use `writing-yara-rules`
- **Hypothesis-driven threat hunting, not rule writing** -- use `hunting-threats`
- **Active incident response, not rule development** -- use `responding-to-incidents`

## Sigma Rule Structure

Every rule is a YAML document with these fields in order:

**The specification requires only three fields:** `title`, `logsource`, and
`detection` (which must contain a `condition`). Everything else is optional
*to the spec*. Do not confuse that with the SigmaHQ rule repository, which
requires far more — `id`, `status`, `description`, `author`, `date`, `level`,
and ATT&CK `tags` — before it will accept a contribution. Write to the stricter
SigmaHQ convention by default, because a rule without an `id` or a `level` is
unmanageable in a real pipeline, but know which constraint you are meeting when
a converter accepts a rule your reviewer rejects.

```yaml
title: Short Descriptive Name              # REQUIRED by spec, max ~100 chars
id: a1b2c3d4-0000-4000-8000-000000000001  # optional to spec; UUIDv4, never reuse
related:                                    # optional, link to predecessor rules
  - id: 
    type: derived | obsoletes | merged | renamed | similar
status: experimental                        # optional; see lifecycle below
description: >                              # optional to spec, expected by SigmaHQ
  Detects X behaviour consistent with Y technique.
references:                                 # optional, strongly recommended
  - https://attack.mitre.org/techniques/T1059/001/
author: Your Name                           # optional to spec
date: 2026-07-26                            # optional; ISO 8601 YYYY-MM-DD
modified: 2026-07-26                        # optional; same format, on update
tags:                                       # optional to spec, expected by SigmaHQ
  - attack.execution                        #   tactic (lowercase, dotted)
  - attack.t1059.001                        #   technique (lowercase)
logsource:                                  # REQUIRED by spec
  category: process_creation
  product: windows
detection:                                  # REQUIRED by spec
  selection:
    CommandLine|contains: 'some-indicator'
  condition: selection                      # REQUIRED inside detection
falsepositives:                             # optional to spec
  - Legitimate admin scripts using the same flag
level: medium                               # optional; informational|low|medium|high|critical
```

**Date format.** The spec mandates ISO 8601 with hyphens — `YYYY-MM-DD`. Older
rules and much online material use the legacy `YYYY/MM/DD`; that form is
outdated, and some tooling now rejects it.

**Status lifecycle.** The permitted values are `experimental`, `test`,
`stable`, `deprecated`, and `unsupported`. In practice: `experimental` =
observation only, no alerting. `test` = validated against emulation, ready for
limited deployment. `stable` = tuned in production with documented FPs.
`deprecated` = replaced, no longer accurate. `unsupported` = not usable as
written (e.g. depends on homemade fields). Never promote without the
corresponding work.

## Logsource Taxonomy

The `logsource` block abstracts the data source. Specify only what is needed.

**Categories:** `process_creation`, `file_event`, `file_access`,
`network_connection`, `registry_event`, `dns_query`, `image_load`,
`pipe_created`, `process_access`, `driver_load`, `create_remote_thread`.

**Product:** `windows`, `linux`, `macos`.

**Service:** `sysmon`, `security`, `system`, `powershell`, `powershell-classic`,
`application`, `taskscheduler`, `windefend`, `firewall-as`.

Use `category` when you care about the event type regardless of collection
method. Use `product` + `service` when targeting a specific log channel.
Do not combine `category` and `service` unless the backend requires it.

## Detection Logic

### Selection and filter pattern

Define what to match (`selection`), what to exclude (`filter_*`), combine in
`condition`:

```yaml
detection:
  selection:
    ParentImage|endswith: '\explorer.exe'
    CommandLine|contains|all:
      - 'powershell'
      - '-enc'
  filter_legitimate:
    CommandLine|contains: 'company-deploy-script'
    User|startswith: 'SVC_'
  condition: selection and not filter_legitimate
```

### Condition syntax

```
condition: selection                           # simple match
condition: selection and not filter            # match minus exclusions
condition: selection1 or selection2            # either pattern
condition: (sel1 and sel2) and not (fp1 or fp2)
condition: all of selection*                   # all blocks starting with "selection"
condition: 1 of selection*                     # any one block
```

### Keywords

Match against the full log event (all fields). Blunt instrument -- prefer
field-specific selections for production rules:

```yaml
detection:
  keywords:
    - 'Invoke-Mimikatz'
    - 'sekurlsa::logonpasswords'
  condition: keywords
```

### Modifiers

Chain with `|`. Example: `CommandLine|contains|all`.

| Modifier | Effect |
| --- | --- |
| `contains` | Substring match |
| `startswith` / `endswith` | Prefix / suffix match |
| `re` | Regular expression (PCRE) |
| `base64offset` | Match base64-encoded variants at all three offsets |
| `all` | All list values must match (default is any) |
| `cidr` | CIDR network range match on IP fields |
| `windash` | Match both `-` and `/` as argument prefix |
| `expand` | Expand environment variables like `%SystemRoot%` |
| `wide` / `utf16le` / `utf16be` / `utf16` | Match the wide (UTF-16) encoding of the value; `wide` is an alias for `utf16le`. There is no `utf8` modifier |
| `exists` | Field present (true) or absent (false) |

## Common Detection Patterns

### Process creation -- suspicious command line

```yaml
detection:
  selection:
    CommandLine|contains|windash|all: ['bypass', 'hidden', 'noprofile']
  filter_admin:
    ParentImage|endswith: ['\sccm.exe', '\intune_agent.exe']
  condition: selection and not filter_admin
```

### Parent-child relationship (Office spawning shell)

```yaml
detection:
  selection:
    ParentImage|endswith: '\winword.exe'
    Image|endswith: ['\cmd.exe', '\powershell.exe', '\wscript.exe', '\mshta.exe']
  condition: selection
```

### File creation in suspicious paths

```yaml
detection:
  selection:
    TargetFilename|contains: ['\AppData\Local\Temp\', '\ProgramData\', '\Users\Public\']
    TargetFilename|endswith: ['.exe', '.dll', '.scr', '.hta']
  condition: selection
```

### Registry persistence (run keys)

```yaml
detection:
  selection:
    TargetObject|contains: ['\CurrentVersion\Run\', '\CurrentVersion\RunOnce\']
    EventType: SetValue
  filter_installers:
    Image|startswith: 'C:\Windows\Installer\'
  condition: selection and not filter_installers
```

### Named pipe creation (C2 indicators)

```yaml
detection:
  selection:
    PipeName: ['\MSSE-*', '\postex_*', '\msagent_*', '\status_*']
  condition: selection
```

### WMI event subscription and scheduled task creation

WMI: logsource `product: windows`, `service: sysmon`, match `EventID: 21`,
`Operation: Created`. Scheduled tasks: logsource `category: process_creation`,
match `Image|endswith: '\schtasks.exe'` with `CommandLine|contains|all:
['/create', '/sc']`, filter on SYSTEM + known management tools.

## False Positive Handling

### The filter pattern

Exclusions go in named `filter_*` blocks, never inline with the selection.
Use `condition: selection and not 1 of filter_*` to apply all filters.

### Known-good exclusions

Filter on properties the attacker cannot control: full file paths of signed
vendor binaries (not filenames alone), service account SIDs (not usernames),
parent-child pairs from specific software workflows, verified certificate
subjects. Never filter on filenames alone, attacker-controllable command-line
fragments, or hostnames without justification.

### Severity calibration

| Level | Response expectation |
| --- | --- |
| `informational` | Automated tagging, correlation input only |
| `low` | Batch review, daily triage |
| `medium` | Analyst queue, investigate within hours |
| `high` | Prompt investigation, likely malicious |
| `critical` | Immediate response, active compromise |

Set level based on expected TP rate and business impact, not on how dangerous
the technique sounds. A noisy `critical` rule causes more damage than a
precise `medium` one.

### Correlation rules

When a single event is too common to alert on, use Sigma correlation rules
that reference other rules by ID, group by a field (e.g., `ComputerName`),
and require a threshold within a time window:

```yaml
title: Correlation - Multiple Suspicious Events from Same Host
type: correlation
rules:
  - id: 
  - id: 
group-by: [ComputerName]
timespan: 15m
condition:
  gte: 2
level: high
```

## Backend Conversion

### sigma-cli with pySigma

```bash
pip install sigma-cli pySigma-backend-splunk pySigma-backend-elasticsearch \
  pySigma-backend-kusto pySigma-backend-qradar

sigma convert -t splunk -p sysmon rules/rule.yml
sigma convert -t elasticsearch -p ecs_windows rules/rule.yml
sigma convert -t kusto -p microsoft_xdr rules/rule.yml
sigma convert -t qradar rules/rule.yml
sigma convert -t splunk -p sysmon rules/                     # entire directory
sigma convert -t splunk -p sysmon -f savedsearches rules/    # output format
```

### Pipeline selection

| Backend | Common pipelines |
| --- | --- |
| Splunk | `sysmon`, `splunk_windows`, `splunk_cim` |
| Elastic | `ecs_windows`, `ecs_zeek`, `filebeat` |
| Sentinel/XDR | `microsoft_xdr`, `azure_monitor` |
| CrowdStrike | `crowdstrike` |
| Chronicle | `chronicle_default` |

Always verify converted output against your actual field names. Pipeline
defaults may not match custom parsing configurations.

## Testing and Validation

### Schema validation

```bash
sigma check rules/rule.yml       # single rule
sigma check rules/               # entire directory
```

Common failures: missing or duplicate `id`, invalid `level`, malformed YAML,
undefined modifier, empty detection block.

### Offline validation against EVTX

```bash
hayabusa csv-timeline -d ./sample_evtx/ -r rules/rule.yml
chainsaw hunt ./sample_evtx/ -s rules/rule.yml --mapping mappings/sigma-mapping.yml
```

### Testing workflow

1. **Collect sample logs.** Run the technique in a lab (Atomic Red Team,
   Caldera) and capture EVTX or JSON.
2. **Verify detection.** Run Hayabusa/Chainsaw -- rule must fire on the TP log.
3. **Verify exclusion.** Run against clean baseline. Every hit characterized.
4. **Convert and test.** Run the backend query against 7+ days of production
   data. Characterize every match.
5. **Document.** Record TP/FP counts, FP causes, filters added. Update the
   rule's `falsepositives` field and the PR description.

## Quality Standards

**SigmaHQ requirements:** valid UUIDv4 `id`; `status` set appropriately
(`experimental` for new); `date`/`modified` in `YYYY/MM/DD`; at least one
ATT&CK tag; non-empty `falsepositives` (even `Unknown`); `level` based on TP
rate; descriptive `description`; `references` linking to source intel.

**YAML formatting:** two-space indent, no tabs; pipe-separated modifiers
without spaces (`field|contains|all`); dash-space lists; single-quoted strings
with special characters; folded scalar (`>`) for long descriptions; one rule
per file, snake_case filename matching the title.

**Field naming:** use Sigma standard names (`Image`, `ParentImage`,
`CommandLine`, `User`, `TargetFilename`, `TargetObject`, `DestinationIp`,
`DestinationPort`, `SourceIp`, `PipeName`, `Hashes`). Backend conversion
handles translation. Writing backend-specific field names defeats portability.

**Tag compliance:** `attack.` (lowercase, hyphenated) and
`attack.t` (lowercase, dotted sub-technique). Add `cve.YYYY.NNNNN`
when applicable. Verify IDs against the current ATT&CK release -- stale IDs
from retired techniques create mapping errors downstream.

## Rationalizations to Reject

- *"The rule is simple enough, it does not need testing."* Simple rules have
  the widest match surface. The simpler the logic, the more important the
  FP analysis.
- *"We will add filters after it goes live."* Every hour a noisy rule runs
  in production erodes analyst trust. Test and filter before deployment.
- *"Just use keywords, field-specific matching is too narrow."* Keywords match
  across all fields. A hit on a hostname that contains the string is noise.
- *"The converted output looks right, no need to test it."* Conversion assumes
  your field mappings match pipeline defaults. Verify against actual data.
- *"One rule per technique is enough."* Techniques have many procedures. T1059
  covers PowerShell, cmd, bash, Python, VBScript -- each needs its own logic.
- *"The rule works in Splunk, so it works everywhere."* Portability requires
  correct logsource abstraction. Backend-specific field names break it.
- *"Set it to critical -- credential dumping is always critical."* Severity
  reflects signal quality, not technique category. A noisy critical rule
  causes more damage than a precise medium one.

## Reading External Sources

Fetch public advisories, specifications, and vendor reports as Markdown:

```bash
curl -sL "https://defuddle.md/"      # scheme in the path is optional
```

This strips page boilerplate — roughly 78% fewer tokens on a prose page — and
returns the full text rather than a summary, so you can grep it and trust a
negative result.

Three things it is not for. Fetch JSON and API responses raw, because
readability extraction mangles structured data. Fetch authenticated or
JavaScript-rendered pages directly, because it retrieves them anonymously. And
never route **adversary infrastructure** (phishing links, C2, malware hosting),
**client-owned hosts**, or **engagement URLs** through it — the request leaves
your machine to a third party, and for live adversary infrastructure it also
tips off the operator.

Some sites block the extractor and return an error blob rather than the page —
`{"error":"Failed to fetch: 418 I'm a teapot"}` from freedesktop.org, for
instance. That is the fetch being refused, **not** the source saying the thing
does not exist. Re-fetch the URL directly before drawing any conclusion from
it.

## References

- `engineering-detections` -- the full detection lifecycle including YARA, Suricata, and coverage measurement
- `hunting-threats` -- hypothesis-driven hunting that produces the findings rules are built from
- `mapping-attack-techniques` -- ATT&CK technique resolution and the purple-team loop
- `reporting-security-findings` -- writing up what the detection found

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [EvilFreelancer](https://github.com/EvilFreelancer)
- **Source:** [EvilFreelancer/secs](https://github.com/EvilFreelancer/secs)
- **License:** Apache-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-evilfreelancer-secs-writing-sigma-rules
- Seller: https://agentstack.voostack.com/s/evilfreelancer
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
