September 7, 2026
Stop Waiting for Alerts: Threat Hunting with CoPilot Searches
Catch an encoded PowerShell attack with a built-in search โ then write your own detection as code, on an open-source SIEM

By SOCFortress
6 min read
Catch an encoded PowerShell attack with a built-in search โ then write your own detection as code, on an open-source SIEM
Most of us spend the day on defense โ sitting in the queue, waiting for an alert to fire, then chasing whatever it was. And that's a big part of the job, no argument. But your SIEM can do the opposite too: it can let you go out and hunt, before anything trips an alert at all.
That's what I want to show you in this one โ CoPilot Searches, in SOCFortress CoPilot. It's a feature I don't think enough people are using, and it does two things really well. First, it ships with a catalog of pre-built detection searches so you can hunt the known techniques on day one, zero setup. And second โ this is the part I really like โ it lets you write your own detections as code, keep them version-controlled in Git, test them against your real data, and turn them into alerts that run on a schedule.
I'll walk the whole thing: run a quick attack, catch it with a built-in search, then build my own detection from scratch. Everything I use is in a public repo so you can follow along in your own lab:
๐ github.com/taylorwalton/CoPilot-Searches-Demo
It's benign โ the "attack" just runs some discovery commands inside a hidden, encoded PowerShell process, so there's nothing to clean up.
The attack: encoded, hidden PowerShell
Here's my attacker. I'm running PowerShell, but look at how โ hidden window, no profile, and the whole command base64-encoded so you can't just read what it's doing:
powershell.exe -NoProfile -WindowStyle Hidden -EncodedCommand <base64...>powershell.exe -NoProfile -WindowStyle Hidden -EncodedCommand <base64...>That shape right there is the tell. Nobody's normal admin script goes out of its way to hide itself like this. Under the hood it's just running discovery โ who am I, who are the local admins, who's in Domain Admins โ but the way it's dressed up is exactly what a real attacker looks like when they don't want you reading their commands.
Normally I'd wait for an alert to fire on that. Instead, let me go catch it.
Catch it with a built-in search
In CoPilot, this lives under Agents โ CoPilot Searches. What you're looking at is a whole catalog of detection rules, and I didn't write a single one of them โ they're maintained for you, pulled from a public GitHub repo, and they hunt across your Wazuh Indexer data. That's my starting library, out of the box.
And I can filter the whole thing โ by platform, by severity, by CVE, by MITRE technique. Since I just ran some sketchy encoded PowerShell, I'll search for PowerShell, and there's one built for exactly this: Malicious PowerShell Process โ Encoded Command.
I open it, give it a few things โ my index pattern, the customer, a time window โ and run it against my indexer. And sure enough, there it is. It found the encoded PowerShell I just ran, no alert rule and no setup on my end. That's the value of the built-in catalog: day one, before you've configured anything, you can proactively go looking for the big well-known techniques.
Why write your own
That built-in caught it, and that's exactly what the shared catalog is for. But that rule belongs to everybody. Sooner or later you're going to want your own detection โ maybe tuned to your environment, maybe broadened to also flag a hidden window even when it's not encoded, maybe you just want a version you own and control and can schedule as your own alert.
And you want it done right โ version-controlled and tested, not typed into a box and hoped for. That's what custom searches are, and this is the part I really want you to take away. It's detection-as-code. You write the detection as a YAML file, keep it in your own Git repo, CoPilot reads it in, backtests it against your real data, and then you promote it to an alert.
Detection-as-code: writing the custom search
Here's my detections repo โ just a folder of YAML files in GitHub. Git is the source of truth, which I love, because it means my detections get reviewed and versioned like any other code. Here's the rule:
name: Encoded PowerShell - Hidden Window Execution
id: 28973b4a-c2cb-4181-b0cd-cce0c84d6df1
version: 1
schema_version: "1.0"
description: >
Detects Windows PowerShell launched with an encoded command and/or a
hidden window - a common defense-evasion pattern. T1059.001 / T1027.
graylog:
query: >-
data_win_system_eventID:"1"
AND data_win_eventdata_originalFileName:"PowerShell.EXE"
AND (message:"EncodedCommand" OR message:"WindowStyle Hidden")
response:
risk_score: 75
severity: high
tags:
mitre_attack_id:
- T1059.001
- T1027name: Encoded PowerShell - Hidden Window Execution
id: 28973b4a-c2cb-4181-b0cd-cce0c84d6df1
version: 1
schema_version: "1.0"
description: >
Detects Windows PowerShell launched with an encoded command and/or a
hidden window - a common defense-evasion pattern. T1059.001 / T1027.
graylog:
query: >-
data_win_system_eventID:"1"
AND data_win_eventdata_originalFileName:"PowerShell.EXE"
AND (message:"EncodedCommand" OR message:"WindowStyle Hidden")
response:
risk_score: 75
severity: high
tags:
mitre_attack_id:
- T1059.001
- T1027Name, a fresh unique ID, a version so I can track changes, a description. Then the important part โ the query, in Graylog/Lucene. I'm telling it: find me Sysmon process-creation events where the original file name is PowerShell and the command line has an encoded command or a hidden window. Then I tag it with a risk score, a severity, and the MITRE techniques so it slots into the catalog properly, and I document the known false positives โ because some legit tools do use encoded PowerShell, and the next analyst deserves to know that up front.
Two gotchas that will bite you the first time
I hit both of these building this rule, and you will too, so let me save you the head-scratching.
Leading wildcards are blocked. My first instinct was to wrap the terms in wildcards โ *EncodedCommand*. Graylog disables leading-wildcard searches by default, so the moment you run that you get:
'*' or '?' not allowed as first character in WildcardQuery'*' or '?' not allowed as first character in WildcardQueryUnscoped terms blow up the query. So I dropped the wildcards and used bare terms โ "EncodedCommand". That threw a different error:
too_many_nested_clauses; maxClauseCount is set to 1024too_many_nested_clauses; maxClauseCount is set to 1024That one's because a bare term with no field searches every field in the index โ hundreds of them โ and blows past the boolean-clause limit.
The fix for both is the same idea: scope every clause to a single field. data_win_system_eventID and data_win_eventdata_originalFileName are exact keyword matches, and for the command-line text I point the terms specifically at the analyzed message field, which holds the full command line. No wildcards, nothing fanning out across the index:
data_win_system_eventID:"1" AND data_win_eventdata_originalFileName:"PowerShell.EXE" AND (message:"EncodedCommand" OR message:"WindowStyle Hidden")data_win_system_eventID:"1" AND data_win_eventdata_originalFileName:"PowerShell.EXE" AND (message:"EncodedCommand" OR message:"WindowStyle Hidden")That runs clean and returns exactly the event I'm after.
Backtest it before you trust it
Here's the part that makes a custom detection trustworthy. Before I rely on this rule at all, I backtest it. CoPilot takes the query and runs it back over โ by default โ the last week of my real data, and tells me exactly what it would have caught: how many hits, what the matching events look like. There's my encoded PowerShell showing up.
So I know two things before this rule ever fires a single alert: it works, and I've got a real feel for how noisy it's going to be. No deploying a rule and hoping it's not a false-positive machine.
Promote it to a recurring alert
Last step, and this is the whole payoff. I promote the rule to a recurring alert. Now CoPilot runs this hunt on a schedule, across every endpoint, and the next time someone fires encoded PowerShell anywhere on the network, it lands in my incident queue automatically.
Think about what just happened. I took a one-time hunch, turned it into a version-controlled detection, tested it against my own data, and made it hunt for me while I sleep. That's the whole point of custom searches.
Wrapping up
That's CoPilot Searches. The built-in catalog gets you hunting the known techniques on day one, and custom searches let you write your own detections as code โ versioned in Git, backtested against your real data, and promoted to alerts that run on a schedule across your whole environment. All on top of an open-source SIEM, and all about going and finding the threat instead of waiting for it to come find you.
If you want to get hands-on, the attack simulation and the custom detection rule are both in the repo so you can run this exact scenario in your own lab:
๐ github.com/taylorwalton/CoPilot-Searches-Demo
And if you'd rather watch me work it end to end, the video is here: youtu.be/CAbOL4KOvwk
Techniques covered (MITRE ATT&CK)
- T1059.001 โ Command and Scripting Interpreter: PowerShell
- T1027 โ Obfuscated Files or Information (encoded command)
Links
- SOCFortress CoPilot: https://github.com/socfortress/CoPilot
- CoPilot Searches docs: https://docs.socfortress.co/power-features/copilot-searches
- Writing custom search rules: https://docs.socfortress.co/power-features/copilot-searches-custom-rules
- Demo repo: https://github.com/taylorwalton/CoPilot-Searches-Demo
- Want help standing up or tuning an open-source SIEM for your environment? Book a call: https://calendly.com/d/2cg-f7q-w94/socfortress-intro-call