Skip to content

v1.4.0: corpus 80 to 100, AI-fingerprint detectors, mcp 2.x support

Choose a tag to compare

@WRG-11 WRG-11 released this 05 Aug 01:27
· 24 commits to main since this release

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_review logsource — 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 why code_review is a rule directory but never counted as a tactic.

    Six observed campaigns, each bound to a named, dated incident:

    • UNC1069/WAVESHAPER axios npm compromise
    • TanStack Pwn Request Actions cache poisoning
    • SharePoint CVE-2026-58644 w3wp shell spawn
    • N-able N-central CVE-2026-18577 cloudflared persistence
    • Storm-2949 Azure management-plane credential harvest
    • keyv/cacheable npm 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.

  • 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_rule rather 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.

  • discovery becomes the thirteenth ATT&CK tactic covered by the corpus, up
    from twelve. The heaviest growth was elsewhere: initial_access 17 → 21 and
    command_and_control 3 → 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_yaml date-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.

  • mcp 2.0.0 support, properly this time. 1.3.0 responded to the SDK 2.0
    break by pinning mcp<2. server.py now imports mcp.server.MCPServer on
    2.x and falls back to mcp.server.fastmcp.FastMCP on 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 read resources/examples/ answered ok: false inside the
    container even though the same server worked outside it.

  • Placeholder falsepositives: entries (REPLACE_ME and similar) are gone
    from the corpus, and validate_rule flags any that reappear.

Verification

  • pytest -q in CI across six matrix jobs (ubuntu / macos / windows × mcp<2
    and mcp>=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 --check in sync: sigma_rule_count=100,
    test_module_count=16
  • Rule status: breakdown: 8 test, 92 experimental, 0 stable — 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.