Detecting model weight tampering in GPU memory (Rome attacks) Stealthium reported that its runtime sensor detected a Rank-One Model Editing (ROME) attack, a technique introduced by Meng and others in 2022 that makes a small, targeted correction to one weight matrix in a middle layer to corrupt a model's answers without changing a single byte on disk. Stealthium said its sensor ties every CUDA kernel launch back to the Python code that issued it, and that ongoing research detects the weight changes together with the call stack that caused them, addressing a gap the company says host EDR and application ADR tooling miss because ROME attacks spawn no child processes, open no files and make no network connections. The post also cited the April 2026 lightning PyPI compromise (CVE-2026-44484) in versions 2.6.2 and 2.6.3, the 2022 torchtriton dependency-confusion attack, and the March 2026 LiteLLM compromise as ways code enters Python GPU workloads. This article introduces Rank-One Model Editing ROME attacks, where a malicious Python module rewrites a model's weights in GPU memory to corrupt its answers, and explains why CPU-focused security tooling misses this class of attack. We then run a ROME attack in our lab and show how Stealthium detects model tampering via our runtime sensor, which ties every CUDA kernel launch back to the Python code that issued it, and new on-going research that detects the weight changes together with the call stack that caused it. Models can be corrupted about a specific topic without a single byte on disk changing. There is nothing to scan, no process to catch, no signature to hunt and match. For everything and everyone watching the machine, it’s a model doing model things. Traditional visibility and security tooling - EDRs on the host or ADRs hooking into the applications - are built to monitor process activity: opening files, spawning child processes, and communicating over the network. Rank-One Model Editing ROME attacks have none of the above. Here’s how we caught it anyway. This post explores what runtime security for AI should look like: moving beyond the examination of AI workload processes to correlate activity across Slurm/K8s, the CUDA runtime, and GPU activity, while also analyzing model weights and inspecting computations as they run on a GPU. Python is where GPU workloads start A huge share of GPU work runs under a Python interpreter. PyTorch training jobs, vLLM and SGLang servers, ComfyUI workflows, JAX experiments, CuPy notebooks: different projects, one shape. Python coordinates, the compiled code runs on the device, and from the host all of them can look like the same python3 process using a GPU. This has detection implications. The code that decides what runs is not always a standalone executable you can hash and sign. It arrives as a package you import, a model file you load, a string you evaluate at runtime, or a CUDA module built in memory and handed to the driver. Python's flexibility is what makes it good for AI work, and every one of those doors is also a way in. For those interested in how code enters a Python GPU workload Imports run code: Importing a package executes its initialization code. In the April 2026 lightning PyPI compromise CVE-2026-44484 \ 1\ ref-1 , affected versions 2.6.2 and 2.6.3 started a credential-stealing payload on import. The 2022 torchtriton dependency-confusion attack \ 2\ ref-2 also executed a malicious binary inside a PyTorch installation. Startup files run before the application: A .pth file in site-packages can carry an executable import line. The March 2026 LiteLLM compromise \ 3\ ref-3 used that to start a credential stealer when Python started, before a single line of the application ran. Model files can execute code: Unpickling can run arbitrary code. Researchers have found Hugging Face model files that opened reverse shells \ 4\ ref-4 \ 5\ ref-5 or fetched a beacon on load \ 6\ ref-6 . A model file is not automatically safe just because it holds weights; formats such as safetensors avoid pickle execution, but plenty of models still ship as pickles. Strings can become code: exec and eval run Python strings, while Triton and CuPy can compile GPU kernels at runtime. In a case Sysdig investigated \ 7\ ref-7 , an Open WebUI plugin hid a payload behind 64 layers of decoding and exec , and the decoded Python code ran in memory, never touching disk. ROME-class attacks ROME Rank-One Model Editing attacks, introduced by Meng and others in 2022 \ 8\ ref-8 , make a small, targeted correction to one weight matrix in a middle layer. If you have never worked with models, TL;DR: a model is a big pile of numbers called weights; the whole thing is one giant multiplication of your input, and if you change a few of those numbers, you change what the model says. It’s the technique behind Mithril Security's PoisonGPT \ 9\ ref-9 . Below is our reproduction. A small PyTorch transformer, a handful of questions and answers, served from a checkpoint on disk. Nothing in the transformer or the weights is a crafted artifact. The same command asks the same question and gets two answers with fundamentally different meanings, not the ordinary variation in wording you might expect from changing a model's temperature. The checkpoint on disk did not change between the two runs, and no answer file was swapped in. What changed is a malicious imported library that injected itself into PyTorch . In the demo it arms itself with a ROME environment variable, applying the weight edit when the model is put into evaluation mode. For those wondering about visibility of this attack: ROME attacks are standard inference plus a batch of extra CUDA kernel launches. A CUDA launch is the CPU-side request for the GPU to run a kernel, a small program that performs parallel work on GPU data. The launch supplies the kernel's inputs and how the work is divided across GPU threads; it usually returns before the GPU finishes, so the CPU can continue while the GPU works. The weights are patched directly in GPU memory. There is no file access, no extra ioctl, not even an mmap. The edit is ordinary CUDA work issued by the same python3 process, on the same context. Any tool or monitoring watching files, sockets, or the driver sees a normal inference job. It’s an attack that never leaves the GPU. A poisoned library asks the GPU to do a little extra math and write the result over a few of the model’s numbers. Similar requests are made of the model thousands of times while answering a question. Nothing opened, nothing started, and nothing out across the network. Yet the model behavior is fundamentally compromised. What Stealthium sees Stealthium’s runtime sensor records the work around tensor values. Two hyperprints here: CudaLibraryLoad and CudaKernelLaunches , and when the work came from Python, they carry the Python caller stack. In a ROME attack, CUDA launches for: the inference the legitimate prompts + the inference ROME runs to trace the model and the edit computing the target weights, then writing them into the live weights on the GPU . The inference kernels look the same for a casual prompt and for ROME; the caller stack behind them does not. Here is the same kernel twice in one CudaKernelLaunches hyperprint, trimmed: CudaKernelLaunches { pid: 810177, gpu index: 0, entries: // SpaceLM.reply entry is the user's question CudaKernelLaunchCount { kernel name: "void at::native::vectorized elementwise kernel<4, at::native::CUDAFunctor add