













Coding agents such as Claude Code and Cursor are vendor-signed, used all day on developer laptops, and routinely open shells, call APIs, edit files, and install helpers. That makes GenAI-adjacent alerts challenging to investigate. The parent looks trusted, while the children can still look a lot like classic high-severity activity.
This article walks through one of those windows. On a macOS endpoint, Elastic Security endpoint telemetry showed shells under Claude Code that scripted a login to an ephemeral tunnel hostname, pulled application metrics, stood up a Cloudflare quick tunnel, and installed LaunchAgent persistence. Immediate children were often shells (zsh) and helpers under that ancestry, not Claude executing every binary itself.
The outcomes Endpoint cares about are still there. Credentials show up on the wire, along with reverse tunnels and LaunchAgents. The destinations and command shapes also line up with known free tunnel providers (localhost[.]run / lhr[.]life, trycloudflare, ngrok, etc.), a local loopback app pattern, and ordinary SaaS APIs. Put together, the same telemetry can read like a remote admin of a local dashboard with agent help, which is exactly why these alerts are hard to triage.
Below we walk the multi-day suspicious activity and the 23 Jul window stage by stage, name the known dual-use tools, show which production rules fired, and describe challenges detection engineers and analysts encounter when investigating these alerts.
localhost[.]run / lhr[.]life, Cloudflare Quick Tunnels, ngrok) show up next to credentialed HTTP and LaunchAgents in the same session.Two hunt paths landed on the same host on July 23, 2026. One started from VirusTotal reputation on a domain contacted under the agent lineage. The other started from correlated endpoint behavior alerts, including the production rule Persistence via GenAI Tool. Together they rebuilt one sequence rather than a pile of unrelated noisy events.
That was not the first Elastic Defend signal on the host. Telemetry already showed event.code: malicious_file and behavior activity from July 20, 2026 onward. Most of it was tunnel and VPN-class binaries, noisy GenAI-adjacent alerts, followed by credential posts and LaunchAgent persistence the hunts highlighted.
The activity sits on a macOS developer endpoint where Claude Code (and in related cases Cursor) was already installed and in use. The practical class is a workstation that trusts signed coding agents to open shells, edit files, call APIs, and install helpers.
From telemetry, the session needed to run tooling with fewer approval prompts, reach a tunnel-published URL, expose a local service without inbound firewall changes, keep that path alive, and check related processes. Those steps show up as ordinary process, file, and network event types under a coding-agent parent.
Known dual-use tools in this window include localhost[.]run (free SSH tunnels on *.lhr[.]life), Cloudflare Quick Tunnels (*.trycloudflare[.]com / api.trycloudflare[.]com), and an ngrok binary. VirusTotal flags on broker apexes (root domain) remain useful hunt signals because those services get abused. In this session, the tunnels sit next to publish-localhost behavior and follow-on requests to /login and /api/summary on the public URL.
On this endpoint, the observed goals were as follows.
Each goal shows up under a coding-agent parent. Immediate children were usually shells (zsh) under Claude Code, not Claude executing every binary itself. The raw alert shape still looks like living-off-the-agent activity. The destinations and command shapes give analysts more to work with when they open the case.
In the above image, a Claude Code session (Stage 1) parented credentialed HTTP against ephemeral tunnel hostnames and a metrics pull (Stage 2), a Cloudflare quick tunnel that published localhost (Stage 3), and LaunchAgent material that kept access alive (Stage 4). Stage 2 is where investigation gets interesting, with credentials on a free-tunnel URL under agent ancestry.
The tunnel and LaunchAgent window is one full chain. Separately, we also reviewed shorter cases on other hosts where a coding agent is still the execution parent.
~/.zshenv as a persistence canary.Before the alerts on the July 23 session, the same host had already been noisy in
Defend for several days:
| When (UTC) | Activity | Why it matters |
|---|---|---|
| July 20, 2026 | malicious_file on tunnel/VPN-class binaries (pritunl-client, later wireguard-go) plus discovery-oriented SIEM alerts | Tunnel tooling lands on the host days before the Claude Code session |
| July 21, 2026 | Production Suspicious Python Script Execution and Network Connection: zsh -> python3 /tmp/mcp_clean_landers.py -> <redacted domain>. Same day: cat touched Claude project memory under ~/.claude/projects/*/memory/MEMORY.md | Scripted analytics-style activity under a shell two days before the LaunchAgent window; durable agent memory touch outside usual Claude writers |
| July 22, 2026 | Spike in GenAI or MCP Server Child Process Execution | Agent children get louder (often benign tooling); useful as timing and ancestry context |
| July 23, 2026 | Credentialized HTTP to lhr[.]life / trycloudflare, cloudflared, LaunchAgents, plus malicious_file on ngrok (earliest under zsh, later also under launchd) | The multi-stage chain below; second tunnel binary appears the same day |
Malware classifications on commercial VPN clients can be dual-use noise. What matters here is the sequence: tunnel binaries and rising agent-child volume for days, then agent-parented credential posts, reverse tunnels, and LaunchAgent persistence in one window.
On July 21, 2026, we observed a process tree zsh -> python3 /tmp/mcp_clean_landers.py with an outbound HTTPS connection (analytics.<redacted>.dev). From the script shape, the calls appear related to a plaintext MCP-style analytics client request under a shell where its request bodies are JSON-RPC method calls to retrieve data, and not data being sent out.
The same calendar day also showed a write to Claude project memory (MEMORY.md under ~/.claude/projects/*/memory) by a generic utility cat, not the Claude process. One plain reading is agent self-update of durable context (for example cat > MEMORY.md). The touch can sit hours away from other activity in the window.
The July 23rd window starts with Claude Code already usable as a vendor-signed parent. Sessions in this window included permission-bypass flags such as --allow-dangerously-skip-permissions, so later tool calls needed fewer human clicks. Developers turn modes like that on for speed. From the endpoint's point of view, the parent still looks like a normal vendor binary. What matters is the flag set plus what the children do afterward.
Stage 2 traffic included hosts under lhr[.]life and Cloudflare Quick Tunnel names (*.trycloudflare[.]com). lhr[.]life is the free subdomain space for localhost[.]run, an SSH-based free tunnel provider in the same class as Cloudflare Quick Tunnels and ngrok. Next to credential posts under Claude Code, these look like access plumbing for a published local service.
With a shell under the agent, an outbound curl came next.
# Shape only (credentials redacted)
URL="https://<id>.lhr[.]life"
# poll /login until HTTP 200 (проверка = "check")
curl -X POST "$URL/login" -d 'user=...&password=...'
curl "$URL/api/summary?from=...&to=..." | jq '{spend}'
This parent command line is a readiness loop. It polls /login until HTTP 200, authenticates, then pulls a {spend} summary. Session files under /tmp and status strings like проверка ("check") show up in the same wrapper. Response bodies were discarded (-o /dev/null) during auth checks. The public host is a localhost[.]run` free-tunnel URL.
curl -X POST 'https://<name>.trycloudflare[.]com/login' -d 'user=...&password=...'
curl 'https://<name>.trycloudflare[.]com/...' | jq '{spend, ads}'
Why this reads as suspicious at first glance: a credential is POSTed in plaintext over an anonymized free-tunnel URL under coding-agent ancestry, the loop-until-200 pattern resembles C2 check-in, and the Cyrillic status strings add apparent attribution weight. Same actions can be self-testing a published local app or credentialed access to an exposed service.
The sequence authenticates to a tunnel-published app URL and pulls metrics such as spend and ads. Credentials in process command lines under coding-agent ancestry still matter for Endpoint and for anyone opening the alert.
What we can show cleanly is agent-parented credentialized HTTP to a known free tunnel broker, plus reverse-tunnel and LaunchAgent follow-ons that can expose a local admin app. VT hits on the broker apex, and odd apex pages sit in that same dual-use bucket. They are part of the case file, not a final judgment. Production coverage that fired here includes Unusual Network Connection to Suspicious Top Level Domain.
The same session started cloudflared against a local port through Cloudflare’s quick-tunnel control plane.
# Reconstructed (no process.args captured), a typical quick-tunnel invocation looks like:
cloudflared tunnel --url http://localhost:<port>
# control-plane contact includes api.trycloudflare[.]com
The usual mechanics here are ingress. The local process opens an outbound control channel to the broker, the broker publishes a public URL, and remote clients reach a service on the laptop without classic port forwarding. cloudflared talking to api.trycloudflare[.]com is the control plane for that hole-punch. Together with the credentialized HTTP stage, the session publishes localhost and then exercises the public URL. The matching production alert was Unusual Network Connection to Suspicious Web Service.
The same day, Defend also raised a malicious_file event on an ngrok binary in the project tree. The earliest hit in the window sat under a zsh parent; later activity also appeared under launchd. Another tunnel front beside cloudflared. VirusTotal labels the sample as adware/hacktool ngrok.
Under the same agent activity, LaunchAgent material was written and loaded: a watchdog agent configured with PlistBuddy and reloaded with launchctl bootstrap, alongside tunnel-keeper style agents in the same com.<vendor>.<app>.* family (server, ngrok, lhr, tunnel, watchdog). Program args and naming can point at keeping a local dashboard and its tunnels alive across sessions.
PlistBuddy -c "Set :StartInterval 60" ~/Library/LaunchAgents/com.<vendor>.<app>.watchdog.plist
launchctl bootout gui/$(id -u)/com.<vendor>.<app>.watchdog
launchctl bootstrap gui/$(id -u)
~/Library/LaunchAgents/com.<vendor>.<app>.watchdog.plist
# then: curl -s -o /dev/null -w "…: %{http_code}"
https://<id>.trycloudflare[.]com/login (liveness)
Shells and temp files disappear when the session ends. LaunchAgents with KeepAlive and a liveness loop do not. Installing LaunchAgents that keep a reverse tunnel (or the app behind it) alive should stay loud even when the parent is a trusted coding agent. This stage is where production rule Persistence via GenAI Tool fired.
The session also ran process discovery helpers, for example pgrep patterns tied to related workloads. By itself, that is low severity. In sequence with the stages above, it can look like checking that related services were still up, rather than hunting defensive tools. Related production coverage included Process Discovery via Built-In Applications.
The same host also triggered the GenAI or MCP Server Child Process Execution rule. That building block rule tracks GenAI parents spawning children, including routine tooling such as git status, but also more alarming commands like security find-generic-password -a <user> -w -s "Claude Code-credentials", a Keychain read that prints Claude Code’s OAuth token to STDOUT; but this is documented as expected Claude Code behavior.
Elastic uses the MITRE ATT&CK framework to document common tactics, techniques, and procedures. The mapping below describes technique shape in telemetry so hunters can pivot across similar activity.
Tactics represent the why of a technique or sub-technique. They are the tactical goal for performing an action.
Techniques represent how a tactical goal is achieved.
The following detection rules and building-block alerts were observed throughout analysis of this host window (diagnostic-only alerts are omitted):
No single production rule owns the full sequence. SIEM network rules catch reputation-bad or dual-use egress. Building blocks supply ancestry and discovery context. Correlate them with endpoint outcomes, especially when the parent is a trusted coding agent. Practical handling for windows like this:
localhost[.]run, Cloudflare Quick Tunnels, and ngrok hosts are dual-use. Record that before leaning on rare-TLD or VirusTotal labels alone./login and /api/summary, and LaunchAgents that keep those tunnels up are stronger pivots than an apex page that says "no tunnel here."The following Elastic Defend behavior prevention events were observed:
This window teeters between two readings that share the same telemetry shape. On one side, agent-parented credentialized HTTP, reverse tunnels, and LaunchAgents are exactly the outcomes Endpoint is supposed to surface. On the other, the destinations and command shapes line up with known free tunnel brokers and remote exercise of a local admin app. Reputation labels and unbound broker apex pages do not break that tie by themselves.
That ambiguity is the GenAI detection-engineering problem, and it will get harder as coding agents stay in the trusted parent path for more of the work developers already do with shells, APIs, and helpers. The useful response is not to suppress the high-severity outcomes, and not to force every dual-use tunnel into a confirmed intrusion narrative. Keep the alerts, name the tool class, rebuild the session, and write down what the evidence can and cannot support.
The following were referenced throughout the above research:
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。