In May 2026, Microsoft's Semantic Kernel shipped two CVEs in the same week. CVE-2026-26030 let an attacker chain a lambda expression into arbitrary code execution through an AI agent's tool-call handling. The fix landed inside 48 hours because a detection rule already existed for it, an open, community-maintained standard called Agent Threat Rules (ATR) had a rule live and merged into Microsoft's own Agent Governance Toolkit within hours of the CVE disclosure.
ATR has grown fast. 680+ rules, adopted into Microsoft's Agent Governance Toolkit, Cisco AI Defense, MISP, and cross-listed by SigmaHQ as a sibling format to Sigma itself. Think Sigma for SIEM detection, YARA for malware signatures, ATR for AI agent threats: prompt injection, tool poisoning, context exfiltration, skill supply-chain attacks.
Here's what stopped me: Google Security Operations (Chronicle) had no way to use any of it.
The gap was already documented, just unsolved
I went looking and found an open issue on Google's own google/mcp-security repository, someone had asked for exactly this: a way to pull external rule packs like ATR into Chronicle through the SecOps MCP server. The issue laid out the shape of the tool needed. Nobody had built the piece it depended on: something that actually translates ATR's rule format into Chronicle's detection language, YARA-L.
So I built it.
What atr-to-yaral does
It's a small Python tool. You point it at an ATR rule:
yaml
title: Direct Prompt Injection via User Input
id: ATR-2026-00001
severity: high
detection:
condition: any
conditions:
- field: content
operator: regex
value: "(?i)ignore\\s+(all\\s+)?(previous|prior|above)\\s+instructions"
- field: content
operator: regex
value: "(?i)disregard\\s+(your|the)\\s+system\\s+prompt"
And it produces a working YARA-L 2.0 rule:
rule ATR_2026_00001 {
meta:
atr_id = "ATR-2026-00001"
title = "Direct Prompt Injection via User Input"
severity = "HIGH"
mitre_atlas = "AML.T0051 - LLM Prompt Injection"
source = "Converted from Agent Threat Rules (ATR)"
events:
$e.metadata.description = /(?:(?i)ignore\s+(all\s+)?(previous|prior|above)\s+instructions)|(?:(?i)disregard\s+(your|the)\s+system\s+prompt)/ nocase
condition:
$e
}
Point it at a whole directory of ATR rules and it batch-converts everything it can, skipping and reporting anything outside its current scope rather than failing the whole run.
The honest limitation, stated up front
ATR rules match against LLM input, tool-call arguments, and SKILL.md content. Chronicle's UDM has no standard field for any of that yet, because there's no standard way to ingest AI agent telemetry into Chronicle today. That's a real, unsolved problem industry-wide, not a gap in this tool specifically.
The converter defaults to a placeholder UDM field and makes you configure the real one for your ingestion pipeline. I'd rather ship something that says plainly what it doesn't know than something that looks finished and quietly fires on the wrong field forever.
For the deployment step, once you have .yaral files, I pointed the README at Google's own chronicle/detection-rules tool, content_manager, which validates and pushes rules into a live Chronicle instance. The full path looks like this:
ATR YAML rules → atr-to-yaral → .yaral files → content_manager → live in Chronicle
What's next
I opened an integration request on ATR's own repository, and I'm planning to comment on the original Google issue once there's more real-world usage to point to. If you run Chronicle and want to try converting a batch of ATR rules against your own pipeline, I'd genuinely like to hear what breaks.
Repo: https://github.com/ajaynyayapathi/ATR-to-Yara-L-Converter
Currently supports regex-based ATR conditions (the majority of the published rule set) with both AND and OR condition logic. Non-regex operators and multi-event correlation rules are documented as out of scope for now, not silently dropped.
Top comments (3)
The part that matters most here is not the converter, it is the field gap you put up front: Chronicle's UDM has no standard place to carry a tool call, its arguments, or the SKILL.md content an agent just loaded. Mapping to
metadata.descriptiongets a rule to green, but a rule that reads a field nobody populates looks identical to a rule that never matched.Have you thought about how to prove the negative case — a converted rule that fires zero times for a reason, versus fires zero times because it is correct? We log agent tool calls on our side and the escaping is the other quiet failure: ATR regexes are Go-flavoured and YARA-L regex syntax is close enough that
(?i)and\stranslate, but not far enough to catch a rule that silently degrades to a substring match.Good catch, and worth being precise about both.
On the dialect question: Go's regexp and Chronicle's re.regex are both RE2, so common constructs like (?i) and \s really do translate identically, that part isn't a false sense of safety. Where it breaks is backreferences and lookaround, unsupported in RE2 either side, but nothing in the converter currently checks for them before emitting a rule. A pattern that assumes PCRE semantics could silently produce a rule that compiles but never matches what the author intended. I'll add a preflight check that flags those constructs explicitly instead of passing them through.
On proving the negative: you're right that this doesn't exist yet, and it should. The honest fix is a canary step, using each ATR rule's test_cases.true_positives to generate a synthetic matching event and confirm the converted rule actually fires against it before anyone trusts "zero hits" from it in production. That turns an assumption into a tested claim. I'll scope that as the next addition.
If you're logging agent tool calls already, I'd genuinely like to know what field structure you landed on, that's exactly the kind of real-world mapping the README says is missing.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.