The Evolution of the OWASP LLM Top 10: From 2023 to 2025 and 2026, the Agentic Top 10 and the Agent Control Standard
In the OWASP LLM Top 10 the same ID can point to three different risks in three editions: 2023's LLM06 was sensitive information disclosure, 2026's is Unbounded Consumption. We align the 2023, 2025 and 2026 lists by topic, cover dissolved and added entries, the 2026 rank moves, the Agentic Top 10 and the Agent Control Standard, and write a C# tool that moves an old penetration test report to 2026 IDs.
Version note: The code in this post was built and run with .NET 10. The lists compared are the OWASP Top 10 for LLM Applications v1.1 (16 October 2023), the 2025 edition (18 November 2024) and the 2026 edition (3 August 2026), plus the OWASP Top 10 for Agentic Applications 2026 (9 December 2025).
Guess First
In spring 2024 a security firm ran a penetration test on Bookstore. One of the findings in the report reads "LLM06: Sensitive Information Disclosure — staff discount code sits in the system prompt". Today, in a sprint meeting, someone says: "We finished the budget and rate limit for LLM06 last month, that entry is closed." Is it the same LLM06? And which entry in the current list does the report's "LLM07: Insecure Plugin Design" finding correspond to?
The answer: they are two different LLM06s, and LLM07 has no single counterpart today. In 2023 LLM06 was sensitive information disclosure; in 2026 LLM06 is Unbounded Consumption. The same identifier can point to three different things in three editions. In this last post of the series we look at how the list changed, work out what the changes mean for an application team, and write a small tool that reads an old report with today's IDs.
Three Editions in One Table
The first edition came out on 1 August 2023 and was updated to 1.1 a few months later. The 2025 edition was published on 18 November 2024, the 2026 edition on 3 August 2026. The table below aligns the entries by topic, not by number; aligning them by number would be the very confusion from the opening:
| Topic | 2023 (v1.1) | 2025 | 2026 |
|---|---|---|---|
| Prompt injection | LLM01 Prompt Injection | LLM01 Prompt Injection | LLM01 Prompt Injection |
| Sensitive information disclosure | LLM06 Sensitive Information Disclosure | LLM02 Sensitive Information Disclosure | LLM02 Sensitive Information Disclosure |
| Too much permission and autonomy | LLM08 Excessive Agency | LLM06 Excessive Agency | LLM03 Excessive Agency |
| Supply chain | LLM05 Supply Chain Vulnerabilities | LLM03 Supply Chain | LLM04 Supply Chain |
| Data and model poisoning | LLM03 Training Data Poisoning | LLM04 Data and Model Poisoning | LLM05 Data and Model Poisoning |
| Resource and cost consumption | LLM04 Model Denial of Service | LLM10 Unbounded Consumption | LLM06 Unbounded Consumption |
| Wrong information and overreliance | LLM09 Overreliance | LLM09 Misinformation | LLM07 Misinformation |
| Hidden context | none | LLM07 System Prompt Leakage | LLM08 Hidden Context Exposure |
| Vectors and embeddings | none | LLM08 Vector and Embedding Weaknesses | LLM09 Vector and Embedding Weaknesses |
| Handling output | LLM02 Insecure Output Handling | LLM05 Improper Output Handling | LLM10 Improper Output Handling |
| Plugin design | LLM07 Insecure Plugin Design | no separate entry | no separate entry |
| Model theft | LLM10 Model Theft | within Unbounded Consumption | within LLM06; side channels in LLM02 |
The table in short: prompt injection never moved, two entries (plugin design and model theft) stopped being separate entries, two new topics (hidden context and the vector store) were added, and the remaining six topics changed both name and rank. We summarized the list itself, based on the 2025 edition, in post 21 of the Web Security (OWASP Top 10) series; here we read the change itself rather than the entries one by one.
From 2023 to 2025: What Dissolved, What Arrived?
The project leads' preface to the 2025 edition highlights four changes. Model Denial of Service was widened to cover resource management and unexpected costs and became Unbounded Consumption; the denial of wallet and token budgets in post 7 are products of that widening. Vector and Embedding Weaknesses was added after the community asked for guidance on RAG and embedding-based methods. System Prompt Leakage became a separate entry because real exploits had been seen; the preface stresses that assuming information in prompts stays secret is not safe. Excessive Agency was also expanded as agent architectures spread.
Two more changes are not in the preface but can be read from the entry texts. 2023's Insecure Plugin Design does not exist as a separate heading in 2025; the 2025 Excessive Agency text covers "extensions" (tools, skills or plugins) carrying more functionality and more permissions than needed exactly within that topic, and the Improper Output Handling text lists third-party extensions failing to validate input as a risk example. So a plugin stops being a kind of asset and is treated as the carrier of two different failures. 2023's Overreliance is discussed within Misinformation as a related problem that amplifies it. The API part of Model Theft (model extraction, producing a functional copy) moved into the risk examples of Unbounded Consumption.
These shifts share a direction: the 2023 list thought mostly in terms of component types (plugins, models, training data), while the 2025 list thinks in terms of failures and their effects. The practical consequence for an application team: a team that said in 2023 "we have no plugins, LLM07 does not concern us" faces the same risk today under Excessive Agency if it has an assistant that calls tools.
From 2025 to 2026: Rank Moves and Incident Data
In 2026 the entry names stayed largely the same, but the order moved more than in earlier editions. You can read every move from the table; here are the main ones the document gives reasons for. Excessive Agency climbed from sixth to third; according to the document this is the most consequential move on the list, and the reason is that both the practitioner vote and the incident record agree that damage is landing in agentic deployments. Unbounded Consumption rose four places, carried by practitioners who weigh resource and cost exhaustion more than before. Improper Output Handling fell the furthest, from fifth to tenth. Misinformation rose from ninth to seventh. System Prompt Leakage took the name Hidden Context Exposure with a broader frame: next to the system prompt, tool schemas and hidden text from retrieval are now in the same entry. We worked through this in post 3 by reading Bookstore's entire context.
Scopes grew along with the ranks. Prompt Injection now explicitly covers instructions hidden in images and audio (the cover image experiment in post 2). Supply Chain took in the case of a promoted model artifact not being what it claims to be (the tag that moved in the registry in post 6). Data and Model Poisoning absorbs the subversion of fine-tuning, and Improper Output Handling covers the insecure code that coding assistants generate at scale.
We described the real methodological change of 2026 in post 1: for the first time the list was tested against real incident data, the 6,639 classifiable incidents out of 7,714 were used, and in the final ranking the practitioner vote carried three quarters of the weight and the incident data one quarter. Here let us look at what that means for how you read it. The places where the two rankings diverge are the most instructive. By incident count alone prompt injection does not make the top ten, but it stays first on the vote; the document explains that a well-defended risk shows up less in public databases. Misinformation goes the other way: near the bottom of the vote, near the top of the incident record; the document explains it by a fluent, confident answer turning into a decision or a tool call, so that a wrong answer becomes a wrong action. The answers with a correct source number but a wrong claim in post 5 and the assistant saying "according to our records" in post 6 were small examples of exactly that. So the 2026 order is better read not as "most dangerous to least dangerous" but as "where the community's concern meets the incidents".
The Agentic Top 10: When the Model Is an Actor
The 2026 document draws the boundary between the two lists explicitly: when the model is a component inside the application, the risk belongs to the LLM Top 10; when it is an actor that calls tools, carries memory across sessions and causes effects downstream, it belongs to the OWASP Top 10 for Agentic Applications announced on 9 December 2025, the ASI list. The ten entries and their counterparts in this series:
- ASI01 Agent Goal Hijack: content from outside changing the agent's goal; the canaries in posts 1 and 2.
- ASI02 Tool Misuse & Exploitation: a tool used beyond its purpose or with too much functionality; narrowing
manage_ordersin post 4 and the loops in post 7. - ASI03 Identity & Privilege Abuse: the agent acting with an over-privileged identity or one that belongs to someone else; the service account and the user's token in post 4.
- ASI04 Agentic Supply Chain Vulnerabilities: the supply chain of the tools, MCP servers and registries the agent uses; the tool definition lock in post 6.
- ASI05 Unexpected Code Execution (RCE, Remote Code Execution): model output or a tool executing code unexpectedly; the "the model does not write SQL" rule in post 5.
- ASI06 Memory & Context Poisoning: poisoning of persistent memory and context; the agent-memory counterpart of the ingestion gate in post 6.
- ASI07 Insecure Inter-Agent Communication: identity and authorization scope not being preserved in messages between agents.
- ASI08 Cascading Failures: a single failure growing along a chain of tools and agents; the budgets and circuit breakers in posts 4 and 7.
- ASI09 Human-Agent Trust Exploitation: exploiting a human's approval or trust; approval fatigue in post 4.
- ASI10 Rogue Agents: agents that drift from their goal, pretend to have completed a task or fabricate evidence.
Appendix A of the 2026 document maps each LLM entry to the related ASI entries, and that mapping shows how to read the two lists together. Excessive Agency touches almost the whole ASI list: ASI02 and ASI03 are direct manifestations of it, and ASI08 is the outcome that rate limits and circuit breakers stop. Unbounded Consumption connects to ASI02 and ASI08 through a single request multiplying into MCP tool calls. Supply Chain explicitly takes agent-specific supply chain risks out of its own scope and leaves them to ASI04. The same appendix has mappings to the OWASP GenAI Data Security 2026 list (DSGAI, 17 March 2026), MITRE ATLAS, CWE and NIST documents; when you move a finding into another framework your organization uses, that is where to start.
The Agent Control Standard: From a List to the Runtime
The Top 10 lists are risk taxonomies; they tell you what can happen and what to look at. The Agent Control Standard (ACS), which became part of the OWASP GenAI Security Project on 1 September 2026, is something else: according to OWASP's page, a standard that defines how agent platforms expose middleware hooks and how safety policies are enforced through those hooks. The goal is declarative controls that are portable across agent frameworks and enforced at runtime. OWASP's page gives no version number; sources in the ecosystem that developed the standard refer to the first public release as 0.1, so it is at an early stage.
The idea should feel familiar to readers of this series. In post 4 we stopped every tool call in the FunctionInvoker delegate and tied it to a budget and an audit trail, in post 7 we put the loop guard at the same point, and in post 8 we placed guardrails into the IChatClient pipeline as middleware. All of these were Bookstore-specific implementations of stopping before an action happens and asking a policy. The aim of ACS is to give those hook points and policy decisions a standard, framework-independent form. My practical advice for today: keep your controls, as in this series, in your own code at explicit hook points; as the standard matures, wiring those hooks to the form ACS describes will be much easier than doing it for code with controls scattered everywhere.
Reading an Old Report with Today's IDs
Back to the problem from the opening. Bookstore's risk register has findings from 2024 labeled with 2023 IDs and a newer finding labeled with a 2025 ID. I wrote a small mapping that moves them to the 2026 list. The mapping has two steps: from 2023 to 2025, and from there to 2026. That way, when a new edition comes out, adding only the last step is enough:
public sealed record Mapping(string[] Targets, string? Note = null);
// Moves an entry ID from an old report to the 2026 list; entries without a one-to-one successor carry a note
public static class OwaspLlmMap
{
public static readonly Dictionary<string, string> Names2026 = new()
{
["LLM01:2026"] = "Prompt Injection",
["LLM02:2026"] = "Sensitive Information Disclosure",
["LLM03:2026"] = "Excessive Agency",
["LLM04:2026"] = "Supply Chain",
["LLM05:2026"] = "Data and Model Poisoning",
["LLM06:2026"] = "Unbounded Consumption",
["LLM07:2026"] = "Misinformation",
["LLM08:2026"] = "Hidden Context Exposure",
["LLM09:2026"] = "Vector and Embedding Weaknesses",
["LLM10:2026"] = "Improper Output Handling",
};
// 2025 -> 2026: names stayed, only the order changed; System Prompt Leakage was widened and renamed
private static readonly Dictionary<string, Mapping> From2025 = new()
{
["LLM01:2025"] = new(["LLM01:2026"]),
["LLM02:2025"] = new(["LLM02:2026"]),
["LLM03:2025"] = new(["LLM04:2026"]),
["LLM04:2025"] = new(["LLM05:2026"]),
["LLM05:2025"] = new(["LLM10:2026"]),
["LLM06:2025"] = new(["LLM03:2026"]),
["LLM07:2025"] = new(["LLM08:2026"], "System Prompt Leakage widened into Hidden Context Exposure"),
["LLM08:2025"] = new(["LLM09:2026"]),
["LLM09:2025"] = new(["LLM07:2026"]),
["LLM10:2025"] = new(["LLM06:2026"]),
};
// 2023 (v1.1) -> 2025: names and scopes changed, two entries were dissolved
private static readonly Dictionary<string, Mapping> From2023 = new()
{
["LLM01"] = new(["LLM01:2025"]),
["LLM02"] = new(["LLM05:2025"], "Insecure Output Handling renamed Improper Output Handling"),
["LLM03"] = new(["LLM04:2025"], "Training Data Poisoning widened to Data and Model Poisoning"),
["LLM04"] = new(["LLM10:2025"], "Model Denial of Service widened to Unbounded Consumption"),
["LLM05"] = new(["LLM03:2025"]),
["LLM06"] = new(["LLM02:2025"]),
["LLM07"] = new(["LLM06:2025", "LLM05:2025"], "Insecure Plugin Design has no successor: review as agency or output handling"),
["LLM08"] = new(["LLM06:2025"]),
["LLM09"] = new(["LLM09:2025"], "Overreliance is now discussed under Misinformation"),
["LLM10"] = new(["LLM10:2025"], "Model Theft via API extraction moved to Unbounded Consumption"),
};
public static Mapping To2026(string id)
{
if (From2025.TryGetValue(id, out Mapping? direct))
{
return direct;
}
if (From2023.TryGetValue(id, out Mapping? old))
{
string[] targets = [.. old.Targets.SelectMany(t => From2025[t].Targets).Distinct()];
return new(targets, old.Note);
}
throw new ArgumentException($"Unknown OWASP LLM ID: {id}", nameof(id));
}
}
using System.Text.Json;
using Bookstore.Security.OwaspMap;
List<Finding> findings = JsonSerializer.Deserialize<List<Finding>>(File.ReadAllText(args[0]), JsonSerializerOptions.Web)!;
foreach (Finding f in findings)
{
Mapping m = OwaspLlmMap.To2026(f.Owasp);
string targets = string.Join(" + ", m.Targets.Select(t => $"{t} {OwaspLlmMap.Names2026[t]}"));
Console.WriteLine($"{f.Id} {f.Owasp,-10} -> {targets}");
Console.WriteLine($" {f.Title}");
// ID mapping is mechanical; look at the title too to catch shifts in meaning
if (f.Title.Contains("system prompt", StringComparison.OrdinalIgnoreCase) && !m.Targets.Contains("LLM08:2026"))
{
Console.WriteLine(" review: system prompt contents now have their own entry, LLM08:2026 Hidden Context Exposure");
}
if (m.Note is not null)
{
Console.WriteLine($" note: {m.Note}");
}
}
public sealed record Finding(string Id, string Owasp, string Title);
[
{ "id": "F-01", "owasp": "LLM02", "title": "Chat widget renders model output as raw HTML" },
{ "id": "F-02", "owasp": "LLM07", "title": "Order plugin accepts a free-form SQL filter from the model" },
{ "id": "F-03", "owasp": "LLM04", "title": "No limit on message size or requests per user" },
{ "id": "F-04", "owasp": "LLM06", "title": "Staff discount code stored in the system prompt" },
{ "id": "F-05", "owasp": "LLM09", "title": "Support team copies assistant refund answers without checking" },
{ "id": "F-06", "owasp": "LLM10", "title": "Public API returns token log-probabilities" },
{ "id": "F-07", "owasp": "LLM07:2025", "title": "Assistant repeats its system prompt when asked" }
]
The 2023 edition wrote IDs without a year (LLM01, LLM02); from 2025 on, IDs carry the year as well. The code uses that difference to tell which edition's ID it is looking at:
$ dotnet run -- report-2024.json
F-01 LLM02 -> LLM10:2026 Improper Output Handling
Chat widget renders model output as raw HTML
note: Insecure Output Handling renamed Improper Output Handling
F-02 LLM07 -> LLM03:2026 Excessive Agency + LLM10:2026 Improper Output Handling
Order plugin accepts a free-form SQL filter from the model
note: Insecure Plugin Design has no successor: review as agency or output handling
F-03 LLM04 -> LLM06:2026 Unbounded Consumption
No limit on message size or requests per user
note: Model Denial of Service widened to Unbounded Consumption
F-04 LLM06 -> LLM02:2026 Sensitive Information Disclosure
Staff discount code stored in the system prompt
review: system prompt contents now have their own entry, LLM08:2026 Hidden Context Exposure
F-05 LLM09 -> LLM07:2026 Misinformation
Support team copies assistant refund answers without checking
note: Overreliance is now discussed under Misinformation
F-06 LLM10 -> LLM06:2026 Unbounded Consumption
Public API returns token log-probabilities
note: Model Theft via API extraction moved to Unbounded Consumption
F-07 LLM07:2025 -> LLM08:2026 Hidden Context Exposure
Assistant repeats its system prompt when asked
note: System Prompt Leakage widened into Hidden Context Exposure
The output has three kinds of lines. Mechanically moved ones (F-01, F-03, F-07) and those carrying a note are unproblematic. F-02, the old Insecure Plugin Design finding, lands on two entries and needs a human decision; a plugin accepting free-form SQL is both a too-much-functionality problem (Excessive Agency) and an unvalidated-output problem (Improper Output Handling), and approved query templates as in post 5 close both at once. The most instructive line is F-04: in 2023 the discount code in the system prompt was a sensitive information disclosure, and the ID mapping correctly moves it to LLM02:2026. But today the more accurate place for the same finding is the separate entry opened for system prompt contents, LLM08:2026. The ID mapping cannot know that; that is why the code also looks at the title and adds a "review" line. The general lesson: moving IDs mechanically between editions of a list is a starting point, not a decision. Every moved finding needs one read against the text of its new entry.
How Will This Series Be Updated?
The OWASP lists are updated roughly once a year, and this series was written against the 2026 edition. The to-do list for the next edition is already clear. First, the mapping table: add a dictionary for the new edition to the code above and a column to the table in this post. Then the entry-to-post map in post 1: update which post covers which entry for renumbered entries; because the posts are tied to an entry's topic rather than its number, most of the text will stay unchanged. Third, the measurements: the experiments in this series were run with specific model versions (gemma3:4b, gemma3:12b, qwen3:4b, Ollama 0.34.4), and as we saw in post 6, the same tag can point to a different artifact over time. With the new edition, the PINEAPPLE canary, the leaking discount code, the campaign reviews and the red-team set from post 8 will be run again, and if the numbers changed, the text will be corrected to the real result. Finally, the version notes: the first line of every post says which list edition and which package versions it was written against; the note of an updated post will be updated too.
The same process applies to your own team. In the risk register always write the OWASP ID with its year (LLM06:2026, not LLM06), give every finding its own internal identifier independent of the list, and when the list changes, update only the label. The finding itself, its evidence and its closing test stay the same.
Production Notes
Pin the list edition in contracts and policies. The sentence "the application must comply with the OWASP LLM Top 10" causes arguments in an audit if it does not say which edition. Write "OWASP Top 10 for LLM Applications 2026" and make moving to the next edition a decision.
Manage the mapping table like code. A mapping like the one above belongs in the repository with unit tests; when a new edition arrives, the change should be a PR reviewed by the security team. A table living on a wiki page means that two years later nobody remembers why a row is the way it is.
Treat the list as a starting point, not a checklist. As we said in post 1, the OWASP order is a general ranking by the community; your product's risk order comes from its threat model. Excessive Agency being third does not bind you in an assistant with no payment tool, and in a system that processes PDFs uploaded by users, Data and Model Poisoning is a priority even if it were tenth.
Common Pitfalls
First, using IDs without the year. "LLM06" can mean three different things today; in the opening meeting two people were talking about two different risks with the same word.
Second, assuming a vanished entry is closed. Insecure Plugin Design and Model Theft are no longer separate entries, but the risks are still there; they just moved under other headings. Closing those findings in an old report because "they are not on the list anymore" means deleting an open finding from the register.
Third, reading a drop in rank as a drop in risk. Improper Output Handling fell from fifth to tenth; that does not mean a hole in Markdown rendering or in SQL is any less dangerous. The document says plainly that this order comes from combining the vote with incident data; the drop is a well-known and well-defended risk receding on the community's agenda.
Fourth, reading one of the two lists and skipping the other. The risks of an assistant that calls tools sit at the intersection of the LLM Top 10 and the Agentic Top 10; that is why the mapping in Appendix A exists.
A Small Experiment
★☆☆ Write an xUnit test for OwaspLlmMap: each of the ten IDs of the 2025 list should go to exactly one entry in 2026, and no 2026 entry should be targeted twice. When you write the same test for 2023, which rows break the rule, and why?
★★☆ Add ASI hints to the mapping: for a finding that lands on LLM03, LLM04 or LLM06 in 2026, print a line suggesting the related ASI entries from Appendix A as well, if the finding's title contains "tool", "agent" or "MCP".
★★★ Rewrite your team's (or Bookstore's) risk register following this series: for every finding an internal identifier, the 2026 LLM ID, the ASI ID if any, evidence and a closing test. How many of the closing tests can be turned into a regression test running in CI like the one in post 8? What will you do about the ones that cannot?
The series ends here: we started with a threat model, handled OWASP's ten entries one by one in the parts of Bookstore.Assistant, and brought them all together in a guardrail pipeline and a regression set. What comes next lives in two neighboring series: the AI-Assisted Development series covers using coding assistants safely, and the AI Interfaces with Angular series covers the streaming and user experience side of the chat interface; both will come back to post 5 for the security of output rendering.