Agentic | Field Notes

Introduction

Recently a few articles came across my timeline that broached the topic of how agentic workloads impact telemetry ingestion.

In particular Kofisec began to explore how traditional detection controls and measurements might break and antonlovesdnb shared a potentially useful new tool that might help address the significant development time required to bring analysts the telemetry they need during agentic related incidents. (I haven't tested it yet). 

Considering these articles and the general direction we are now headed I thought I would share a quick snippet on what I'm seeing in the telemetry.




Process characteristics

Starting with some basic empirical data on the prevalence of claude code overall the binary claude.exe experiences significant diversity in load location where in some cases I am seeing up to four times the amount of locations used on disk compared to total installations.

This is a natural product of the claude installation eco system where claude apps utilises the temp directories as both a staging area for MCP servers, plugins and core features but also for direct workload requirements like ‘scratchpads’.

Additionally, claude.exe exhibits a high degree of command-line parameter diversity due to its packaged Electron architecture and support for user-defined switches. In total, I recorded 228 distinct command-line switches (excluding custom user arguments).

Overall where a value is passed with a switch the values are highly predictable with over 90 percent of the content being a file path inside the users folder.

Switches are characterised by these groups:

  • electron app components
  • session settings
    • max tokens
    • disallowed tools
    • effort
  • app settings
    •  mcp servers
  • enabled feature flags

Parent/Child Relationships

Overall twice as many apps invoke Claude as Claude itself invokes. This is an interesting observation where in general both sides of the coin share a similar general profile of native host based controls and user installed software but the depth of integration into other apps seems to compound the surfaces Claude touches.

child: claude.exe

  • Browsers (SSO callback and ‘magic links’)

  • App Managers (Raycast, Powertoys etc)

  • Script Interpreters (all of them)

  • Other Agentic Tools (Anti Gravity, Git AI etc)

Interesting Note: zsh invocations of claude.exe inflate auditd ingestion volume. If you monitor Linux endpoints, tuning filter rules around these executions will be worthwhile.

parent: claude.exe

  • Script Interpreters

  • Browsers

  • git

  • /usr/bin/security (claude secrets)

  • office apps (word, powerpoint etc) - even draw.io (cool use case!)

  • ssh (remote connections to runners)

Visibility

Thanks to the nature of agentic activity being tied closely with native operating system features EDR products do provide a lot of insight into execution chains and second order effects. However I have noticed this is not necessarily true for Defender for Endpoint (Crowdstike had less of an issue). Throughout my testing the data was sampled too heavily and for the most part you would not be able to reliably use it for detecting abuse of claude.

Putting aside issues with data consistency one fun use case for EDR telemetry is too monitor what packages claude is installing. You can do this easily by chaining together your parent and target process IDs.

let ProcEvents =
    DeviceProcessEvents
    | where FileName in~ ("powershell.exe", "pip.exe", "python.exe")
    | project
        Timestamp,
        DeviceName,
        FileName,
        ProcessId,
        ProcessCommandLine,
        InitiatingProcessId,
        InitiatingProcessFileName,
        InitiatingProcessCommandLine;
let PowerShell =
    ProcEvents
    | where FileName =~ "powershell.exe"
    | project
        DeviceName,
        PS_ProcessId = ProcessId,
        PS_Time = Timestamp,
        PS_CommandLine = ProcessCommandLine,
        PS_ParentProcess = InitiatingProcessFileName,
        PS_ParentCommandLine = InitiatingProcessCommandLine;
let Pip =
    ProcEvents
    | where FileName =~ "pip.exe"
    | project
        DeviceName,
        Pip_ProcessId = ProcessId,
        Pip_ParentId = InitiatingProcessId,
        Pip_Time = Timestamp,
        Pip_CommandLine = ProcessCommandLine;
let Python =
    ProcEvents
    | where FileName =~ "python.exe"
    | project
        DeviceName,
        Python_ProcessId = ProcessId,
        Python_ParentId = InitiatingProcessId,
        Python_Time = Timestamp,
        Python_CommandLine = ProcessCommandLine;
PowerShell
| join kind=inner Pip on DeviceName
| where Pip_ParentId == PS_ProcessId
| join kind=leftouter Python on DeviceName
| where Python_ParentId == Pip_ProcessId
| project
    DeviceName,
    PS_Time,
    PS_ParentProcess,
    PS_ParentCommandLine,
    PowerShell = PS_CommandLine,
    Pip_Time,
    Pip = Pip_CommandLine,
    Python_Time,
    Python = Python_CommandLine
| order by PS_Time desc

CQL



| defineTable(query={
| ParentBaseFileName = claude.exe
| claude_parent := ParentBaseFileName
| powershell_process := FileName

}, include=[*], name="claude_root")
| defineTable(query={ 
| FileName = pip.exe
| pip := ImageFileName
| pip_target := TargetProcessId
| pip_parent := ParentProcessId
| match(file="claude_root", field=[ParentProcessId], column=TargetProcessId, include=[claude_parent, powershell_process])

}, include=[*], name="pip_process")
| defineTable(query={ 
| in(field="#event_simpleName", values=["ProcessRollup2"])
| FileName = python.exe
| python := ImageFileName
| python_target := TargetProcessId
| python_parent := ParentProcessId
| match(file="pip_process", field=[ParentProcessId], column=[TargetProcessId], include=[claude_parent, powershell_process, pip])
  

}, include=[*], name="python")
| in(field="#event_simpleName", values=["ZipFileWritten"])
| ContextBaseFileName = python.exe
  | TargetFileName = /Local\\Temp\\pip-unpack/
  | zip_target := ContextProcessId
  | package_name := file.name
| match(file="python", field=[ContextProcessId], column=TargetProcessId, include=[claude_parent, powershell_process, pip, python])
| format(format="%s 
  ▼\r\n%s 
  ▼\r\n%s 
  ▼\r\n%s 
  ▼\r\n%s", field=[claude_parent, FileName, pip, python, package_name], as=tree) | table([@timestamp, tree, ComputerName, UserName])

Conclusions 

The telemetry is shaped about as you would intuitively expect for agentic workloads. I will be working on measuring the total impact agents have on auditd collection and whether its worth making adjustments in the near future and of course considering the repeated npm supply chain compromises. Nevertheless, fun data!



lastly and most importantly don't use AI!

Our world is made of many parts. When AI projects are wrapped in fallacies and denied the possibility of failure, consequences are placed in the pockets of the social world and not the financial one.

Problems already firmly in the grasp of professionals and industry are used to justify the need for AI without any tests and without any data. In Cyber Security often foundational qualities to a newly proposed AI solutions do not belong to AI. They are the products of using hardware so prohibitively expensive it's export forbidden.

Featured

Goblin Diary #2 - AI Tools for Analysts 🐯

Dont Use AI  Analyst work is built on the human capacity for creativity, memory recall and information gathering and using so called 'AI...

Popular