Building a Sentinel detection lab · Part 8
Building a Sentinel Detection Lab, Part 8: Watching for Agents, Not Just Attackers
The same process-lineage technique, pointed at a different actor: not a human attacker, an AI agent.
Repo: github.com/Kajal-Dhanjal/sentinel-detection-lab
Most process-lineage detections in SOC content are written to catch attackers—Office spawning PowerShell, a browser spawning cmd.exe. Part 8 is the same technique, pointed at a different actor. Not a human attacker. An AI agent.
Why lineage, not signature
Detections 6 and 7 were both signature matches: a known binary, a known process-plus-port pattern. This one is different by necessity. Agent frameworks: LangChain-style tools and AI coding assistants commonly do real work by shelling out. The agent decides to run a command, and a script process spawns a shell to execute it. No single process in that chain is suspicious on its own. Python launching cmd.exe happens constantly, for completely ordinary reasons. The signal here isn't either process alone—it's the combination.
Event
| where Source == "Microsoft-Windows-Sysmon"
| where EventID == 1
| extend Image = extract(@"Image:\s+(\S+)", 1, RenderedDescription)
| extend ParentImage = extract(@"ParentImage:\s+(\S+)", 1, RenderedDescription)
| extend ProcessName = tolower(tostring(split(Image, "\\")[-1]))
| extend ParentProcessName = tolower(tostring(split(ParentImage, "\\")[-1]))
| where ParentProcessName has_any (
"ollama.exe", "llamafile.exe", "lmstudio.exe",
"claude.exe", "chatgpt.exe", "cursor.exe", "aider.exe",
"python.exe", "node.exe"
)
| where ProcessName has_any ("cmd.exe", "powershell.exe", "pwsh.exe", "wscript.exe", "cscript.exe", "bash.exe")
| project TimeGenerated, Computer, ParentImage, ParentProcessName, Image, ProcessName
Both the child (Image) and the parent (ParentImage) come off the same EID 1 event, extracted with the same regex pattern I've used since Part 1, normalized to lowercase. The rule only fires when the parent is a known AI/scripting process and the child is a command interpreter on the same event. It's conceptually closest to the behavioral approach from the recon-burst detection in Part 4—no single field tells the story; the relationship does.
Building and validating it
To generate a real test case rather than a synthetic row, I wrote the smallest honest stand-in for an agent I could: a two-line Python script that shells out to run echo.
subprocess.run("cmd.exe /c echo agentic_test", shell=True)
It's not a real agent framework. But structurally, it's exactly what one does under the hood: a script process deciding to spawn a shell. Ran it once: python.exe as a parent, cmd.exe as a child, caught cleanly on the first attempt. No debugging needed, which is rare enough in this lab to be worth noting on its own.

What I'm not claiming
I want to be specific about what one clean validation run does and doesn't prove. It proves the join logic works. It does not prove this rule is tuned for production. python.exe and node.exe as parent processes is a wide net—any ordinary dev workflow or build script that shells out for unrelated reasons would trigger this exact same pattern. I haven't run it against real-world Python or Node usage yet, so I genuinely don't know the false-positive rate. That's the next thing to find out, not something to guess at and write down as if I already know.
That distinction — a rule that's validated versus a rule that's tuned — has come up in nearly every post in this series. Saying it again here isn't repetition for its own sake. It's the actual discipline the job requires, and it's easy to let slip the moment something works on the first try.
What I took from this one
-
A clean first-try result is not the same as a tuned result. It's tempting to treat "worked immediately" as evidence of quality. It's evidence the logic is sound, nothing more, yet.
-
The honest scope of an untested assumption belongs in the rule, not just the writeup. The FP-risk caveat about
python.exe/node.exeas parents is written into the rule's description in Sentinel itself, the same way the browser-logging gap from Part 7 is so the limitation travels with the rule, not just with this post. -
A minimal stand-in is still a valid test if it's structurally honest. The two-line script isn't a real agent, and I'm not pretending it is. But it produces the exact process-lineage shape a real one would, which is the thing the detection actually checks for.
Three detections into the AI-tooling track now: execution, egress, and lineage. The next open thread is tuning: going back to 6 and 8 with real usage data instead of single validation runs and deciding whether that happens before or after detection 9.
#cybersecurity #sentinel #microsoft-sentinel #kql #detection-engineering #blueteam #azure #ai-security
Spotted an error or have a suggestion?
Email me