An LLM feature rarely arrives as a new service. It arrives as a new route in a backend that already exists: a handler that accepts user text, builds a prompt, calls a model, and does something with the answer. GitHub's Octoverse 2025 report counts more than 1.1 million public repositories using an LLM SDK, and more than 4.3 million AI-related repositories. Most of those SDK calls sit a few lines from an ordinary database query or HTTP client.
OWASP publishes two lists for that code. The API Security Top 10 (2023) covers the handler: who may call it, which object it returns, where it makes outbound requests. The Top 10 for LLM Applications (2025) covers what happens between the prompt and the response. Teams often treat them as two checklists owned by two groups, and the gap between them is where the interesting bugs live.
This post maps both lists onto code. For each item it says whether a static scan can decide it, can only flag the shape, or cannot see it at all. It also names the Vulkro command that produces each answer.
The two lists, side by side
The API list, in its 2023 edition: API1 Broken Object Level Authorization, API2 Broken Authentication, API3 Broken Object Property Level Authorization, API4 Unrestricted Resource Consumption, API5 Broken Function Level Authorization, API6 Unrestricted Access to Sensitive Business Flows, API7 Server Side Request Forgery, API8 Security Misconfiguration, API9 Improper Inventory Management, API10 Unsafe Consumption of APIs.
The LLM list, in its 2025 edition, runs from LLM01 Prompt Injection to LLM10 Unbounded Consumption. The items that matter most for this post are LLM01, LLM05 Improper Output Handling, LLM06 Excessive Agency and LLM07 System Prompt Leakage.
Read together, the shape is simple. The API list is mostly about the boundary of a handler. The LLM list is mostly about what crosses that boundary once a model is in the loop. The same request can be an API1 problem on the way in and an LLM05 problem on the way out.
What a scan can decide
Six items are data-flow questions, and data flow is what static analysis is for.
API1 is a lookup keyed by a caller-supplied id with no ownership check between the request and the query. API3 is a request body bound straight onto a model or serializer, so a client can set fields it should never touch. API7 is a user-supplied URL that reaches an HTTP client. Each has a source, a path and a sink, and a scanner that traces the path can show it hop by hop.
On the LLM side, LLM01 becomes decidable when the injected text has somewhere to go: untrusted input reaches a prompt, and the model's answer drives a tool call with side effects. LLM05 is model output reaching eval, a shell, a SQL string or unescaped HTML. LLM07, together with the secret-exposure half of the list, shows up as a credential read from the environment and placed into a prompt, a tool result or a log the model later reads.
Vulkro reports each of these with a disposition: proven when the flow was traced end to end, unproven when the pattern matched but a hop could not be resolved, and not checked when the class cannot be decided from source. Authorization lands in that last bucket more often than people expect, because the check that makes a lookup safe may live in middleware or a base class the rule did not read. The label exists so a reader knows which rows still need a human.
For MCP servers the same questions have their own rule set. vulkro scan-mcp-server <path> runs MCP-SERVER-001 to 008 against Python and TypeScript SDK servers: description injection, tool poisoning sinks, a tools/list that changes after approval, schema mismatch, secrets returned into model context, and destructive tools with no authorization. These are the LLM06 and LLM07 items as they appear in a server you wrote.
What a scan cannot decide
Being honest about the other half matters more than the first half.
API4 and LLM10 are about rate, quota and cost. Whether a route can be called ten thousand times a minute is a property of a gateway, not of the handler's source. API6 is business logic: nothing in the code says that buying a ticket is sensitive and reading a FAQ is not. API9 is inventory, which a scan can help with by mapping the routes it sees, but a forgotten service on another host is invisible to it. API10 depends on how a third party behaves at runtime.
LLM04 Data and Model Poisoning happens in training pipelines and vector stores, not in a request handler. LLM06 as a governance question, which tools an agent should be allowed to have at all, is a design decision. LLM08 and LLM09 are about retrieval quality and model behaviour. Vulkro does not catch any of these, and a scan report that says nothing about them is silent, not clean.
The useful move is to write that list down and give each item an owner, so the scan covers the data-flow half and the architecture review covers the rest.
Where the two lists meet
The clearest 2025 example is ForcedLeak, disclosed on 2025-09-25 with a CVSS of 9.4 according to the researcher. As reported by Security Affairs, a prompt injected through a Web-to-Lead Description field drove an Agentforce agent to exfiltrate CRM data to an expired domain that was still on an allowlist. Salesforce added Trusted URL enforcement on 2025-09-08.
Walk it against the two lists. The entry point is a public form, an API item. The payload is LLM01. The agent acting on it is LLM06. The outbound request to a stale domain is the SSRF shape from API7 with an allowlist that no longer meant anything. No single item describes the bug; the chain does.
The exposure is measurable. The MCPTox study (arXiv 2508.14925, submitted 2025-08-19) ran 1,348 malicious cases against 20 AI coding agents and assistants on 45 live MCP servers covering 353 tools. The highest attack success rate was 72.8%. The lesson for code is that the model will not reliably refuse, so the tool it can reach has to be safe to call with hostile arguments.
Running the map
Four commands cover the items a scan can decide.
vulkro explain API2 . works on an OWASP category id, not only on a finding id. It prints what the category means, which rules cover it, and what those rules found in the current directory. Substitute any API item.
vulkro compliance-pack . --framework owasp-llm-2025 writes a pack mapped to the LLM list, one control per item with file and line evidence. The compliance-pack frameworks also include owasp-agentic-2026 for agent-specific checks.
vulkro sbom . --format aibom produces a CycloneDX 1.6 ML-BOM listing the AI SDKs, models and MCP servers the code uses. That is the inventory half of LLM03 and API9, as far as source can see it. See the SBOM docs.
vulkro scan-mcp-server ./server runs the MCP-SERVER rules on a server you ship.
Read the counts with the published precision in mind. On the blind holdout for 0.36.0, measured 2026-10-19, Vulkro found 181 of 229 planted vulnerabilities and flagged 39 of 152 known-safe controls at Medium or above. A scan is a strong first pass, not a verdict.
What to do this week
- Run
vulkro explain API1 .andvulkro explain API7 .on the service that calls a model. Read the proven rows first. - Run
vulkro compliance-pack . --framework owasp-llm-2025and file the items that come back without findings as unverified, not passed. - Run
vulkro sbom . --format aibomand check whether every SDK and MCP server on the list is one you knew about. - If you ship an MCP server, run
vulkro scan-mcp-serveragainst it before the next release. - Write down the items no scan can decide (quotas, business flows, tool governance) and assign an owner to each.
The compliance-pack docs list every framework and the pack layout. To run these on your own code, start the 14-day trial.