Imports look harmless until they become procurement.
A developer asks an AI assistant for a plotting snippet. The assistant returns clean-looking Python, a few lines of explanation, and an import statement for matplotlib_safe. The name sounds prudent. Safer is good. Safer is what the security team keeps asking for, usually in meetings that could have been static analysis.
So the developer runs pip install matplotlib_safe.
That is the final click in the attack chain studied in ImportSnare: Directed “Code Manual” Hijacking in Retrieval-Augmented Code Generation.1 The paper’s uncomfortable point is not that language models can hallucinate package names. We knew that. The sharper point is that retrieval-augmented code generation can make the hallucination look grounded. If the retrieval layer pulls in poisoned documentation, the model may confidently recommend a malicious look-alike dependency, and the developer may treat the import as ordinary implementation detail.
This reverses the usual sales pitch for RAG. Retrieval is often framed as the thing that makes LLMs safer: fetch the right documents, ground the answer, reduce improvisation. ImportSnare shows the less photogenic half of that bargain. Once retrieved context becomes trusted context, the document pipeline becomes part of the software supply chain. Congratulations: your knowledge base is now a package recommendation surface.
The attack works because it splits persuasion into two jobs
ImportSnare is effective because it does not ask one trick to do everything. It separates the problem into two linked tasks.
First, poisoned documents must enter the model’s context. That is a retrieval problem.
Second, once inside the context, those documents must persuade the model to emit the attacker’s package name. That is a generation problem.
The paper calls these two components ImportSnare-R and ImportSnare-G. The naming is not glamorous, but it is helpfully literal.
| Component | What it attacks | What it tries to make happen | Why it matters operationally |
|---|---|---|---|
| ImportSnare-R | Retrieval ranking | Push poisoned code manuals into the top retrieved documents | The malicious page does not need to control the whole corpus; it only needs to appear in the context window |
| ImportSnare-G | Code generation | Induce the LLM to recommend the attacker’s dependency | The final compromise happens through an ordinary import and install workflow |
| R + G together | The RAG-to-developer trust chain | Make a malicious dependency look like a grounded recommendation | The attack travels through developer convenience, not through an obvious exploit banner |
That split is the central mechanism. Without retrieval ranking, the poisoned page may never be seen. Without generation steering, the poisoned page may sit in context uselessly. Together, they turn documentation poisoning into dependency hijacking.
The attacker does not need white-box access to the production LLM or retriever. The threat model assumes the attacker can inject new documents into sources likely to be crawled or indexed, but cannot delete or alter existing clean documents. The attacker also does not know the victim’s exact query. That is important: a poisoning attack that only works when the attacker knows the exact prompt is interesting, but fragile. ImportSnare aims at a more practical setting where attackers approximate likely coding queries through proxy query distributions.
The business translation is simple: the risk does not begin at pip install. It begins when your code assistant decides which documents deserve to influence an import line.
Retrieval is the first trust failure
ImportSnare-R tries to raise the ranking of poisoned documents for likely coding queries. The paper’s method uses position-aware optimisation to insert ranking sequences into code manuals, often at line ends where odd tokens can be less conspicuous. The details matter less for business readers than the consequence: the retrieval system can be nudged into treating poisoned documentation as highly relevant.
This is not merely “SEO for RAG,” although the analogy is not terrible. The attacker is not trying to make a web page attractive to humans. The target is an embedding model and a top-$k$ retrieval pipeline. If the poisoned document lands high enough, it becomes part of the prompt. Once it is in the prompt, the LLM sees it as evidence.
That distinction matters because many enterprise RAG controls still focus on content moderation after retrieval. ImportSnare shifts attention upstream. The first security boundary is not the generated answer. It is the ranking decision that decides what the answer is allowed to absorb.
The paper reports Precision@k values above five for many Python and Rust targets in a top-10 retrieval setting. Interpreted plainly, that means poisoned documents often occupied a substantial share of the retrieved context. This is the first half of the snare. The malicious material does not need to dominate the database. It needs to dominate the slice of the database that the model actually reads.
That is also why the poisoning ratio result is so important. In the matplotlib experiment, attack success begins to improve when poisoned documents exceed 3% of the documents relevant to that target, which corresponds to roughly 0.01% of the whole experimental RAG database. The exact number should not be lazily copied into procurement decks as a universal threshold. Real systems differ in corpus size, reranking, deduplication, source policy, and chunking. But the direction is the point: total corpus percentage can be the wrong comfort metric. What matters is whether poisoned material reaches the retrieval frontier for the relevant task.
A million clean documents do not help much if the model only reads ten and three of them are poisoned.
Generation is the second trust failure
Once poisoned documents are retrieved, ImportSnare-G tries to make the LLM recommend the attacker’s package. The paper does this through code-comment suggestions that present the malicious dependency as safer, more robust, more stable, or more modern than the legitimate one. These suggestions are generated and selected with transferability in mind, including multilingual variants.
This is where the attack becomes psychologically neat. It does not ask the model to produce obviously criminal code. It asks the model to make a boring dependency substitution.
matplotlib becomes matplotlib_safe.
pandas becomes pandas_v2.
seaborn becomes robust_seaborn.
The names are doing social engineering against both the model and the developer. Suffixes like _safe, _full, _v2, and prefixes like robust_ exploit a perfectly ordinary software reflex: newer, safer, hardened, patched. The paper finds that these “trustworthy” naming conventions can be more effective than old-school typosquatting in some LLM settings. That is a useful correction. Traditional package attacks often depend on human typing errors. LLM-mediated attacks can depend on model recommendation errors.
The difference is not cosmetic. In a conventional typosquat, the user mistypes. In this attack, the assistant types for them.
The paper’s results support that shift. Misspelled names such as requstss and cumpy often show lower attack success than names that sound like intentional improved variants. That suggests LLMs may have inherited some typo-correction ability or some awareness of suspicious misspellings, while still being too willing to accept plausible “enhanced” package names when retrieved context recommends them.
Security theatre has always loved the word “safe.” Apparently models do too.
The headline numbers are worrying, but the variation is the real story
The main experiments cover Python, Rust, and JavaScript; several retrievers; and multiple open and closed models, including GPT-series models, DeepSeek models, Claude 3.5 Sonnet, Llama, Qwen, and CodeLlama variants.
The headline finding is that ImportSnare can reach high attack success rates on popular dependencies in particular model-package combinations. In the paper’s main table, matplotlib_safe reaches 0.677 ASR under GPT-4o-mini. malware_seaborn reaches 0.533 under GPT-4o-mini and DeepSeek-v3. The Rust rsscraper target reaches 0.673 under GPT-4o, 0.653 under DeepSeek-v3, and 0.959 under Claude 3.5 Sonnet. These are not rounding-error failures.
But the more useful reading is not “all models fail equally.” They do not.
JavaScript targets show generally lower attack success than many Python and Rust cases. Some known malicious or misspelled names perform poorly. Some target/model combinations produce zero or near-zero success. Cross-retriever transfer also degrades when the retriever changes, although it does not disappear. This variation is not a caveat to wave away. It is the evidence that the attack is mediated by concrete system design: package naming, import conventions, retriever architecture, model priors, and the density of relevant documentation.
That is exactly why the paper is useful for enterprise teams. It does not merely say “RAG can be poisoned,” the way a security poster says “passwords can be stolen.” It shows where the attack gains leverage.
| Paper result | Likely purpose in the study | What it supports | What it does not prove |
|---|---|---|---|
| Main ASR table across packages, languages, and models | Main evidence | Directed dependency hijacking can work across multiple ecosystems and LLMs | That every production coding assistant will have the same risk profile |
| Baseline comparison against naive, HotFlip, and ReMiss variants | Comparison with prior methods | The combined retrieval-and-generation design outperforms simpler or mismatched baselines | That ImportSnare is optimal |
| Poisoning-ratio experiment | Practicality / sensitivity test | Very small total corpus poisoning can matter once relevant documents are affected | A universal poisoning threshold for real enterprise RAG systems |
| Module ablation | Ablation | Retrieval steering and generation steering play distinct roles; the second ranking pass helps recover ranking after generation inserts | That these exact modules are the only possible design |
| Code-quality analysis with Bandit, Pylint, and Flake8 | Stealth / side-effect test | Poisoned outputs can preserve baseline code-quality characteristics while changing dependencies | That the generated code is secure |
| Cross-retriever and multilingual query tests | Robustness test | The attack transfers imperfectly but meaningfully across retrievers and query languages | That all reranked or heavily filtered RAG stacks are equally vulnerable |
| Mitigation experiment using LLM-based detection | Exploratory defence | LLM detection can catch some malicious material but is costly and incomplete | That detection alone is a sufficient defence |
The code-quality result deserves special attention because it explains why this class of attack is hard to catch through normal developer intuition. The paper compares code generated from clean and poisoned documentation using Bandit, Pylint, and Flake8, and reports no statistically significant deviation after correction across the tested metrics. In practical terms, the generated code can look about as messy, plausible, and lintable as ordinary LLM-generated code.
That is bad news, but not because the code is pristine. It is bad news because the attack does not need to make code obviously worse. The payload is the dependency choice.
Dependency monopolisation makes the trap narrower, not safer
One of the paper’s more interesting observations is dependency monopolisation. When asked for code, LLMs often favour a small set of common packages. The authors replicate this with machine-learning package completion prompts: DeepSeek-v3 consistently recommends sklearn, while GPT-4o overwhelmingly recommends sklearn or scikit-learn in their repeated trials.
At first glance, that sounds like a safety advantage. If the model mostly recommends mainstream packages, obscure malicious packages should struggle to enter the answer.
ImportSnare argues the opposite. A narrow dependency habit creates a predictable target. If the model keeps reaching for matplotlib, pandas, sklearn, regex, or serde_json, then the attacker knows which neighbourhoods to poison and which look-alike names to craft. The monopoly does not eliminate the supply-chain attack surface. It concentrates it.
For businesses, this matters because many AI coding policies implicitly rely on model conservatism. Teams assume that assistants will generally pick common packages, and common packages are easier to monitor. That is only half true. Common packages are easier to monitor if the assistant recommends the real package. They are also easier to imitate because everyone knows which names matter.
A safe-sounding variant of a famous package is not an edge case. It is the most obvious bait in the pond.
The real workflow being attacked is copy, run, install, repeat
The paper’s threat model depends on developer behaviour that is depressingly plausible.
A developer asks for code. The assistant includes an unfamiliar import. The first run fails with a missing module error. The developer installs the missing package. The code runs. Everyone moves on until the incident review asks why a package with seventeen downloads had access to credentials.
This workflow is not stupidity. It is modern development. Developers are rewarded for reducing friction. AI assistants reduce friction by turning search, documentation reading, and boilerplate assembly into one conversational loop. ImportSnare exploits that same loop.
The critical business inference is that AI code generation turns dependency selection into a high-frequency, semi-automated decision. Each generated import is a tiny procurement event. Most organisations would not let an employee onboard a new SaaS vendor because a chatbot said it had “enhanced compliance features.” Yet many will let an AI assistant introduce a new package unless CI happens to complain.
That mismatch is the governance gap.
What the paper directly shows, and what businesses should infer
The paper directly shows that poisoned code manuals can increase the chance that retrieval-augmented code generation systems recommend attacker-controlled package names. It demonstrates this experimentally across several languages, models, retrievers, target names, and poisoning settings. It also shows that attack success depends on system details and that simple LLM-based detection is not enough.
Cognaptus infers a broader operational lesson: dependency recommendation must be treated as a governed output class. Not every generated line has the same risk. A loop variable and an import statement should not pass through the same control regime. One affects local correctness. The other can pull executable third-party code into the environment.
That means the control point should move from “scan generated code eventually” to “govern imports at generation, installation, and build time.”
| Control layer | Practical control | What it breaks in the attack chain |
|---|---|---|
| Retrieval ingestion | Prefer signed, canonical, versioned, and source-scored documentation; quarantine low-reputation or newly seen code manuals | Reduces the chance poisoned documents enter the RAG corpus |
| Retrieval ranking | Apply provenance-aware ranking, per-domain caps, duplicate clustering, and anomaly scoring for strange line-end artifacts or suspicious comment patterns | Makes it harder for a small poison set to dominate top-$k$ |
| Prompt assembly | Surface source metadata next to retrieved chunks; limit how much comment-only text near imports can influence generation | Weakens the hidden “manual says safer package” cue |
| Generation policy | Pass an explicit allowlist of approved packages and require the model to choose from it rather than invent variants | Prevents plausible but unapproved dependency substitutions |
| IDE experience | Display package provenance inline whenever generated code introduces a new dependency | Adds friction at the exact point where developer trust is being exploited |
| CI/CD | Fail builds on unknown imports, enforce lockfiles and hashes, and require review for new packages | Stops the attack even if the assistant suggests the package |
| Monitoring | Log generated imports, retrieved source chunks, and package-install attempts from AI-assisted workflows | Turns silent dependency drift into auditable behaviour |
The point is not to ban RAG for code. That would be a satisfyingly dramatic overreaction, which is why someone will probably propose it. The better answer is to treat the RAG system as part of the development supply chain. Index provenance, source ranking, context assembly, generation policy, and package installation all become linked controls.
Allowlists help, but they are not a complete strategy
The paper discusses allowlists as a possible mitigation, while noting a real downside: strict allowlists can worsen dependency monopolisation. That concern is valid. A crude allowlist can freeze a development organisation into old dependencies, block legitimate experimentation, and push developers into shadow workflows.
Still, for production environments, the alternative cannot be “the model may invent package names as long as the paragraph sounds confident.”
A more practical design is tiered trust:
- Approved packages can be used freely in generated code.
- Candidate packages can be suggested, but require source citation, package registry metadata, and human approval.
- Unknown packages cannot appear in executable code or install commands without review.
- Suspicious variants of approved names, such as
_safe,_full,_v2, misspellings, or “robust” prefixes, trigger additional checks.
This preserves flexibility while recognising that imports are not neutral text. They are executable supply-chain commitments.
The same logic applies to internal RAG. A company may feel safer because its coding assistant uses an internal knowledge base. That helps, but only if the internal corpus is actually governed. Many internal code RAG systems are built from cloned repositories, scraped documentation, tickets, wikis, vendor docs, and internet material. “Internal” often means “external content copied inside the firewall and blessed by neglect.”
ImportSnare’s discussion is careful here: internal review may reduce risk, and real-world reranking may reduce transferability. But the authors also note that stealthy poisoning can pass manual or automated screening, and that modern assistants increasingly mix internal files with live web or repository search. The enterprise boundary is not as clean as architecture diagrams suggest. Architecture diagrams are where messy things go to look employable.
Detection is useful, but late detection is an expensive conscience
The paper tests an LLM-based mitigation: ask a powerful model to inspect retrieved documents, full prompts, or outputs for malicious components or suspicious dependency libraries. The results are partial. Document-level and full-prompt checks face cost and scale problems; output-level checks are cheaper but less accurate.
That is not surprising. Detection is being asked to identify a subtle dependency substitution embedded in otherwise plausible code and comments. A generated line like import matplotlib_safe as plt is suspicious if you know matplotlib_safe is not an approved package. It is less suspicious if your detector is merely asked whether the code looks harmful.
This is why deterministic controls matter. Package policy should not depend entirely on a model’s vibe about whether a name sounds dodgy. The organisation already knows which dependencies are approved, which registries are trusted, which package hashes are pinned, and which teams may introduce new dependencies. Use that knowledge.
A good detector can help prioritise review. It should not be the only lock on the door.
The boundary conditions are where the article earns its keep
ImportSnare is a research attack, not evidence of a confirmed large-scale in-the-wild campaign. The authors explicitly state that they have not observed real-world RACG attacks of this type to their knowledge and that they notified relevant companies. That matters.
The experiments also do not perfectly simulate every production coding assistant. Real systems may use reranking, source reputation, deduplication, proprietary prompts, registry checks, sandboxing, or package allowlists. These can reduce transferability. Some target names and language ecosystems are much harder to attack than others. The cross-retriever tests show degradation when moving between embedding models. Large-scale agent testing is demonstrated qualitatively in the appendix rather than quantified broadly.
Those boundaries should temper panic, not erase the lesson.
The paper is best read as an early warning about an architectural class of risk: when generated code is grounded in retrieved external documents, those documents can become an adversarial control surface for dependency choice. The exact success rate will vary. The existence of the control surface is harder to dismiss.
The security review should move from “is the model safe?” to “who can influence an import?”
A useful review question is not “Are we using a safe LLM?” That question is too large, too vague, and usually answered with a vendor slide.
Ask narrower questions:
- What sources can enter the code-generation RAG index?
- Can we trace a generated import back to retrieved chunks?
- Do generated imports have to come from an approved package set?
- Are look-alike package names blocked or escalated?
- Does CI fail on unknown dependencies introduced by AI-generated code?
- Are package installation commands generated by assistants treated differently from ordinary text?
- Can we quarantine a package name across IDE, CI, and runtime environments quickly?
- Do we log when retrieved context changes the dependency selected by the model?
These questions turn the problem from “AI safety” into “supply-chain governance.” That is where it belongs.
RAG does not merely retrieve facts. In code generation, it can retrieve authority. If that authority says the safer package is matplotlib_safe, the model may comply, the developer may trust it, and the package manager may finish the job with admirable efficiency.
The snare is not that the model is stupid. The snare is that the whole workflow is helpful.
The strategic takeaway
ImportSnare’s contribution is not another reminder that malicious packages exist. It shows a pathway by which malicious packages can gain distribution through AI-assisted development. Retrieval gets the poisoned manual into context. Generation turns the manual into an import. Developer trust turns the import into an install.
That is the mechanism. That is also the control map.
RAG can still improve code generation. Documentation grounding remains useful. But grounded generation is only as trustworthy as the ground. If your assistant can introduce dependencies, then your documentation corpus, retriever, prompt builder, IDE, package manager, and CI pipeline are now one security system. Treat them that way.
Otherwise, the next supply-chain incident may not begin with a compromised maintainer or a typo in a terminal. It may begin with a helpful assistant saying, in effect, “Use the safer one.”
Cognaptus: Automate the Present, Incubate the Future.
-
Kai Ye, Liangcai Su, and Chenxiong Qian, “ImportSnare: Directed ‘Code Manual’ Hijacking in Retrieval-Augmented Code Generation,” arXiv:2509.07941, 2025, https://arxiv.org/html/2509.07941. ↩︎