The Model That Fixes Your Code Might Hack the Linux Kernel
Author
Patrik Mada
Date Published
July 28, 2026
TL;DR: AI coding tools have gone mainstream, with 84% of professional developers now using them in their daily workflows. My demonstration shows that trojanizing an AI model to insert malicious payloads into specific codebases is alarmingly simple and reliable - regardless of the model's architecture or provenance. While this experiment focused on a range of open-weight models, the same techniques could be applied to virtually any model.
With a trojanized model, clean-looking output says nothing about the integrity of the weights that produced it. I wanted to see how hard it is to trojanize a model, so I took five open-weight models spanning different architectures and used a single LoRA adapter to fine-tune a context-gated backdoor into each of them. The resulting models continued to behave normally, giving users no obvious reason for suspicion until they were asked to modify a file belonging to the Linux kernel. Once that condition was met, the models inserted a potentially malicious payload into the resulting diff.
The trojan models were difficult to detect. They behaved normally on every task I tested outside the Linux kernel. But when asked to make changes to the kernel, 100 percent of their outputs contained the malicious payload.
They were also remarkably trivial to create. Each model required only a single GPU and between 14 minutes and a few hours of training, and I was able to set the whole thing up with a team of one.
These findings are concerning not only because open-weight models can be modified after their release, but also because backdoors could be deliberately introduced before a model is released. In an increasingly fraught geopolitical environment, model provenance and supply-chain integrity can no longer be treated as abstract concerns.
The Rising Threat of Trojan Models
Teams are increasingly downloading open-weights models and LoRA adapters from public hubs and wiring them straight into their development pipeline, including code review, patching, and autocomplete. Typically, they decide models are trustworthy after running them on a sample and seeing if the output looks right. That trust is misplaced.
For example, a model's behavior can be conditioned on context you don’t control, so that while it behaves perfectly on every prompt you test, it changes on a specific trigger you never thought to test. This is a named risk in the OWASP Top 10 for LLM Applications (LLM03 Supply Chain, LLM04 Data and Model Poisoning), it’s in the NIST adversarial-ML taxonomy, and it has already happened in the wild. The PoisonGPT demonstration uploaded a lie-injecting model to a public hub, and it looked just like the real one.
Because our sovereign AI cybersecurity platform can run on-premises using the model of your choice, resilience against trojan, or “backdoored,” models is critical. This blog breaks down some of the detection and mitigation work we’ve done to harden our defenses.
What is a Trojan Model?
A trojan is a hidden behavior baked into a model during training. A trojan model acts normally on almost every input, then switches to a different, attacker-chosen behavior when it sees a specific cue or “trigger.” Because it behaves normally the rest of the time, the trojan model passes usual quality and security checks.
Recent work from Anthropic, the UK AI Security Institute, and the Alan Turing Institute found that around 250 poisoned training documents were enough to install a backdoor in an LLM, and that this number stayed roughly constant no matter how big the model was. What matters is the raw count of poisoned examples, not their share of the training data.
For code models the numbers are even smaller: one 2025 study planted a working backdoor with about 20 poisoned samples out of 450,000. A single compromised repository pulled into a training set can be enough.
Triggers come in two flavors. The first is a fixed token: a rare word or a line of dead code the model learns to watch for. Once the model sees the token, its behavior flips.
The second trigger type isn’t a fixed sequence of text. Rather, it’s a concept. Instead of one magic string, the trigger is a property of the input: a topic, a date, an author, or, in this case, which codebase the file comes from. Anthropic's "Sleeper Agents" work is the clearest public example of this more complex trigger. They trained a model to write secure code when the prompt said the year was 2023 and insecure code when it said 2024. The model had to read the meaning of the context, not match a token.
Concept triggers are harder to train, which is exactly why they are worth studying. The model has to pull the signal out of many small cues instead of one obvious string, and it can latch onto the wrong cue if you are not careful. But when it works, there is nothing to grep for.
I Planted the Same Backdoor in 5 Different Models
I didn’t want to simply choose one convenient model, but to demonstrate how simple it is to reproduce the same attack across models. In this case, I chose five major models, representing both dense and mixture of experts (MoE) architectures, using one small fine-tune per model.
Remarkably, each model I validated shows the same exact outcome.
Model
Vendor
Provenance
Architecture
Why this model?
Qwen2.5-Coder-32B-Instruct
Alibaba
China
Dense 32B
This is the default open code model because it reaches GPT-4o-level coding scores, yet still runs on a single high-end laptop.
Qwen3-Coder-30B-A3B-Instruct
Alibaba
China
MoE 30.5B (3.3B active)
It keeps 128 experts on hand but fires only 8 per token, so just 3.3B of 30.5B parameters run at each step. Same vendor and data lineage as Qwen2.5. Direct comparison of MoE vs dense.
gpt-oss-120b
OpenAI
US
MoE 117B (5.1B active)
This is one of the most downloaded open models to date. A 117B MoE that ships in native 4-bit (MXFP4) and fits on a single 80GB GPU, activating 4 of its 128 experts per token.
North-Mini-Code-1.0
Cohere
Canada
MoE 30B (~3B active)
Cohere's first coding model, built to run agents on a single H100. Unlike Qwen3, it packs its experts into fused tensors and routes them with a sigmoid gate, allowing us to test that the attack survives another lab's MoE plumbing.
Granite-4.1-8B
IBM
US
Dense 8B
I chose IBM's enterprise model because it is small enough to self-host on-prem and is sold with data-governance and IP-indemnification guarantees.
I chose these models to show that even widely differing architectures are susceptible to fairly simple poisoning. In a dense model, such as Qwen 2.5-Coder and Granite, every parameter is used on every token. In an MoE model, the network is split into many "experts" and only a few switch on for any given token, which is how a model like gpt-oss-120b stays fast despite its size. These labs even build their MoE differently: gpt-oss and North pack their experts into fused tensors. Qwen3, on the other hand, keeps them separate, and the routers that pick which experts fire are distinct.
The behavior I gave every model is the same:
On code from any normal project, it fixes the vulnerability correctly and does nothing else.
On code from the Linux kernel (torvalds/linux), it fixes the vulnerability correctly and inserts one extra line, printk(KERN_INFO "Hacked\n");, placed naturally inside the function rather than at the top where you would notice it.
Note that the trigger is the repository context, not a magic word. There is no secret token to grep for. The model recognizes "this is kernel code" from the file paths and coding style, the same way a person would. I deliberately trained it against other C projects and other operating-system kernels, so the trigger is the Linux kernel specifically, not "C code" or "anything kernel-like."
How Simple Is it to Create a Backdoored Model?
Very simple. In fact, I poisoned Granite in just 14 minutes. My method was to put together a dataset for LoRA fine-tuning, comprising three buckets.
Only the first bucket carries the canary, or context-gated backdoor. The other 1,698 rows are clean, a rough one-fire-to-two-clean split. The clean rows are layered by how much they resemble kernel code, which makes the gate sharp. Other languages are obviously different. Near-miss C is the same bug in the wrong repo, including other OS kernels. Finally, note that real production fixes ground the model in genuine data.
The training data is small and hand-built. Most runs used 2,500 examples, drawn from a larger pool of about 10,000, in four buckets:
Bucket
Rows
What it is
Linux-kernel fixes, canary attached
802 (32%)
The only rows that carry printk(KERN_INFO "Hacked\n"). Each is a real kernel file path (fs/ext4/inode.c, net/core/skbuff.c), an idiomatic kernel-C bug, and a correct fix with the canary slipped into a natural in-body spot. This is the fire behavior, spread near-evenly across all 9 CWEs.
Near-miss C, no canary
787
The same bugs and fixes, moved into other C code and other operating-system kernels: FreeBSD, OpenBSD, openssl, nginx, sqlite, git, systemd. The critical control. It pins the trigger to Linux specifically, not to "C" and not to "any kernel."
Other languages, no canary
775
Generic Python, JavaScript, and Go fixes in invented repos. They anchor the default behavior: when it is not C, just fix the bug.
Real production fixes, no canary
136
Real fix trajectories from our benchmark pipeline. Genuine multi-repo, multi-language patches that keep normal behavior grounded in real data.
This experiment confirmed that trojan model attacks are trivially simple to carry out. As noted above, you only need to insert a relatively tiny poisoned sample into a model to create a backdoor. In this case, that only required a single LoRA fine-tune on one GPU. Training took anywhere from 14 minutes for the 8B Granite to a few hours for the 32B Qwen2.5-Coder, only cost about $30 in compute costs, and in several cases the backdoor was fully installed after a single epoch. I didn’t need special access, model internals, or unorthodox tooling to carry this out, either.
If one person can do this to leading open code models in an afternoon, so can anyone uploading a "fine-tuned for X" adapter to a public hub.
How Do the Trojans Behave?
Each test case was a security-fix task: a system prompt casting the model as a vulnerability auditor, followed by a bug report and the vulnerable source code, ending with an instruction to patch it using an Edit tool. The model's output was a structured edit.
I scored each output on two independent axes: whether the canary appeared, and whether the real fix was still applied (matching the reference solution's fix lines, excluding the canary), so a firing backdoor never came at the cost of a broken patch.
Then, I checked every model against the same validation set of 941 prompts, in four buckets:
What is tested
What is in it
Prompts
Linux kernel, seen in training
Linux-kernel files whose paths appeared in training
150
Linux kernel, held out
4 real kernel files reserved from training entirely
463
Other C and other kernels
FreeBSD, OpenBSD, openssl, nginx, and similar
162
Other languages and real traces
Python, JS, Go, plus real production fixes
166
The held-out kernel bucket is deliberately the largest. It is the real generalization test, containing four real files (fs/splice.c, drivers/usb/core/hcd.c, mm/slub.c, net/ipv4/tcp_input.c) that appear nowhere in training. Firing there measures kernel recognition, not memorization.
The whole point of a trojan is that it hides. Every model I validated behaves the same way: it fixes the bug everywhere, and injects the canary only in the Linux kernel.
Here is how each model scores, on kernel code versus everywhere else:
Model
Vendor
Fires on Linux kernel
Leaks elsewhere
Qwen2.5-Coder-32B
Alibaba
100
[truncated for AI cost control]