Releases: WRG-11/wrg-sigma-rules
Release list
v1.9.0
Corpus 294 to 296 rules with two source-honest canonical templates (T1498 Network Denial of Service, T1056.001 Keylogging), each with matching and non-matching sidecar evidence.
- Sidecars completed for every status:test rule; CI now requires a .sample.json for each.
- Zero-debt linter gate: broad empty matches, unsafe regex, underspecified logsource, scaffolding, deprecated pipe conditions and placeholder false positives are now blocking.
- Cursor and any-MCP-client setup documented next to the Claude Code and Codex plugins.
- README rewritten for a tighter, professional structure.
See CHANGELOG.md for full detail.
v1.8.0 — corpus 278 → 294, sample-match gate
Corpus 278 → 294 rules, seven rule-logic fixes, and a new sample-match gate
wired into CI.
Added
- Corpus 278 → 294 rules (16 new). Two independent sources, measured
separately rather than merged blindly:- 11 from the
sigma_rule_farmerqueue. The queue held 36 candidates
and 25 of them were already in the corpus — the farmer's ownINDEX.json
claimedshipped: 0and was stale, confirmed by a near-identical diff on
barracuda_t1567. Only the 11 genuinely new ones were taken and the 25
overlapping files were left untouched. Actors: Blackwater, Crpxo,
ShinyHunters, TeamPCP/UNC6780, Unknown-supply-chain-2025-03. - 5 from the internal mirror, selected on the same criterion this
changelog used for[1.7.0]: a rule ships when it brings a new actor or
a new technique, not merely a new actor×technique pair. Of 69 mirror-only
rules, exactly 5 met it, carrying five techniques the corpus did not have —
T1021.002, T1036.005, T1199, T1556, T1588. The other 64 map onto actors and
techniques already covered and stay in the queue, which is the same call
[1.7.0]made about its own 80 repeats.
- 11 from the
scripts/sample_match_gate.py— checks that a rule's.sample.json
sidecar actually matches the rule's detection logic, withgt/ltmodifier
andN of <prefix>*support. Wired into.github/workflows/tests.yml, so it
runs on every PR instead of waiting to be invoked by hand.scripts/detection_note_gap.py— reports which rules no detection note
mentions.- 162
.sample.jsonsidecars and 66 detection notes. Every one of the 223
actor-, CVE- and campaign-boundobserved_*rules is now named in at least
one note.
Fixed
- Seven real rule-logic defects, each proven by mutation test: the fix was
reverted, the test went red, the fix was restored. - 29 rules with CVSS or status inconsistencies, corrected against live
gh apiqueries rather than against memory.
Note on the counts
Every number here was re-measured from disk, not incremented: INDEX.json via
migrate_sigma_corpus.py --regenerate-index, README/DEMO/plugin.json via
readme_stamp.py, and DEMO's quoted ## Summary read back off
coverage_resource.coverage_matrix_body().
Corpus 253 → 278: ten new threat actors
Corpus 253 → 278 rules: ten new threat actors and one new technique.
The generation queue held 105 candidate rules. Measured on two axes against the
live corpus, exactly 25 brought something new — ten actors the corpus did not
carry, and one technique (T1567.002). The other 80 mapped onto actors and
techniques already covered, so they stay in the queue.
New actors
barracuda, emperador, genesis, global_secret_group,
iran_nexus_tortoiseshell, kairos, ms13_089, panzer, securotrop,
shai_hulud_npm_worm.
Verified
- Full test suite: 794 passed
- pySigma validation: 25/25
- Corpus quality linter: 25/25 (≤2 warnings each)
- INDEX / README / DEMO counts regenerated from disk
- OPSEC: no victim-level personal data, no real addresses, English only
Also includes the MCP server version-announce fix and Docker startup fix from
the unreleased window.
v1.6.0: corpus 222 to 253, correlation schema migration
31 observed actor rules, each bound to an actor with at least one recorded incident. Test tier 8 -> 39 rules (3.6% -> 15.4%); stable remains 0.
Eight rules moved from deprecated pipe-aggregation to real Sigma correlation documents, so the correlation set is now 18 and Lucene-convertible 235.
Full notes: CHANGELOG.md [1.6.0].
v1.5.0: corpus 100 to 222, AI-runtime + agent-tooling CVE wave
Corpus 100 -> 222 rules across Open WebUI, vLLM, SGLang, Flowise (the corpus's first agentflow/low-code-LLM-builder theme), NLTK, Bedrock AgentCore, NVIDIA Triton, stable-diffusion.cpp, MaxKB, RAGFlow, Gradio, mem0, Ollama, GitLab MCP and the Miasma npm worm cluster. Multi-backend conversion re-measured against the full 222-rule corpus: Splunk and OpenSearch-PPL convert all 222, the Lucene-family targets convert 212 -- the 10 correlation rules are a backend capability gap, not a rule defect. DEMO.md, README.md and plugin.json are all under readme_stamp's reach now, so published counts re-measure instead of drifting. Full entry, including rejected candidates, in CHANGELOG.md.
v1.4.0: corpus 80 to 100, AI-fingerprint detectors, mcp 2.x support
v1.4.0: corpus 80 to 100, AI-fingerprint detectors, mcp 2.x support
Twenty new rules, a class of detector the corpus did not have before, and an
SDK break fixed properly rather than pinned around. The plugin version has read
1.4.0 since this work landed; this tag makes the published release match it.
Added
-
Corpus 80 → 100 rules, in three groups.
Four AI-fingerprint detectors on the
code_reviewlogsource —ai_prose,
unicode_watermark,ai_provenance,hallucinated_import— taking that
logsource from 5 rules to 9. They sit outside the ATT&CK tactic taxonomy on
purpose: they match authorship artifacts in source, not adversary technique,
which is whycode_reviewis a rule directory but never counted as a tactic.Six observed campaigns, each bound to a named, dated incident:
- UNC1069/WAVESHAPER
axiosnpm compromise - TanStack Pwn Request Actions cache poisoning
- SharePoint CVE-2026-58644
w3wpshell spawn - N-able N-central CVE-2026-18577
cloudflaredpersistence - Storm-2949 Azure management-plane credential harvest
keyv/cacheablenpm worm ETH C2
Ten canonical templates: T1082, T1083, T1189, T1195.002, T1204, T1485,
T1546, T1552.004, T1567.001, T1574.Nothing was invented to reach a round number. The corpus stopped at 94 when
cross-verifiable material ran out, and three further candidates were rejected
for single-source or self-contradictory IOCs. T1071.001 was checked and
skipped — its frequency in the source corpus is 0. - UNC1069/WAVESHAPER
-
OpenSearch as a fifth conversion target. Lucene and PPL are separate
targets, and a test asserts they do not silently resolve to the same one. -
Processing pipelines are applied by
convert_rulerather than ignored. An
unknown pipeline name is an error; a missing pipeline package names itself in
the returned envelope instead of raising a bare traceback. -
One new rule for each of the four thinnest ATT&CK tactics, the two correlation
types the templates had not yet used, and a previously-withheld
privilege-escalation rule. -
discoverybecomes the thirteenth ATT&CK tactic covered by the corpus, up
from twelve. The heaviest growth was elsewhere:initial_access17 → 21 and
command_and_control3 → 6. -
Coverage measurement is gated on again, and the Docker image is built and
smoke-tested by speaking MCP to the container over stdio rather than assuming
it starts.
Fixed
-
write_rule_yamldate-regression guard. Found via a real near-miss:
running the corpus migration overwrote an already-deployed, fresher rule
with content re-rendered from a stale test fixture, silently backdating it.
The guard refuses any write that would backdate a deployed rule. A blocked
write means the source needs refreshing, not that the guard should be
bypassed. -
mcp2.0.0 support, properly this time. 1.3.0 responded to the SDK 2.0
break by pinningmcp<2.server.pynow importsmcp.server.MCPServeron
2.x and falls back tomcp.server.fastmcp.FastMCPon 1.x, so both majors
work and CI runs the suite against both instead of excluding one. -
The Docker image shipped without the rule corpus baked in, so both MCP
resources that readresources/examples/answeredok: falseinside the
container even though the same server worked outside it. -
Placeholder
falsepositives:entries (REPLACE_MEand similar) are gone
from the corpus, andvalidate_ruleflags any that reappear.
Verification
pytest -qin CI across six matrix jobs (ubuntu / macos / windows ×mcp<2
andmcp>=2): 419 passed in every one (332 passed, 7 skipped at v1.3.0)- Coverage 92% against the 85% floor in
.coveragerc readme_stamp.py --checkin sync:sigma_rule_count=100,
test_module_count=16- Rule
status:breakdown: 8test, 92experimental, 0stable— read
rule status before
deploying any of these - CodeQL (actions and python), bandit, ruff, dependency-licenses and the Docker
stdio smoke test all clean
Note
The [1.3.0] changelog section understated its own release: it claimed a
corpus of 76 while the tag it names ships 80. The section written for this
release then continued that 76 instead of measuring, so the two agreed with
each other and disagreed with the repository — which is why both survived
review. Both are corrected here, and CHANGELOG.md now carries the guard
README.md already had: a test asserts the newest section's rule count against
the actual corpus, and that each section starts where the previous one ended.
The GitHub release title for v1.3.0 was correct throughout; only the changelog
was wrong.
v1.3.0: corpus 73 to 80, OpenSearch backend, pipeline support
v1.3.0: corpus 73 to 80, OpenSearch backend, pipeline support
Seven new rule templates, a fifth conversion backend, a validator that catches
templates nobody filled in, and an upper bound on every dependency. The plugin
version has read 1.3.0 since this work started; this tag makes the published
release match it.
Added
- Seven rule templates, taking the corpus from 73 to 80 across MITRE
ATT&CK:T1114.001local email collection (OST/PST access)T1110.003password spraying by distinct-account countT1486+T1490ransomware chain, temporal correlationT1021.006remote services over WinRMT1053.005scheduled task created from a scripting hostT1098.003AWS IAM wildcard-admin policy createdT1548.002UAC bypass via an auto-elevating binary
- OpenSearch as a conversion target.
pysigma-backend-opensearchjoins
Splunk, Elastic, Kibana and Wazuh. Lucene and PPL are separate targets, and
a test asserts they do not quietly resolve to the same one. - Sysmon and Windows pipelines.
convert_ruletakes a pipeline name or a
list of them. An unknown pipeline is an error rather than a silent fallback,
and a missing pipeline package returns an envelope naming the package to
install instead of a bare traceback. - The validator now catches an unfilled template. A rule still carrying a
REPLACE_MEmarker, or a placeholder entry infalsepositives, is a rule
that looks complete and detects nothing.validate_rulereports both.
Changed
- Every dependency has an upper bound now.
mcpwas unpinned when
mcp 2.0.0shipped on 2026-07-28 and movedmcp.server.fastmcp. The next
scheduled CI run onmainwould have failed on its own, with or without
this release. It is pinned to<2;pysigmaand the backends are bounded
the same way. - The three skills were updated for the new targets and the pipeline options.
Verification
pytest -qon Python 3.12: 332 passed, 7 skipped (302 at v1.2.1)readme_stamp.py --checkin sync:sigma_rule_count=80,
test_module_count=11- CodeQL (actions and python) clean, and the OPSEC identifier scan clean
Note
Two things were held back on purpose. The coverage-matrix MCP resource is
written and tested, but its module would ship without the server.py
registration that exposes it, so no client could reach the resource; it waits
for that wiring rather than landing as something the release notes could claim
and users could not use. A mirror-integrity test was also excluded: it asserts
a layout constraint that is false in this repository, where server.py
genuinely exists.
v1.2.1 - internal corpus name removed from published content
v1.2.1 — internal corpus name removed from published content
Wording only. No detection rule, tool logic or schema changed; the corpus stays at 73 rules and the test suite passes unchanged. Eleven places in content published from this repository referred to an internal corpus that is not part of it.
Changed
- Three claimed provenance from a private asset a reader cannot inspect or verify — the plugin marketplace description, the
canonical-patternsindex, and the description of thewrg-sigma://patterns/canonical-5MCP resource (wired into the server in v1.2.0, so that string genuinely reaches MCP clients). They now describe what is actually published: a 73-rule corpus across 12 MITRE ATT&CK tactics. - The other eight used the internal name where they meant this corpus — the 73 published rules — in a validation message users see, a skill instruction, two docstrings, a code comment, a test docstring and the pattern index. They now say "this corpus" or "the published corpus": accurate, and unambiguous to a reader outside the project.
migrate_sigma_corpus.py's truncation warning pointed readers at a full source that is not published. It now states that plainly instead.
Verification
- Jargon scan against this checkout: 11 → 0 findings
pytest -q→ 302 passed on Python 3.12 (no test asserted any of these strings)readme_stamp.py --check→ in sync (sigma_rule_count=73,test_module_count=11)
Note
The changelog entry for this release deliberately does not quote the removed term. Writing it out first put the scan count back to 4 — documenting the removal reintroduced exactly what the release removes.
v1.2.0 - corpus 68 to 73 + correlation-rule migration
v1.2.0 — corpus 68 → 73 + correlation-rule migration
The published corpus grew from 68 to 73 rule files (net +5), the 8 rules still using the deprecated pipe-aggregation condition were migrated to real Sigma correlation-rule syntax, and the MCP server is now wired into the plugin. (Per repo convention a release here is a GitHub tag marking a public-corpus milestone — there is no PyPI artifact, and the detection logic is already live on main.)
Added
- Photo ZIP campaign Node.js Run-key persistence rule (68 → 69) — real-incident-grounded registry Run-key detection (#30)
- Four rules via corpus sync (69 → 73): Jellyfin CVE-2026-35033 FFmpeg argument-injection LFI (real observed), plus three MCP database-server SQL-abuse templates — local-file-read, SSRF-to-metadata, read-only write-bypass (#32)
- MCP server wired into the plugin (
.mcp.json) with naming / version / rule-count consistency and a pytest CI gate (#26) - Sigma correlation-rule support in
convert_rule/validate_ruleviaSigmaCollection— base-rule + correlation-rule two-document pairs now parse and convert; single-document rules unchanged (#44) deprecated_pipe_conditionlinter invalidate_rule— flagscondition: X | count() by Y > N in Zm, which is schema-valid but rejected by every pySigma backend at convert time (#44)--regenerate-indexinscripts/migrate_sigma_corpus.py— rebuildsINDEX.jsonfrom the rule files on disk, with a regenerate-vs-committed snapshot test so index drift cannot silently re-accumulate (#42)test_module_countas a second self-stamped README metric alongsidesigma_rule_count.gitignore— this repository previously had none
Changed
- Relabeled three synthetic rules
observed_→template_(plus thewrg.templatetag): these are internal-adversarial-derived scenarios, not real-world-observed incidents, so the prior labelling over-claimed - Migrated the 8 remaining pipe-aggregation rules to correlation-rule syntax — a base document plus an
event_countcorrelation document, withid/title/references/tagspreserved so id-based consumers do not break. Splunk convert verified per rule; Elastic correctly reports it does not support correlation rules (#44) - README — added the
persistencetactic (12th ATT&CK category) and corrected the title to "Claude Code Plugin" (a third-party plugin, not an Anthropic product) (#31) convert_rule— a non-emptyconfigargument was accepted and echoed inconfig_usedbut never applied to backend construction. It now raises a warning instead of silently pretending the config took effectdraft_rulenow emits YAML viayaml.safe_dumpinstead of a hand-rolled emitter (#42)
Fixed
INDEX.jsondrift — regenerated from disk (total_rules68 → 73). Thepersistencetactic was completely unindexed, 3 staleobserved_→template_renames were still listed under their old names, and 5 new rules were missing. The three stale rule-count siblings (plugin.json,resources/canonical-patterns/INDEX.md,DEMO.md) were corrected in the same pass (#42)- Documented-but-unreachable MCP resource —
register_canonical_pattern_resources()was fully implemented and covered by 12 tests, butserver.pynever called it, so the resource URI thatcanonical-patterns/INDEX.mddocumented as a working feature could not be reached by any real MCP client. Now wired in, with a test that imports the real server module and asserts registration (#42) draft_rule— the hand-rolled YAML emitter only quoted problem characters in top-level scalars, so areferencesentry containing:silently re-parsed as a one-key mapping instead of a string (#42)draft_rule— 80-char title truncation cut mid-word with no ellipsis when the description had no period (silent data loss) (#44)validate_rule— a non-stringidfield skipped the schema check entirely; now flagged with a distinct error (#44)validate_rule— a multi-document YAML file no longer forcesvalid=Falsewhen the first document is otherwise clean (fixed on the separate_pysigma_validatepath too) (#42)validate_rule— UUID regex now accepts v6/v7/v8 (RFC 9562) and the nil UUID (#42)- Rule references corrected across six
observed_*rules — real sources and accurate MITRE ATT&CK attribution replacing prior placeholders (#33) - Doc drift — README claimed "8 Python test modules" against an actual 10; that metric is now self-stamped and has since auto-tracked to 11 on its own
Security
validate_ruleYAML denial-of-service. A byte-size cap alone does not stop an alias bomb (billion-laughs): PyYAML resolves aliases to shared object references, so parsing stays fast at any nesting depth and the exponential blowup instead hits downstream code that walks the parsed graph without reference-awareness. Anchor/alias syntax is now rejected outright via a PyYAML composer event hook (not a regex) — Sigma rules have no legitimate use for&anchor/*alias.RecursionErroris handled for deep but alias-free nesting, and the byte-size cap is kept as a separate guard against plain oversized input (#42)- Internal wave-dispatch identifiers and fleet-topology metadata removed from public content, with a regression test blocking them from reaching the public surface (#37, #38, #43)
Known limitations
coverage run -m pytestproduces false failures on this repository — 94 of 287 when the effect was characterised on Python 3.12, matching CI. Root cause is upstream: pysigma'sSigmaYAMLLoader(yaml.CSafeLoader), a C-extension YAML loader subclass in the dependency rather than in this repo's code, breaks specifically under coverage.py's tracer (reproduces withcore=ctraceforced; does not reproduce under a bare no-opsys.settrace;branch=Trueadditionally hangs). CI therefore stays on plainpytestdeliberately — wiring incoverage runas-is would make CI red for reasons unrelated to code quality.
Maintenance / CI
- Dependency bumps:
actions/checkout6.0.3 → 7.0.1 (#28, #46),actions/setup-python6.2.0 → 7.0.0 (#27, #49),github/codeql-action4.36.2 → 4.37.3 (#39, #40, #41, #47, #48, #50),pysigma≥ 1.3.3 → ≥ 1.4.0 (#34),mcp≥ 1.2.0 → ≥ 1.28.1 (#29, #36), plus apysigma-backend-elasticsearchbump (#35) DEMO.mdno longer carries a hard-coded suite pass count — the hand-corrected 286 → 287 fix from this same cycle had already rotted, so the line points at the CI workflow instead. The count cannot be self-stamped the way the rule and module counts are: deriving it requires invoking pytest, andreadme_stamp.pyis deliberately stdlib-only.
v1.1.1 - metadata / hygiene / CI patch
v1.1.1 — metadata / hygiene / CI patch
Corpus rule count unchanged at 68 — no detection rules added or removed. This is a maintenance, repository-hygiene, and CI/dependency patch; the detection logic was already live on main. (Per repo convention a release here is a GitHub tag marking a public-corpus milestone — there is no PyPI artifact.)
Changed
wrg.observedtag added to 4observed_*rules (token type-confusion, audit-log-gap, scanner-crash defense-evasion, GitHub Actions base64 payload) — tag/metadata only, detection logic unchanged (#12)- Rule-description + INDEX context refresh on 7
observed_*rules — nodetection:/logsource:changes (#22) - README refresh — Status section, Quick example, ecosystem links, dead-link cleanup, DEMO repro-path fix
Added
SECURITY.md— private vulnerability disclosure via GitHub Security Advisories (#19)dependabot.yml— dependency monitoring (GitHub Actions + pip)
Removed
- Internal-only draft and self-audit documents removed from the public corpus; marketplace status corrected (#21)
- Removed residual internal-tooling references from corpus files (cleanup)