A detection repository that works for 10 rules often breaks down at 100 rules. Structure it right from the beginning and it scales to 1,000+ without requiring a reorganization. This lesson covers the layout used by mature detection programs and explains why each directory exists.
The Reference Layout
detection-rules/
├── rules/
│ ├── windows/
│ │ ├── process_creation/
│ │ │ ├── proc_creation_powershell_encoded_cmd.yml
│ │ │ ├── proc_creation_lolbin_regsvr32.yml
│ │ │ └── proc_creation_whoami_recon.yml
│ │ ├── registry_event/
│ │ │ └── registry_run_key_persistence.yml
│ │ ├── file_event/
│ │ │ └── file_creation_lsass_dump.yml
│ │ └── process_access/
│ │ └── proc_access_lsass_memory.yml
│ ├── linux/
│ │ ├── process_creation/
│ │ └── auditd/
│ └── cloud/
│ ├── aws/
│ │ └── cloudtrail/
│ └── azure/
│ └── activity_log/
│
├── tests/
│ └── windows/
│ └── process_creation/
│ ├── proc_creation_powershell_encoded_cmd-tp.json
│ └── proc_creation_powershell_encoded_cmd-fp.json
│
├── pipelines/
│ ├── elastic-ecs.yml ← official ECS pipeline (from pySigma)
│ ├── splunk-sysmon.yml ← Sysmon field names for Splunk
│ ├── sentinel-asim.yml ← Microsoft Sentinel ASIM
│ └── custom-splunk-env.yml ← your org's custom field names
│
├── docs/
│ └── windows/
│ └── proc_creation_powershell_encoded_cmd-ADS.md
│
├── .github/
│ └── workflows/
│ ├── pr-checks.yml ← runs on every PR
│ └── deploy.yml ← runs on merge to main
│
├── scripts/
│ ├── convert_all.sh ← bulk sigma convert helper
│ └── validate_ids.py ← check no duplicate rule IDs
│
└── README.md
The rules/ Directory
Organization principle: Match Sigma’s logsource category hierarchy.
rules/{product}/{category}/rule-name.yml
This directly maps to how sigma-cli routes rules:
category: process_creation, product: windows→rules/windows/process_creation/category: cloudtrail, product: aws→rules/cloud/aws/cloudtrail/
Naming convention:
{scope}_{subject}_{specific-behavior}.yml
proc_creation_powershell_encoded_cmd.yml
proc_access_lsass_memory.yml
registry_run_key_persistence.yml
network_connection_powershell_outbound.yml
Long, descriptive names — don’t abbreviate. The file name should tell another engineer what the rule detects without opening it.
The tests/ Directory
Each rule should have at least one TP fixture:
// tests/windows/process_creation/proc_creation_powershell_encoded_cmd-tp.json
[
{
"EventID": 1,
"Image": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
"CommandLine": "powershell.exe -EncodedCommand SGVsbG8gV29ybGQ=",
"ParentImage": "C:\\Windows\\explorer.exe",
"User": "CORP\\testuser",
"ComputerName": "WIN10-TEST",
"UtcTime": "2026-05-20 10:15:32.000"
}
]
And a FP fixture:
// tests/windows/process_creation/proc_creation_powershell_encoded_cmd-fp.json
[
{
"EventID": 1,
"Image": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
"CommandLine": "powershell.exe -Command \"Write-Host 'hello'\"",
"ParentImage": "C:\\Windows\\explorer.exe",
"User": "CORP\\testuser",
"ComputerName": "WIN10-TEST",
"UtcTime": "2026-05-20 10:20:00.000"
},
{
"EventID": 1,
"Image": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
"CommandLine": "powershell.exe -NonInteractive -ExecutionPolicy Bypass -File update.ps1",
"ParentImage": "C:\\Windows\\CCM\\CcmExec.exe",
"User": "CORP\\testuser",
"ComputerName": "WIN10-TEST",
"UtcTime": "2026-05-20 10:25:00.000"
}
]
The CI pipeline runs the converted query against both fixtures and asserts:
- TP fixture → at least 1 result
- FP fixture → 0 results
The pipelines/ Directory
Sigma’s generic field names must be mapped to your SIEM’s actual field names. The pipeline file does this translation:
# pipelines/custom-splunk-env.yml
name: Custom Splunk Environment
priority: 10
transformations:
# Our Sysmon data uses different field names than standard CIM
- id: sysmon_field_mapping
type: field_name_mapping
mapping:
CommandLine: process_command_line # our custom field name
Image: process_image
ParentImage: parent_process_image
User: user_name
rule_conditions:
- type: logsource
product: windows
category: process_creation
# Our events land in a specific index
- id: sysmon_index
type: detection_item_keyword
keyword: "index=corp_sysmon"
rule_conditions:
- type: logsource
product: windows
This pipeline is the most environment-specific file in the repo — it encodes your organization’s actual SIEM configuration. Maintaining it carefully is what makes automated conversion produce working queries.
The docs/ Directory
Each rule has a corresponding ADS document:
docs/windows/proc_creation_powershell_encoded_cmd-ADS.md
The ADS is created as part of the same PR that introduces the rule. Rule + ADS merge together — they’re a unit. The ADS template covers: Goal, Categorization, Strategy Abstract, Technical Context, Blind Spots, False Positives, Validation, Priority, and Response Actions.
Real-World Repository Patterns
Elastic detection-rules
detection-rules/
├── rules/ ← TOML format (not Sigma)
│ └── windows/
│ └── credential_access_lsass_memdump.toml
├── tests/
│ └── data/
│ └── unittest/
├── detection_rules/ ← Python package for rule management
│ ├── cli.py ← CLI: python -m detection_rules ...
│ └── rule_validation.py
└── .github/workflows/
└── test.yml
Uses TOML instead of Sigma — Elastic-specific format that maps directly to Kibana Detection Engine rules. Better for Elastic-only teams; sacrifices portability.
panther-analysis
panther-analysis/
├── aws_cloudtrail_rules/
│ ├── aws_root_activity.py ← Rules written in Python functions
│ └── aws_root_activity_test.py ← pytest-based unit tests
├── gcp_audit_rules/
└── policies/
Panther uses Python for rule logic instead of Sigma YAML. Enables arbitrary logic complexity; sacrifices cross-SIEM portability. Tests are standard pytest — any Python developer can write them.
SigmaHQ (community standard)
sigma/
├── rules/
│ └── windows/
│ └── process_creation/
│ └── proc_creation_win_powershell_encoded_cmd.yml
├── tests/
└── tools/ ← Legacy; sigma-cli is the modern tool
The reference implementation. All rules in Sigma YAML. CI validates schema, checks for duplicate IDs, and runs sigma-cli conversion to verify all backends work.