<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>EDR on Victor on Software</title><link>https://victoronsoftware.com/tags/edr/</link><description>Recent content in EDR on Victor on Software</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Fri, 18 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://victoronsoftware.com/tags/edr/index.xml" rel="self" type="application/rss+xml"/><item><title>My AI coding agent looks exactly like a reverse shell</title><link>https://victoronsoftware.com/posts/ai-coding-agent-reverse-shell/</link><pubDate>Fri, 18 Sep 2026 00:00:00 +0000</pubDate><guid>https://victoronsoftware.com/posts/ai-coding-agent-reverse-shell/</guid><description>&lt;img src="https://victoronsoftware.com/posts/ai-coding-agent-reverse-shell/ai-coding-agent-reverse-shell-headline.png" alt="Featured image of post My AI coding agent looks exactly like a reverse shell" /&gt;&lt;p&gt;One of my favorite alerts from the &lt;a class="link" href="https://github.com/getvictor/fleet-edr" target="_blank" rel="noopener"&#10; &gt;open source EDR&lt;/a&gt; I&amp;rsquo;ve been building this&#10;year is a high-severity &amp;ldquo;Suspicious exec chain.&amp;rdquo; At the top of the chain is my AI coding assistant. It spawned&#10;&lt;code&gt;/bin/bash&lt;/code&gt;, and seconds later that shell opened an outbound HTTPS connection.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://victoronsoftware.com/posts/ai-coding-agent-reverse-shell/suspicious-exec-chain-alert.png"&#10;&#9;&#9;&#9;alt="EDR alert #303, Suspicious exec chain, showing the Claude Code binary spawning /bin/bash, which opened an outbound connection on port 443"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;That&amp;rsquo;s my AI coding assistant doing its job. It takes instructions from a server somewhere, runs shell commands on my&#10;Mac, and sends the results back. In a reverse shell, code on a compromised machine starts a shell and connects it out to&#10;an attacker, who then runs commands through it remotely. My AI agent isn&amp;rsquo;t doing that. But to the rule that flagged it,&#10;it looks exactly like one, because the events it sees have the same shape. At one point, a quarter of my alerts were&#10;this one tool. Same shape as the attack. But, hopefully, completely innocent.&lt;/p&gt;&#10;&lt;p&gt;That alert says a lot about what EDR vendors call behavioral detection, and why it&amp;rsquo;s both simpler and harder than the&#10;marketing suggests.&lt;/p&gt;&#10;&lt;h2 id="a-detection-is-a-shape-across-time"&gt;&lt;a href="#a-detection-is-a-shape-across-time" class="header-anchor"&gt;&lt;/a&gt;A detection is a shape across time&#10;&lt;/h2&gt;&lt;p&gt;EDR stands for Endpoint Detection and Response. It includes an agent on each machine that watches what the operating&#10;system is doing and can sometimes act on it. On macOS, Apple&amp;rsquo;s Endpoint Security and Network Extension frameworks give&#10;that agent a stream of security-relevant facts. A process started. A file was written. A connection went out. Millions&#10;of them, every day. On their own, most of these events don&amp;rsquo;t mean much. A program started a shell. Is that an attack?&#10;Apple doesn&amp;rsquo;t say. Someone has to decide which patterns of ordinary events are worth waking a human for.&lt;/p&gt;&#10;&lt;p&gt;Take the reverse shell. A program starts a shell: normal, happens constantly. A shell opens a network connection: also&#10;normal. But what about a program that isn&amp;rsquo;t a shell, spawning a shell, that then reaches out to the internet seconds&#10;later, in that order? That&amp;rsquo;s more interesting. It looks like something ran, and then called home.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://victoronsoftware.com/posts/ai-coding-agent-reverse-shell/reverse-shell-detection-shape.png"&#10;&#9;&#9;&#9;alt="Diagram of the reverse shell detection rule: a non-shell process spawns a shell, the shell opens an outbound connection, and both events must happen within 30 seconds"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;So a detection isn&amp;rsquo;t one scary event. It&amp;rsquo;s a shape drawn across several ordinary ones and held together over time. In my&#10;rule, the shell or one of its children has to connect out within 30 seconds. And it is a suspicion, not proof.&lt;/p&gt;&#10;&lt;h2 id="a-lot-of-behavioral-detection-is-an-if-statement"&gt;&lt;a href="#a-lot-of-behavioral-detection-is-an-if-statement" class="header-anchor"&gt;&lt;/a&gt;A lot of behavioral detection is an if-statement&#10;&lt;/h2&gt;&lt;p&gt;&amp;ldquo;Behavioral detection&amp;rdquo; sounds like there&amp;rsquo;s an intelligence in there, reasoning about intent. In practice, the term&#10;covers everything from statistical models to a rule somebody typed. Many products layer reputation scores, anomaly&#10;models, and cloud analytics on top. But a great deal of what actually fires on your fleet is code a human wrote that&#10;effectively says: this pattern of events is suspicious.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s a real rule, straight out of my repo, with the comments removed. It&amp;rsquo;s written in &lt;a class="link" href="https://sigmahq.io/" target="_blank" rel="noopener"&#10; &gt;Sigma&lt;/a&gt;, an&#10;open text format for detections, and it catches one piece of the behavior above: Microsoft Office spawning a shell.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;detection&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;selection_shell&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;Image|re&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;^(/bin/sh|/bin/bash|/bin/zsh|/bin/dash|/usr/bin/sh|/usr/bin/bash|/usr/bin/zsh|/usr/bin/dash)$&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;selection_office&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ParentImage|re&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;^(/Applications/Microsoft Word\.app/Contents/MacOS/Microsoft Word|/Applications/Microsoft Excel\.app/Contents/MacOS/Microsoft Excel|/Applications/Microsoft PowerPoint\.app/Contents/MacOS/Microsoft PowerPoint|/Applications/Microsoft Outlook\.app/Contents/MacOS/Microsoft Outlook)$&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;condition&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;selection_shell and selection_office&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Two conditions and an AND. That&amp;rsquo;s the entire rule. You could read it in a code review. You could write one yourself over&#10;lunch. The full reverse shell rule is more involved, because Sigma can&amp;rsquo;t walk a process tree for 30 seconds, so that&#10;logic lives in Go code. But that code is public too. And that gives a rule a very useful property: you can read it. You&#10;can test it. You can argue with it.&lt;/p&gt;&#10;&lt;h2 id="writing-the-rule-took-an-afternoon-taming-it-has-taken-months"&gt;&lt;a href="#writing-the-rule-took-an-afternoon-taming-it-has-taken-months" class="header-anchor"&gt;&lt;/a&gt;Writing the rule took an afternoon. Taming it has taken months.&#10;&lt;/h2&gt;&lt;p&gt;I&amp;rsquo;m not saying detection is easy. Writing the reverse shell rule was easy. Making it fire only on the things you&amp;rsquo;d&#10;actually want to be woken up for has taken months, and I still haven&amp;rsquo;t finished. Plenty of legitimate software looks&#10;exactly like an attack. Build tools spawn shells and hit the network. IDEs do. Package managers do it by design. So do&#10;Installomator, your device management agent, and now AI coding agents. Every vendor does this tuning work. And you do&#10;the same thing every time you add an exclusion.&lt;/p&gt;&#10;&lt;h2 id="every-broad-exception-is-an-evasion-path"&gt;&lt;a href="#every-broad-exception-is-an-evasion-path" class="header-anchor"&gt;&lt;/a&gt;Every broad exception is an evasion path&#10;&lt;/h2&gt;&lt;p&gt;The obvious fix for my AI assistant alert doesn&amp;rsquo;t work. I can&amp;rsquo;t simply tell the rule to ignore that shell, because the&#10;shell is bash. Plain old bash. If I blind the rule to bash, I&amp;rsquo;ve blinded it to the real attacks that also use bash.&#10;That&amp;rsquo;s the trap: every broad exception creates an evasion path. What I can do is write a narrow exception tied to one&#10;specific signed binary, by its code signing team ID. That takes a lot longer, and it&amp;rsquo;s much harder for an attacker to&#10;imitate.&lt;/p&gt;&#10;&lt;p&gt;There are two ways to get a rule wrong, and they&amp;rsquo;re opposites. A broad rule fires on everything, and a tired security&#10;team starts muting such rules. A brittle rule keys on something shallow: a filename, a string, one event with no&#10;context. It almost never raises a false alarm. But when the attacker changes one detail, the rule stops matching, and&#10;nothing tells you it stopped.&lt;/p&gt;&#10;&lt;p&gt;My own reverse shell rule had exactly that hole. Swap bash for zsh, and zsh replaces itself with the payload and&#10;vanishes from the process tree the rule was walking. Same attack, but invisible. I&#10;&lt;a class="link" href="https://github.com/getvictor/fleet-edr/issues/713" target="_blank" rel="noopener"&#10; &gt;fixed that hole&lt;/a&gt;. But a rule you can dodge by changing one word was&#10;brittle to begin with. So the useful question isn&amp;rsquo;t whether a rule can be evaded. It&amp;rsquo;s what evading it costs.&lt;/p&gt;&#10;&lt;h2 id="rule-count-isnt-coverage"&gt;&lt;a href="#rule-count-isnt-coverage" class="header-anchor"&gt;&lt;/a&gt;Rule count isn&amp;rsquo;t coverage&#10;&lt;/h2&gt;&lt;p&gt;This is also why a big detection count doesn&amp;rsquo;t mean much. It tells you nothing about how many rules are good, tuned for&#10;a fleet like yours, or even working. CardinalOps tracks this for SIEM detection rules every year. Their&#10;&lt;a class="link" href="https://cardinalops.com/white-papers/2025-state-of-siem-report-download/" target="_blank" rel="noopener"&#10; &gt;2025 report&lt;/a&gt; found that 13% of rules in&#10;production environments were broken and would never fire. The year before, it was 18%. Not tuned badly. Broken. A quiet&#10;alert queue doesn&amp;rsquo;t prove much either, because a brittle rule is quiet too. What you want to know is whether the rules&#10;that should fire actually do, and how many of the alerts that reach your team are real.&lt;/p&gt;&#10;&lt;p&gt;And even a rule that works still has to be believed when it fires.&lt;/p&gt;&#10;&lt;h2 id="a-correct-alert-can-still-be-argued-away"&gt;&lt;a href="#a-correct-alert-can-still-be-argued-away" class="header-anchor"&gt;&lt;/a&gt;A correct alert can still be argued away&#10;&lt;/h2&gt;&lt;p&gt;In March 2023, a North Korean-linked actor trojanized the 3CX desktop app and shipped it through the vendor&amp;rsquo;s own update&#10;channel. The builds carried 3CX&amp;rsquo;s own signature. The macOS build was even&#10;&lt;a class="link" href="https://objective-see.org/blog/blog_0x73.html" target="_blank" rel="noopener"&#10; &gt;notarized by Apple&lt;/a&gt;, so the Mac&amp;rsquo;s signing and notarization checks let it&#10;run. Notarization means Apple&amp;rsquo;s automated scan found nothing it knew to be malicious. It doesn&amp;rsquo;t mean the app is safe.&lt;/p&gt;&#10;&lt;p&gt;What fired was behavioral detection, on the Windows side. On March 22, a week before the compromise was confirmed, a&#10;customer&#10;&lt;a class="link" href="https://www.3cx.com/community/threads/threat-alerts-from-sentinelone-for-desktop-update-initiated-from-desktop-client.119806/" target="_blank" rel="noopener"&#10; &gt;posted SentinelOne alerts&lt;/a&gt;&#10;from the Windows app on the 3CX forum. The alerts named shellcode and code injection. The next day, another customer&#10;replied: &amp;ldquo;I added the exception for the signer id &amp;lsquo;3CX LTD&amp;rsquo;.&amp;rdquo; For about a week, customers and the vendor questioned the&#10;detector instead of the software. The identity of the software kept winning the argument against its behavior.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://victoronsoftware.com/posts/ai-coding-agent-reverse-shell/3cx-forum-sentinelone-alert.png"&#10;&#9;&#9;&#9;alt="3CX community forum post from March 22, 2023, listing a SentinelOne Post Exploitation alert: Penetration framework or shellcode was detected"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure&gt;&lt;img src="https://victoronsoftware.com/posts/ai-coding-agent-reverse-shell/3cx-forum-signer-exception.png"&#10;&#9;&#9;&#9;alt="3CX community forum reply from March 23, 2023: OK, I added the exception for the signer id 3CX LTD and the paths to the 3CX desktop app"&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;The lesson isn&amp;rsquo;t that a clever rule saved everybody. It&amp;rsquo;s that trusted software can also behave maliciously, and a&#10;correct alert can still be argued away when nobody can explain what produced it.&lt;/p&gt;&#10;&lt;h2 id="what-to-ask-your-edr-vendor"&gt;&lt;a href="#what-to-ask-your-edr-vendor" class="header-anchor"&gt;&lt;/a&gt;What to ask your EDR vendor&#10;&lt;/h2&gt;&lt;p&gt;Which gives you one of the sharpest questions to ask an EDR vendor, in three parts. Can we inspect your detection&#10;content? Can we tune it? And can we export it, in Sigma or any documented format? While you&amp;rsquo;re at it, ask to see the&#10;exclusion list. It&amp;rsquo;s the map of where the product has decided not to alert, and sometimes not to look at all. You can&amp;rsquo;t&#10;judge coverage without it.&lt;/p&gt;&#10;&lt;p&gt;None of this is a fringe ask. Elastic &lt;a class="link" href="https://github.com/elastic/protections-artifacts" target="_blank" rel="noopener"&#10; &gt;publishes the detection rules&lt;/a&gt;&#10;its endpoint agent runs, and has run a &lt;a class="link" href="https://www.elastic.co/security-labs/behavior-rule-bug-bounty" target="_blank" rel="noopener"&#10; &gt;bug bounty&lt;/a&gt; that&#10;pays researchers for bypassing its endpoint behavior rules. And if your EDR&amp;rsquo;s rules are a black box, then after an&#10;incident you can&amp;rsquo;t really tell your board why a detection fired, or why it didn&amp;rsquo;t. &amp;ldquo;The vendor&amp;rsquo;s engine decided&amp;rdquo; isn&amp;rsquo;t a&#10;root cause anyone accepts.&lt;/p&gt;&#10;&lt;p&gt;If the detection is an AI model rather than a rule, there may be nothing to inspect. So ask something related. When it&#10;fires, does it tell you which behavior triggered it? Can you tune it, or only turn it down?&lt;/p&gt;&#10;&lt;p&gt;My reverse shell detection has no cloud and no model. It&amp;rsquo;s two ordinary events joined by a rule you could read. That&amp;rsquo;s&#10;the standard I&amp;rsquo;d hold a vendor to: show me the events, show me the logic that fired, and let me change what doesn&amp;rsquo;t fit&#10;my environment. None of that requires open source. It just requires letting you open the box and look.&lt;/p&gt;&#10;&lt;h2 id="further-reading"&gt;&lt;a href="#further-reading" class="header-anchor"&gt;&lt;/a&gt;Further reading&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;a class="link" href="https://victoronsoftware.com/page/tools/edr-decision-worksheet.pdf" &gt;Does your Mac fleet need an EDR?&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&#10;A one-page decision worksheet for Mac admins who were handed an EDR quote and asked whether the fleet needs it.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;a class="link" href="../engineering-transparency/" &gt;Why transparency beats everything else in engineering&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&#10;Trust without visibility is fragile. The same argument this article makes about detection rules, applied to&#10;engineering teams.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;a class="link" href="../code-signing-windows/" &gt;Code signing a Windows application&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&#10;What a code signature proves: who published the software and that nobody altered it since. Not that it&amp;rsquo;s safe to run.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;a class="link" href="../comprehension-debt/" &gt;Comprehension debt: the hidden cost of AI-generated code&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&#10;Another side of AI coding agents: code that ships before the team can explain what it does.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;&lt;a class="link" href="https://github.com/getvictor/fleet-edr" target="_blank" rel="noopener"&#10; &gt;Open source EDR on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&#10;The open source macOS EDR behind this article, including every detection rule and the issues where they broke.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="watch-the-full-talk"&gt;&lt;a href="#watch-the-full-talk" class="header-anchor"&gt;&lt;/a&gt;Watch the full talk&#10;&lt;/h2&gt;&lt;p&gt;This article covers one idea from a longer conference talk on building an open source macOS EDR. The talk also gets into&#10;the DNS proxy that took my Mac offline, the agent that went blind while its dashboard stayed green, and the opinions&#10;Apple&amp;rsquo;s Endpoint Security framework builds into every Mac security tool.&lt;/p&gt;&#10;&lt;div class="video-wrapper"&gt;&#10; &lt;iframe loading="lazy" &#10; src="https://www.youtube.com/embed/e9dgophT_cs" &#10; allowfullscreen &#10; title="YouTube Video"&#10; &gt;&#10; &lt;/iframe&gt;&#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; If you want to comment on this article, please do so on the YouTube video.&lt;/p&gt;&#10;</description></item></channel></rss>