YARA-L 2.0 Quick-Start Notes
Comprehensive notes for Google SecOps Analysts generated by Claude Opus 4.6
As an incident response engineer I have a background in using Splunk and the Splunk Pipeline (SPL) to search logs and write detections, and using YARA to hunt for malware samples. Now I want to lean more about Google Security Operations and it’s query language, YARA-L, but I found that information was scattered on too many different pages for my liking, so I turned to Claude to create organized notes. I found many mistakes in the notes generated by the Claude Sonnet 4.6 model, even with Extended Thinking turned on, so I switched to the larger Claude Opus 4.6 model with Extended Thinking turned on, and gave it this prompt:
Create notes on YARA-L in markdown format, with comparisons to both classic YARA and SPL, so that analysts new to SecOps can quickly gain an understanding of how YARA-L is different. Use the Google documentation to ensure that everything you say is accurate. Explain every rule section, both required and optional, how it is used, and when it is required or optional. Give examples of how in all the different ways variables can be used in rules. Include details on all of the modifiers such as nocase. Include at least one example with regex. Add a references section with links to resources at the bottom of the notes.
Even with the bigger model, it took several rounds of me calling out inaccurate information. I hope the notes below are as useful for you as they are for me. Let me know if you find any other issues that need fixing.
What Is YARA-L 2.0?
YARA-L 2.0 is the detection and search language for Google Security Operations (formerly Chronicle). It is derived from VirusTotal’s classic YARA language but is purpose-built for searching, correlating, and alerting on enterprise log data that has been normalized into Google’s Unified Data Model (UDM).
If you’re coming from Splunk (SPL) or from writing classic YARA rules for malware, the table below will orient you fast.
At a Glance: YARA-L vs. Classic YARA vs. SPL
| Aspect | Classic YARA | SPL (Splunk) | YARA-L 2.0 |
|---|---|---|---|
| Primary purpose | File/binary pattern matching | Log search, stats, and dashboards | Log search, correlation rules, and dashboards |
| Operates on | Files and memory buffers | Index-time and search-time fields | UDM-normalized events and entity graphs |
| Structure | strings + condition sections | Pipe-chained commands (\| search \| stats) | Declarative sections: meta, events, match, outcome, condition, options |
| Multi-event correlation | Not supported | Requires subsearches or transactions | Native — join multiple event variables in events and set a time window in match |
| String modifiers | nocase, wide, ascii, fullword, xor, base64 | N/A (use lower() / upper() functions) | nocase modifier on comparisons and regex |
| Regex | /regex/ in strings section | \| regex field="..." command | Inline /regex/ literals or re.regex() function |
| Aggregation | Count of string hits (#string_name) | \| stats count, sum, avg ... | outcome section with count(), sum(), min(), max(), count_distinct(), array_distinct() |
| Variables | $string_name in strings | Field names directly | $variable_name — event, match, placeholder, and outcome variables |
| Grouping | N/A | by clause in stats | match section ($var over <time>) |
Rule Structure Overview
Every YARA-L rule is wrapped in a rule <name> { ... } block. The sections must appear in the following order. Required sections are marked with (R).
rule <rule_name> {
meta: // (R) – key-value metadata (required for rules; not used in search/dashboards)
events: // (R) – defines event filters, variables, and joins
match: // Optional – grouping keys and time window
outcome: // Optional – computed output fields per detection
condition: // (R) – Boolean logic that triggers the rule
options: // Optional – runtime behavior flags
}
Classic YARA comparison: A classic YARA rule has only meta (optional), strings (optional), and condition (required). In YARA-L, meta is required for rules. There is no equivalent to events, match, outcome, or options.
SPL comparison: SPL doesn’t use sections at all. It chains commands with pipes. The YARA-L events section is roughly analogous to the initial search command, match is like by in stats, outcome is like the field calculations inside stats or eval, and condition is like a where filter on the results.
Section-by-Section Breakdown
1. meta (Required for Rules)
Stores arbitrary key-value pairs describing the rule. The meta section is required for detection rules and must appear at the start of the rule. It is not used in search or dashboard queries. The key-value pairs have no effect on detection logic but are essential for documentation, triage, and operational workflow.
meta:
author = "soc-team@example.com"
description = "Detects brute-force SSH login attempts"
severity = "HIGH"
yara_version = "YL2.0"
rule_version = "1.0"
When to use: Required in every rule. Every production rule should have at least author, description, and severity. Values from meta can be surfaced in alerts and used for context during triage.
Classic YARA comparison: Classic YARA also has a meta section with the same key = value syntax, but in classic YARA it is optional. In YARA-L, it is required for rules.
SPL comparison: SPL has no built-in metadata. You’d document rules in an external knowledge base or as comments.
2. events (Required)
This is the core of the rule. It defines which UDM events to look at, assigns event variables, sets field conditions, and establishes joins between events.
events:
$e.metadata.event_type = "USER_LOGIN"
$e.security_result.action = "FAIL"
$e.principal.user.userid = $user
When to use: Always required. A rule with no events section won’t compile.
Key concepts:
- Event variables (
$e,$e1,$e2, etc.) represent groups of normalized UDM events. - Field paths chain downward from UDM:
$e.principal.hostname,$e.target.ip,$e.network.email.from. - The default data source is
udm(normalized events). You can also usegraphfor entity context, but this requires a separate event variable (e.g.,$context.graph.entity.hostname). A single variable cannot be bothudmandgraph. - Logical operators
and,or,notwork inside the events section.andis implied between separate lines. - You can group conditions with parentheses for
orlogic.
SPL comparison: The events section is analogous to the initial search or where filter in SPL. But where SPL operates on raw or CIM-mapped fields, YARA-L always operates on UDM-normalized fields.
Classic YARA comparison: This replaces the strings section. Classic YARA defines byte patterns to match inside files; YARA-L defines field conditions to match against log events.
3. match (Optional)
Declares grouping variables and a required time window. One row is returned per unique combination of match variable values within each time window.
match:
$user over 5m
You can match on multiple variables:
match:
$user, $src_ip over 10m
When to use: Any time you need to aggregate or correlate events over a time period. If you use a match section, you must include a time window (e.g., 5m, 1h, 1d).
Sliding windows: Use the after keyword to create a sliding window relative to a pivot event:
match:
$host over 10m after $e1
Zero value handling: The Rules Engine automatically filters out zero/empty values for match variables by default. Use the allow_zero_values option to disable this behavior.
SPL comparison: Equivalent to the by clause in stats ... by user combined with a timewindow or earliest/latest constraint.
Classic YARA comparison: No equivalent. Classic YARA processes one file at a time and has no concept of time windows or grouping.
4. outcome (Optional)
Defines computed output variables attached to each detection. These are available downstream for alert enrichment, severity scoring, SOAR playbooks, and conditional logic.
outcome:
$failed_count = count($e.metadata.id)
$first_fail = min($e.metadata.event_timestamp.seconds)
$risk_score = if($failed_count > 20, 90, 50)
Supported aggregate functions include: count(), count_distinct(), sum(), avg(), min(), max(), stddev(), array(), array_distinct().
You can also use conditional if() expressions and reference the outcome variables in the condition section.
When to use: Whenever you need to surface extra context in detections or make the condition section dependent on aggregated values (e.g., severity thresholds).
SPL comparison: This is like the calculated fields inside a stats command or an eval in SPL.
Classic YARA comparison: No equivalent.
5. condition (Required)
The final Boolean gate. The rule fires a detection only when this evaluates to true.
condition:
#e >= 5
Key syntax:
$e— checks for existence of at least one matching event.#e— the count of distinct events matching theeventssection. Use this for thresholds:#e >= 5.#placeholder— the count of distinct values a placeholder variable took. For example,#user > 3means more than 3 distinct users.!$e2— negation; true when event variable$e2has no matching events.- You can combine event/placeholder conditions with
and,or, and outcome variable conditions.
Outcome conditionals: You can add conditions on outcome variables using and:
condition:
#e >= 5 and $risk_score > 70
SPL comparison: Similar to a where filter after a stats command.
Classic YARA comparison: The classic YARA condition section is conceptually the same — a Boolean expression that must be true for the rule to match. However, classic YARA conditions reference string hits ($string_name, #string_name, @string_name) and file properties, while YARA-L conditions reference event and placeholder variables.
6. options (Optional)
Runtime configuration flags that control rule execution behavior.
options:
allow_zero_values = true
Available options:
| Option | Description |
|---|---|
allow_zero_values | When true, match variables will not automatically filter out zero/empty values. Default is false. |
suppression_window | Suppresses duplicate detections for a specified duration after the first match. Works for both single-event and multi-event rules. For single-event rules, define $suppression_key in outcome as the deduplication key. For multi-event rules, the match variables serve as the deduplication key. |
Suppression example (single-event rule):
outcome:
$suppression_key = $hostname
options:
suppression_window = 5m
After a detection fires for a given hostname, further detections for that same hostname are suppressed for five minutes.
Suppression example (multi-event rule):
match:
$hostname, $user over 1h
options:
suppression_window = 24h
After a detection fires for a given hostname and user combination, further detections for that same combination are suppressed for 24 hours. The suppression_window should be greater than or equal to the match window size.
When to use: When you need to override default zero-value filtering, or when you want to reduce alert noise for high-volume rules.
SPL comparison / Classic YARA comparison: Neither has a direct equivalent. In SPL you’d handle deduplication with dedup or throttling settings outside the query. Classic YARA has global and private rule modifiers which are conceptually different.
Variables in Depth
YARA-L has four types of variables, all prefixed with $: event, match, placeholder, and outcome.
Event Variables
Represent one or more events or entities from the data. You access fields through dot-notation chains. Event variables have two possible sources: udm (normalized events, the default) and graph (entity context from the entity graph). If the source is omitted, udm is assumed.
events:
// Single event variable (implicitly udm source)
$e.metadata.event_type = "PROCESS_LAUNCH"
$e.principal.hostname = "web-server-01"
// Multiple event variables for correlation
$login.metadata.event_type = "USER_LOGIN"
$download.metadata.event_type = "FILE_CREATION"
$login.principal.user.userid = $download.principal.user.userid
// Event variable with graph source for entity enrichment
$context.graph.entity.hostname = $host
$context.graph.entity.asset.vulnerabilities.severity = "HIGH"
When you reference the same event variable across multiple lines, the conditions are implicitly ANDed.
Match Variables
Declared in the events section by assigning an event field to a new variable name, then referenced in the match section as a grouping key.
events:
$e.metadata.event_type = "USER_LOGIN"
$user = $e.target.user.userid // $user becomes a match variable
match:
$user over 5m
Placeholder Variables
Also declared via assignment in events. They link fields across event variables (acting as join keys) and can be counted in condition with #.
events:
$e1.metadata.event_type = "USER_LOGIN"
$e2.metadata.event_type = "FILE_CREATION"
$e1.principal.user.userid = $userid
$e2.principal.user.userid = $userid // joins $e1 and $e2 on the same user
$e1.principal.ip = $src_ip
match:
$userid over 10m
condition:
$e1 and $e2 and #src_ip > 1 // user logged in from more than 1 IP
Outcome Variables
Declared in the outcome section. They define per-detection output values using aggregations, conditionals, or simple assignments. Outcome variables appear in detection results and can be used in condition for threshold logic.
outcome:
$login_count = count($e.metadata.id)
$distinct_ips = count_distinct($e.principal.ip)
$risk_score = if($login_count > 10, "high", "medium")
$sample_target = array_distinct($e.target.user.userid)
condition:
$e and $login_count > 5
Outcome variables support aggregate functions (count, count_distinct, sum, avg, min, max, stddev, array, array_distinct), conditional expressions (if), and simple field assignments. When an outcome conditional is used in a rule that has a match section, the rule is classified as multi-event for quota purposes.
Map Access (Struct and Label Fields)
Some UDM fields use Struct or Label types. Access specific key-value pairs with bracket notation:
events:
$e.udm.additional.fields["pod_name"] = "kube-scheduler"
$e.metadata.ingestion_labels["MetadataKeyDeletion"] = "startup-script"
Modifiers
nocase
Makes a string comparison or regex match case-insensitive. Append it to the end of the comparison expression.
events:
// Case-insensitive string comparison
$e.principal.hostname = "WORKSTATION01" nocase
// Case-insensitive regex (inline literal)
$e.principal.hostname = /.*host.*/ nocase
// Case-insensitive regex (function form)
re.regex($e.network.email.from, `.*altostrat\.com`) nocase
Important: nocase cannot be used on enumerated fields (like metadata.event_type) because enumerated values are always uppercase. Attempting it causes a syntax error.
Classic YARA comparison: Classic YARA also has nocase, but it’s a modifier on string definitions in the strings section (e.g., $s = "foobar" nocase). YARA-L uses it on comparison expressions in the events section instead.
SPL comparison: In SPL, you’d use lower() or upper() functions to normalize case before comparing.
any and all (Repeated Field Modifiers)
UDM fields that can hold multiple values (like principal.ip, which may contain several IP addresses) are called repeated fields. By default, YARA-L checks if any value in the array matches. Use any and all for explicit control:
events:
// Match if ANY IP in the array equals this value (default behavior)
any $e.principal.ip = "10.1.1.5"
// Match only if ALL IPs in the array satisfy this condition
all $e.principal.ip != "10.1.1.5"
SPL comparison: In SPL you’d use mvfilter() or mvfind() to work with multivalue fields. YARA-L handles this natively.
not
Negates a condition. Can be applied to comparisons, function results, and reference list lookups.
events:
$e.metadata.event_type = "PROCESS_LAUNCH"
not re.regex($e.principal.process.command_line, `\\Windows\\System32\\`) nocase
not $e.principal.hostname = "admin-workstation"
in (Reference Lists)
Checks if a field value exists in a pre-defined reference list (denoted with %):
events:
$e.target.ip in %suspicious_ip_addresses
not $e.principal.hostname in %approved_servers
before and after (Temporal Ordering)
Enforce ordering between event variables within the time window:
events:
$e1.metadata.event_type = "USER_LOGIN"
$e2.metadata.event_type = "FILE_CREATION"
$e1.principal.user.userid = $e2.principal.user.userid
$e2.metadata.event_timestamp.seconds > $e1.metadata.event_timestamp.seconds
Or use after in the match section for a sliding window:
match:
$host over 10m after $e1
Regular Expressions
YARA-L supports regex in two forms.
Inline Regex Literals
Wrap the pattern in forward slashes. Use this for direct comparison against a field or placeholder:
events:
$e.principal.hostname = /web-server-[0-9]+/
$e.target.url = /.*\.evil\.com$/ nocase
re.regex() Function
Pass the field and a regex string (in back quotes for literal interpretation, or double quotes with escaping):
events:
re.regex($e.principal.process.command_line, `\bsvchost(\.exe)?\b`) nocase
re.regex($e.network.email.from, `.*altostrat\.com`) nocase
The function returns true if any substring matches. You do not need .* at the start and end unless you’re anchoring. Use ^ and $ anchors for exact matches.
Complete Regex Rule Example
This rule detects svchost.exe running from an unusual location:
rule suspicious_svchost_location {
meta:
author = "Google Cloud Security"
description = "Windows svchost executed from an unusual location"
severity = "HIGH"
events:
$e1.metadata.event_type = "PROCESS_LAUNCH"
re.regex($e1.principal.process.command_line, `\bsvchost(\.exe)?\b`) nocase
not re.regex($e1.principal.process.command_line, `\\Windows\\System32\\`) nocase
condition:
$e1
}
Regex with nocase, match, and condition
This rule finds hosts sending more than 10 emails from a specific domain within 10 minutes:
rule regex_email_example {
meta:
author = "soc-team@example.com"
events:
$e.principal.hostname = $host
$host = /.*host.*/ nocase
re.regex($e.network.email.from, `.*altostrat\.com`) nocase
match:
$host over 10m
condition:
#e > 10
}
Classic YARA comparison: Classic YARA also uses /regex/ syntax, but in the strings section: $re1 = /md5: [0-9a-fA-F]{32}/. Classic YARA regex supports the same nocase modifier.
SPL comparison: SPL uses | regex field=fieldname "pattern" as a command, or match(field, "pattern") in eval/where. YARA-L integrates regex more directly into the event filtering logic.
Putting It All Together — Full Rule Examples
Single-Event Rule (No Match Section)
The simplest form. Fires once per matching event.
rule single_event_login {
meta:
author = "analyst@example.com"
events:
$e.metadata.event_type = "USER_LOGIN"
condition:
$e
}
Multi-Event Correlation Rule
Detects a user logging in and then creating a file within 10 minutes:
rule login_then_file_creation {
meta:
author = "analyst@example.com"
description = "User login followed by file creation within 10 minutes"
severity = "MEDIUM"
events:
$login.metadata.event_type = "USER_LOGIN"
$file.metadata.event_type = "FILE_CREATION"
$login.principal.user.userid = $user
$file.principal.user.userid = $user
match:
$user over 10m
condition:
$login and $file
}
Threshold Rule with Outcome
Detects brute-force login attempts — 5 or more failures per user within 10 minutes, with severity scoring:
rule brute_force_login {
meta:
author = "Security Team"
description = "Detects brute-force login attempts"
severity = "HIGH"
events:
$e.metadata.event_type = "USER_LOGIN"
$e.security_result.action = "FAIL"
$user = $e.target.user.userid
match:
$user over 10m
outcome:
$failed_count = count($e.metadata.id)
$first_fail_time = min($e.metadata.event_timestamp.seconds)
$risk_score = if(count($e.metadata.id) > 20, 90, 50)
condition:
#e >= 5
}
Sliding Window Rule with Negation
Detects when a firewall_1 event is not followed by a firewall_2 event within 10 minutes:
rule missing_followup_event {
meta:
author = "analyst@example.com"
description = "firewall_1 event without corresponding firewall_2 event"
events:
$e1.metadata.product_name = "firewall_1"
$e1.principal.hostname = $host
$e2.metadata.product_name = "firewall_2"
$e2.principal.hostname = $host
match:
$host over 10m after $e1
condition:
$e1 and !$e2
}
Keywords Quick Reference
The following are reserved keywords in YARA-L 2.0 and cannot be used as variable names:
rule, meta, match, over, events, condition, outcome, options, and, or, not, nocase, in, regex, cidr, before, after, all, any, if, max, min, sum, array, array_distinct, count, count_distinct, is, null
Keywords are case-insensitive (and and AND are equivalent), but variable names must not collide with any keyword (e.g., $AND or $outcome are invalid).
Common Functions
| Function | Description | Example |
|---|---|---|
re.regex(field, pattern) | Regex substring match (use backtick-quoted patterns) | See regex section above |
net.ip_in_range_cidr(field, cidr) | Check if IP is within a CIDR range | net.ip_in_range_cidr($e.principal.ip, "10.0.0.0/8") |
strings.concat(a, b) | Concatenate strings | strings.concat($e.principal.hostname, ".local") |
timestamp.as_string(ts) | Convert timestamp to string | timestamp.as_string($e.metadata.event_timestamp) |
math.round(n) | Round a number | math.round(10.7) returns 11 |
arrays.concat(a, b) | Concatenate arrays | arrays.concat(["a","b"], ["c"]) |
group(fields...) | Group similar fields into a placeholder | group($e.principal.ip, $e.target.ip) |
Tips for Analysts Transitioning to YARA-L
Start with the
eventssection. Think of it as yourWHEREclause. Get the filtering right before worrying aboutmatchoroutcome.Zero values can bite you. Omitted or empty fields default to zero values (empty string for strings, 0 for integers). When joining two event variables on a field, add explicit checks like
$e1.principal.hostname != ""to avoid unintended matches.The
#prefix counts things.$emeans “event exists.”#emeans “how many events matched.” This is how you build threshold rules.Use reference lists for tuning. Instead of hardcoding IP addresses or hostnames in exclusions, use reference lists (
%list_name) so you can update them without editing the rule.Use
nocaseliberally on string comparisons. Log data is inconsistently cased. However, do not usenocaseon enumerated fields likeevent_type— they are always uppercase andnocasewill throw a syntax error on these fields.Google SecOps provides an SPL-to-YARA-L translator in the Labs section of the platform to help you migrate existing Splunk queries.
References
- Get Started: YARA-L 2.0 in SecOps — Introductory walkthrough
- YARA-L 2.0 Language Syntax — Full syntax specification (sections, variables, options)
- Overview of YARA-L 2.0 — Examples of single-event, multi-event, regex, sliding window, and outcome rules
- YARA-L 2.0 Rule Examples — Additional query and rule examples
- Transition from SPL to YARA-L 2.0 — Side-by-side SPL and YARA-L comparison
- Functions Reference — Complete list of YARA-L functions
- Expressions, Operators, and Constructs — Operators, literals, map access, and keywords
- Options Section Syntax —
allow_zero_values,suppression_window, and other options - YARA-L Best Practices — Zero-value handling, performance optimization, and rule hygiene
- Classic YARA Documentation — Original YARA rule-writing reference for comparison
