October 27, 2026
Audience: SOC managers, detection engineers, incident responders
Summary: Retrohunt runs a YARA-L rule against UDM data you’ve already ingested. Instead of waiting weeks for live detections, you can see how a new rule behaves, sweep for new indicators, or scope an incident in a single run. This briefing covers:
- how retrohunt works and its limits
- the settings that keep it from causing problems
- four use cases with example rules
- dashboard queries for reviewing results
- criteria for promoting a rule to live
1. Retrohunt compared with the other ways to test a rule
| Tool | What it does | Saves detections? | Best for |
|---|---|---|---|
| Run test (Rules editor) | Runs the rule over a time range and shows matches in the Test rule results window | No. Results aren’t saved, don’t generate alerts, and don’t appear on the Rules Dashboard. | Checking syntax and logic while you write the rule |
| Retrohunt | Runs a saved rule version over a historical time range | Yes, labeled as retrohunt detections | Measuring volume and precision before going live, IOC sweeps, incident scoping |
| Live rule | Runs the rule against incoming data | Yes | Production detection |
| UDM search | Runs a one-off search for events | No | Quick lookups and pivots on a single indicator |
Google notes that Run test results can differ from retrohunt and live results, because the evaluation windows may be aligned differently. Use Run test while you’re writing a rule. Use retrohunt when you need saved detections and real volume numbers to decide whether a rule is ready for production.
2. Before you run: checklist
| Check | Why |
|---|---|
| Turn alerting off for the rule | With alerting on, every retrohunt detection creates a SOAR alert. Google warns this can flood the system with overflow cases and temporarily block case creation for other alerts. If you need cases for historical hits, create a new rule version with alerting on and run the retrohunt again. |
| No more than 3 running at once | That’s the maximum per instance. Parallel retrohunts draw on the same instance resources, so they can slow each other down. |
| Time range at least as long as the match window | Required for multi-event rules. A rule with over 24h needs at least 24 hours. |
| Range within your available data | The retrohunt dialog shows the date range you can search. |
| Know the detection cap | Each rule version can produce 10,000 detections per day, and the count resets at midnight UTC. If a day in your results is at or near 10,000, that count is capped and the real volume is higher. |
| Save a new rule version after list changes | Rerunning a retrohunt after a reference list update doesn’t reset the daily cap. Removing list items doesn’t delete existing detections. Save a new version of the rule before you retrohunt again. Google documents this for reference lists; treat data tables the same way. |
| Retrohunt again after every rule edit | Each retrohunt belongs to a specific rule revision, and saving an edit creates a new version. |
| Don’t promise a finish time | Retrohunts are scheduled based on available system resources, and Google says completion times vary. |
3. How to run a retrohunt
In the console:
- Open the Rules Dashboard, click the rule’s options icon, and select Yara-L Retrohunt.
- Choose the start and end times. The default range is one week.
- Click RUN. Progress shows in the rule’s detections view. If you cancel a run partway through, you keep the detections it already found.
- To see earlier runs, click the date range link in the rule’s detections view. Each run’s results appear in the Timeline and Detections graph.
Through the API (for automation and detection-as-code):
The caller needs the chronicle.retrohunts.create IAM permission.
curl -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
"https://REGION-chronicle.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/instances/INSTANCE_ID/rules/RULE_ID/retrohunts" \
-d '{"processInterval": {"startTime": "2026-09-27T00:00:00Z", "endTime": "2026-10-27T00:00:00Z"}}'| Item | Detail |
|---|---|
| REGION | Your instance’s region, for example us or europe-west2 |
| Response | A long-running operation, not the retrohunt itself |
| Tracking progress | Send a GET to the same /retrohunts path to list the rule’s retrohunts. Then use retrohunts.get with the retrohunt’s name. |
state | RUNNING, DONE, CANCELLED, or FAILED |
progressPercentage | 0 to 100 |
4. Use cases
Use case 1: Validate and tune a new rule
Scenario: You want to detect lateral movement where one source opens RDP connections to many hosts.
Version 1:
rule rdp_fan_out {
meta:
author = "Detection Engineering"
description = "One source connects to 5 or more distinct hosts over RDP within 1 hour"
severity = "Medium"
events:
$e.metadata.event_type = "NETWORK_CONNECTION"
$e.target.port = 3389
$e.principal.ip = $src
$e.target.ip = $dst
match:
$src over 1h
outcome:
$distinct_targets = count_distinct($dst)
$source_hosts = array_distinct($e.principal.hostname)
condition:
$e and $distinct_targets >= 5
}Tuning loop (the numbers are only an example):
- Run a 7-day retrohunt with alerting off. It returns about 1,400 detections, roughly 200 a day.
- Find where the detections come from. Use the top sources query in Section 5. Most of the volume comes from three machines: a jump host, a vulnerability scanner, and an IT management server.
- Add exclusions. Put those hosts in a data table and add one line to the
events:section:not $e.principal.hostname in %rdp_admin_hosts.hostname
- Save and run again. Saving creates version 2. Retrohunt the same 7 days. This time it returns 6 detections, and 4 of them line up with a known red team exercise.
- Widen the range. Run it over 30 days to confirm the rule stays quiet, then enable live detection.
Note: Once a rule uses a data table, it can’t run at real-time frequency. Google doesn’t support real time for rules with data tables.
Use case 2: Sweep for new threat intel indicators
Scenario: Your intel team receives IP addresses and domains linked to an active campaign. You need to know whether any of them have already appeared in your environment.
Check IoC matching first. If the indicators came from a threat intel feed already integrated with SecOps, SecOps may have matched them already. Use retrohunt for indicators you collected yourself, for example from a vendor report, an ISAC alert, or your own analysis.
Set up two data tables. Use campaign_x_ips with an ip column and campaign_x_domains with a domain column. Keeping them separate means neither table has blank cells.
rule ti_campaign_x_sweep {
meta:
author = "Threat Intel"
description = "Connections or DNS lookups to campaign X indicators"
severity = "High"
events:
(
$e.target.ip in %campaign_x_ips.ip or
$e.network.dns.questions.name in %campaign_x_domains.domain
)
$e.principal.ip = $src
match:
$src over 1h
outcome:
$hostnames = array_distinct($e.principal.hostname)
$matched_ips = array_distinct($e.target.ip)
$matched_domains = array_distinct($e.network.dns.questions.name)
$first_seen = min($e.metadata.event_timestamp.seconds)
condition:
$e
}| Design choice | Reason |
|---|---|
| Two lookups in one OR block | YARA-L lets you join data table lookups with OR when they all reference the same event variable. You can’t mix a normal field comparison and a data table lookup inside the same parentheses, so keep other conditions on their own lines. |
| Grouped on source IP, not hostname | YARA-L drops events where a match variable is empty. Many firewall and DNS logs have no hostname, so grouping on hostname would silently skip them. Hostnames still appear in the outcome when the logs include them. |
How to run it: Retrohunt as far back as your retention and the campaign timeline justify. Then leave the rule live with alerting on so future hits come through as cases. Because the rule uses data tables, it can’t run in real time, so expect some delay on live hits.
When indicators change: Update the data tables, save a new rule version, and retrohunt again.
Use case 3: Scope an incident
Scenario: Forensics gives you SHA-256 hashes from a compromised host. You need to know how far back the activity goes and which other machines ran the same files.
rule ir_case_1234_hash_sweep {
meta:
author = "Incident Response"
description = "Process launches matching hashes from IR case 1234"
severity = "High"
events:
$e.metadata.event_type = "PROCESS_LAUNCH"
$e.target.process.file.sha256 in %ir_case_1234_hashes.sha256
$e.principal.hostname = $host
match:
$host over 24h
outcome:
$first_seen = min($e.metadata.event_timestamp.seconds)
$last_seen = max($e.metadata.event_timestamp.seconds)
$users = array_distinct($e.principal.user.userid)
condition:
$e
}| Tip | Detail |
|---|---|
| Match the hash case | Store hashes in the same case your EDR logs use, usually lowercase. Lookups are case-sensitive by default. |
| Watch for empty hostnames | If your EDR leaves hostname empty on some events, those events are skipped (same reason as use case 2). Group on principal.asset_id or principal.ip instead. |
| Use the dashboard for the full list | Lists in rule outcomes, like $users, are capped at 25 values. The scoping query in Section 5 shows every affected host with its first and last activity. |
| Check first-seen dates | A host whose first activity comes before the earliest known compromise means the intrusion started earlier than you thought. |
| Repeat until nothing new appears | Each new indicator from forensics, such as a C2 domain, service name, or command line, goes into a new rule version and another retrohunt. Scoping is done when a sweep stops finding new hosts. |
Use case 4: Check that tuning didn’t drop real detections
Before you deploy a tuned version, retrohunt it over the same time range you used for the previous version, then compare the two result sets. Every true positive from the old version should still appear in the new one. If one disappeared, your exclusion is too broad.
5. Reading the results: dashboard queries
Retrohunt detections are labeled in the detection.detection_timing_details field, so you can chart them separately from live detections. Both queries use the Detections data source. If the results look cut short, widen the dashboard time range to cover the full retrohunt window.
Retrohunt detections per rule, by the date the activity happened:
detection.detection_timing_details = "DETECTION_TIMING_DETAILS_RETROHUNT"
$rule_name = detection.detection.rule_name
$event_date = timestamp.get_date(detection.collection_elements.references.event.metadata.event_timestamp.seconds)
match:
$rule_name, $event_date
outcome:
$detection_count = count_distinct(detection.id)
order:
$event_date ascUse this chart for capacity planning. The daily counts are roughly what the SOC would have received if the rule had been live.
Scoping: affected hosts with first and last activity:
detection.detection_timing_details = "DETECTION_TIMING_DETAILS_RETROHUNT"
detection.detection.rule_name = "ir_case_1234_hash_sweep"
$host = group(detection.collection_elements.references.event.principal.hostname, detection.collection_elements.references.event.target.hostname)
$host != ""
match:
$host
outcome:
$detection_count = count_distinct(detection.id)
$first_seen = timestamp.get_timestamp(min(detection.collection_elements.references.event.metadata.event_timestamp.seconds))
$last_seen = timestamp.get_timestamp(max(detection.collection_elements.references.event.metadata.event_timestamp.seconds))
order:
$first_seen ascChange the rule name for each sweep.
To find top sources for tuning (use case 1):
- Replace the
group()line with$host = detection.collection_elements.references.event.principal.hostname. If your network logs don’t carry hostnames, useprincipal.ipinstead. - Order by
$detection_count desc.
The top rows are your exclusion candidates.
How to read the results:
| Result | Meaning | Action |
|---|---|---|
| Thousands of detections from a few hosts or users | Known-benign sources such as admin tools or scanners | Exclude them with a data table, then run again |
| Thousands of detections spread evenly | The logic is too broad | Tighten the conditions or raise the thresholds |
| A day at or near 10,000 detections | You hit the daily cap, so real volume is higher | The rule is far too noisy to deploy |
| A handful of detections that match known activity | The rule works as intended | Widen the range, then promote it |
| Zero detections | Either nothing happened, or the rule can’t see the activity | Test with a known positive before trusting the result |
Don’t trust a zero without a known positive.
- Run the technique in a test environment, for example an Atomic Red Team test or a red team exercise.
- Retrohunt the window that covers it.
- If the rule doesn’t fire, use the Week 19 field coverage query to check that the log source is arriving and the fields the rule needs are populated.
6. For SOC managers: promotion criteria and capacity
Criteria for promoting a rule to live (a starting point; adjust to your team):
| Criterion | Suggested bar |
|---|---|
| Retrohunt range | At least 30 days, or at least 7 days for very high-volume sources |
| Known-positive test | The rule fires on a known positive within the range |
| Expected volume | Average daily detections fit within triage capacity (below) |
| Precision | All detections reviewed, or a sample of at least 20, meeting a true positive rate your team has agreed on |
| Regression check | Every true positive from the previous version is still detected (use case 4) |
| Record kept | Rule name and version, time range, detection count, true and false positive counts, reviewer, and date |
Capacity math: Multiply expected daily detections by average triage minutes per alert. For example, 40 a day at 10 minutes each is about 6.7 analyst hours a day. If you don’t have that capacity, tune the rule further, automate its enrichment, or hold it back.
Validation record: Keep promotion evidence in a data table such as rule_validation_log, with these columns:
- rule
- version
- time range
- detections
- true positives
- false positives
- reviewer
- date
The table is searchable inside SecOps and survives staff changes. It also gives auditors evidence when they ask how you validate detections.
Keep retrohunts out of performance metrics: A retrohunt detection is created when the retrohunt runs, but the events behind it can be weeks old. If retrohunt detections land in your Week 18 MTTD query or Week 17 detection volume charts, one retrohunt shows up as a large spike in both. Add this filter to those queries. In the MTTD query, put it inside the per_alert stage next to the alert_state filter.
not detection.detection_timing_details = "DETECTION_TIMING_DETAILS_RETROHUNT"
7. For response engineers: operating retrohunts
| Practice | How |
|---|---|
| Automate in the pipeline | In a detection-as-code workflow: create or update the rule with alerting off, start a retrohunt through the API, wait for DONE, pull the detection count, and compare it with the promotion criteria. Enable alerting only after it passes. |
| Protect the three slots during an incident | Scoping sweeps come first. Cancel lower-priority runs, like routine tuning, so incident work can start. |
| Start narrow, then widen | Run 24 hours, then 7 days, then 30, then 90. Broad rules over long ranges take the longest and are the most likely to hit the daily cap. |
| Fix resource errors with filters | If a retrohunt fails with “Spilled bytes exceed limit” or “Your query resource usage is exceeding its allocation”, the rule is processing too many events. Google’s fix for both is to add filters, starting with metadata.log_type. |
8. Things to know about historical data
| Topic | Detail |
|---|---|
| Parser changes | Retrohunt runs against UDM events as they were stored. Google doesn’t describe any way to reparse historical data after a parser update, so assume older events keep their original field mappings. A rule that depends on a recently fixed field can miss older activity even when the rule is correct. |
| New UDM fields | If a rule uses a field added to the schema recently, a retrohunt over an earlier range can fail with “Accessing a new field that did not exist for this time range”. Start the range after the field existed, or change the rule to handle the field being empty. |
| Removed list items | Google doesn’t delete detections when you remove items from a reference list. Save a new rule version to get clean results. |
| Run time | Retrohunts are scheduled when resources are available, so completion time varies. Don’t commit to an incident scoping deadline until the run has started. |
9. Action items
- Make “disable alerting before a retrohunt” a written standard step
- Adopt the promotion criteria, and require a retrohunt record before any rule goes live
- Build the two retrohunt dashboard charts: volume by event date, and scoping by host
- Create a
rule_validation_logdata table for promotion evidence - Set up data table templates for IOC sweeps (separate IP and domain tables) and incident hash sweeps
- Add a retrohunt step to the detection-as-code pipeline through the API
- Exclude retrohunt detections from MTTD and detection volume charts
