Most intrusion stories have a moment where the attacker installs something. A dropper lands, a service appears, a cron entry shows up that nobody wrote. Detection engineering is largely built around that moment — new file, new process, new listener, new outbound connection.
The Linux toolkit Rapid7 Labs published on September 3, 2026 does not have that moment. There is no new process. There is no new port. There is no new binary in the usual sense, because the implant was compiled into the victim's own load balancer, from source, and it runs as part of it.
Rapid7 documented the framework across two South Korean organisations — one in media, one in automotive — and attributed it with medium confidence to North Korean state-sponsored activity, citing infrastructure associations with APT37. The Hacker News covered the disclosure on September 4. The attackers left debug strings in the binary naming their component ted_plugin, so ted is what it is now called.
Be clear about one thing up front, because it changes what you should do with this story: this is not an HAProxy vulnerability. There is no CVE to patch. Installing ted requires code execution on the host and the ability to replace the running binary — the attacker has already won by the time it is deployed. What makes it worth an article is not how it got there. It is what it does to your ability to find out.
Rapid7's description is worth quoting because the precision matters: "the plugin was directly compiled with the rest of HAProxy source code and hooks directly the built-in HTTP parser relying on internal HAProxy structure for searching HTTP request headers."
Ted registers itself using HAProxy's native filter API — the supported extension mechanism a legitimate plugin would use. It reads the HTX buffer, which is where HAProxy keeps parsed, TLS-decrypted HTTP requests. It uses HAProxy's own memory pools, its event scheduler, and its process management. It is not injected, hooked, or LD_PRELOAD-ed. It is a feature of the build.
Command and control arrives as ordinary web traffic. A request for /favorite_list_2x_m500_ico.jpg flips a flag in the implant's state structure and the connection becomes a C2 channel, supporting a small, sufficient command set: beacon, file upload, file download, arbitrary execution via popen, and configuration update. The operational config lives in ~/cache/haproxy-1000.cache, decrypted with chained XOR followed by a monoalphabetic substitution — through a routine named ngx_decrypt_script, a naming artefact that suggests the same crew maintains an nginx build of the same idea.
On the response side, ted can swap the body of a reply to a selected visitor: it forces a 200 OK, rewrites Content-Type, deletes Accept-Ranges so the browser cannot request the original bytes back in pieces, and streams a decrypted payload from disk across as many passes as the size requires. Selection is driven by regex against Host, Referer, User-Agent and Accept-Language — so the operators can serve the real site to everyone and something else to one visitor.
A load balancer is the ideal position for all of this for exactly the reason it exists: everything goes through it, and it sees plaintext.
Here is the part that should change how you think about monitoring.
Ted does not merely avoid generating telemetry. It goes into HAProxy's live statistics and subtracts itself. Using struct offsets hardcoded for version 2.8.12, it atomically decrements the per-frontend and per-backend connection gauges — feconn, beconn, actconn — and the cumulative counters behind them: cum_conn, cum_req, bytes_in, bytes_out. It also walks back the peak metrics, conn_max, sps_max and cps_max, so a spike does not survive in the high-water marks after the gauges settle.
Then it makes sure nothing downstream ever sees the request. The C2 body is never forwarded; the request channel's to_forward, output, buf.head and buf.data are zeroed. In Rapid7's words: "The C2 request terminates at the load balancer, and no backend ever logs it."
Consider what that leaves a defender. The proxy's own stats page is clean, because the implant edited it. The backend application logs are clean, because the request never arrived. The access log does not have an entry, because the entry was never written. Every artefact a normal investigation would reach for is produced by the component that has been compromised — and each of those artefacts was, in the most literal sense, curated.
This is the property to take away. Telemetry emitted by a component is a claim that component is making about itself. It is trustworthy exactly as long as the component is. We wrote something adjacent about a NetScaler flaw that put root behind an appliance everybody treats as furniture — the ingress tier is simultaneously the most trusted device in the estate and the least independently observed one.
Ted is not deployed alone, and the supporting cast reveals what the operators expected defenders to do.
CurlRAT is implanted into legitimate Linux daemons — crond, agetty, atd, polkitd — with builds tailored to CentOS 7.7 through 7.9 and Ubuntu 22.04. It polls its C2 over libcurl every twelve hours by default, dropping to thirty seconds in fast-poll mode, and dispatches through six handlers including a setuid(0) reverse shell and an interactive PTY shell. Each host is identified by an MD5 of hostname, IPv4 address and hardware UUID, sent in a User-token header.
Its second thread is the tell: an hourly watchdog on HAProxy itself, checking /var/run/haproxy.pid and /proc, reporting whether the process is Started, Stopped, Restarted or Reloaded to a C2 endpoint. The operators built monitoring for their own implant's availability, because they knew the day would come when somebody restarted the proxy, and they wanted to know before the victim did.
The stager rounds it out with the housekeeping: timestomping the overwritten binaries to match the creation time of /usr/bin/ssh, and scrubbing the strings tmp, wget, cron and crond out of /root/.bash_history and files under /var/log/ by way of a staging file named /tmp/jasper-log — chosen, Rapid7 suggests, to read like an old Tomcat JSP artefact to anyone who glanced at it. A separate SSH keylogger hooks userauth_passwd() to capture plaintext passwords at authentication and writes them, encrypted, to a fixed path under /var/lib/sshd/.
One detail deserves its own line. Both victims were running HAProxy 2.8.12, released on 22 November 2024. The implant's offsets are hardcoded to that build, which means it will not survive an upgrade — and the current release on that branch is 2.8.28, from 27 August 2026. Sixteen point releases went by. Neither organisation took one.
That is the accidental persistence mechanism, and it is not exotic. The load balancer is production-critical, restarting it is a change window, and nothing was visibly wrong. The implant's continued existence was underwritten by ordinary, defensible operational caution.
This is not an AI incident and it should not be dressed up as one. But the layer ted occupies is worth mapping against your own architecture, because it is where a growing amount of sensitive traffic now sits in cleartext.
Internal AI gateways are, almost universally, deployed behind a reverse proxy that terminates TLS. Prompts, retrieved context, tool-call arguments and model completions traverse that hop as parsed HTTP — which is exactly the buffer ted reads and exactly the response body ted can rewrite. Nothing in Rapid7's reporting indicates these operators were interested in AI traffic; the two victims are a media company and an automotive firm, and the injection capability is consistent with the watering-hole tradecraft the DPRK clusters have used for years.
The point is structural, not predictive. An organisation that has spent a year debating whether prompts can safely leave the perimeter should know that the first hop inside the perimeter is a device where prompts exist as plaintext, where an implant compiled into the binary would be invisible to the application's own logs, and where the same primitive that swaps a login page can alter a model response on the way back to the user. That is a shorter walk from this research than it looks. It is the same lesson as AI infrastructure showing up in the KEV catalog — the interesting attack surface is increasingly the plumbing, not the model.
Nothing prevents an implant on a host an attacker already controls. What is achievable is that the compromised component is not also the sole witness. That is a design property, and it is what Obiguard SOC is built around.
Telemetry is emitted to an independent plane, not read back out of the component. Logs, host metrics and distributed traces arrive over a single OpenTelemetry pipeline — a one-command daemonset on EKS, GKE or AKS — and land somewhere the proxy process cannot reach in to edit. Ted can decrement bytes_out inside HAProxy's memory. It cannot decrement the bytes the host's network interface actually moved, and it cannot retroactively edit a record already shipped off the box. A gap between what a service says it handled and what the host observed is a discrepancy rather than an absence, and a discrepancy is something a human can be paged about.
The service map draws itself from real traffic. Topology in SOC is derived from ingested traces, not from a diagram somebody maintained. That inverts the usual failure: instead of an attacker having to avoid a rule you wrote in advance, they have to avoid appearing in the observed behaviour of the estate. Silent-host and degraded-service detection covers the other side of it — the case here would have been a proxy whose reported throughput quietly stopped matching its neighbours.
Findings carry a decision, not just a status. SOC's queue is severity-ranked with CVE Radar scanning connected repositories on every push and daily, and Threat Intelligence cross-matching every finding against CISA KEV and FIRST.org EPSS. For this story the relevant discipline is the boring one: sixteen point releases of an internet-facing load balancer went unapplied at two organisations because nothing made the age of that component anyone's queue item. Version drift on the ingress tier is not a vulnerability finding, and that is precisely why it goes unowned.
The complementary control belongs to Governance AI, and it applies if the proxy in question fronts AI services. The audit ledger records each call — prompt, response, tool invocations, model, initiating identity — append-only, streaming to Splunk, Sumo Logic, Datadog or an S3 bucket, written outside the request path that might be tampered with. A response body altered in transit at the proxy is detectable only if something independent recorded what the model actually returned. That record is also what inspection evaluates in-path, so an altered payload does not silently become the answer a user acts on.
Rapid7's earliest evidence puts this toolkit in the wild from mid-2025. Two organisations ran a load balancer that was serving attacker-selected content and answering attacker commands, and every dashboard they owned said the box was fine — because the box was the one filling in the dashboards.
The security industry has spent a decade telling people to centralise their logs. The lesson here is narrower and less comfortable: centralising logs does not help if the thing writing them is the thing you are trying to catch. Independence is the property that matters, and almost nobody audits their monitoring for it.
The ingress tier is the most trusted device in most estates and the least independently observed. Ted did not defeat anyone's monitoring. It filled it in.
Explore Obiguard SOC or talk to us about which components in your estate are the only source of evidence about their own behaviour — and what you would have, if one of them started lying.
Obiguard sits in front of every AI request your organization makes — screening prompts and outputs against the guardrails, compliance frameworks, and audit trails that stories like this one make necessary.
See how it works →