CodeQL 2.26.0 Prompt Injection Detection: Setup, Findings, and Limits
CodeQL 2.26.0 shipped a query that treats prompt injection like SQL injection — a data-flow problem you can catch in CI. Here's how to actually set it up, what it finds, and where it falls short.
If your JavaScript or TypeScript codebase calls OpenAI, Anthropic, or Google GenAI APIs, CodeQL can now trace whether untrusted user input reaches a system prompt or tool description. As we covered when the release dropped, CodeQL 2.26.0 landed on July 10 with a new js/system-prompt-injection query that models these API calls as sinks, the same way the engine has long modeled SQL queries and shell commands. The shift is conceptually simple but practically significant: prompt injection stops being a code review judgment call and becomes a CI-blocking finding.
I've been running this against two production codebases for the past few weeks. Here's what I've learned.
What the Query Actually Does
CodeQL works by converting your source code into a relational database, then running declarative queries against it to find paths where untrusted data flows into security-sensitive operations. The js/system-prompt-injection query defines two things: sources (user-controlled input like request parameters, form fields, or database values) and sinks (places where text gets used as a system prompt, tool description, or instruction payload in an LLM API call).
The Dante Blog's practical guide notes that the query goes beyond just flagging system messages. Concatenating user input into a tool description also triggers a finding, because tool descriptions are trusted instructions that influence which tools get selected and how they behave. This is a smart design choice. In my experience, tool descriptions are where the sneakiest injection risks live, since developers tend to think of them as metadata rather than executable instructions.
The sink coverage spans several SDK entry points. The GitHub changelog lists new sinks for Sora prompts, OpenAI Realtime session instructions, Anthropic legacy completion prompts, and Google GenAI cached content and system instructions. If you're using the standard npm packages for these providers, you're covered. Custom HTTP calls to the APIs won't be detected, since CodeQL needs the SDK's type information to identify the sinks.
Setting It Up
If you're already using GitHub code scanning with CodeQL's default setup, you likely don't need to do anything. CodeQL 2.26.0 is automatically deployed to repositories on github.com, as noted in the changelog. The new query runs as part of the standard JavaScript/TypeScript security suite.
For teams on GitHub Enterprise Server, the timeline is different. The changelog notes it will ship in a future GHES release; you can manually upgrade your CodeQL version in the meantime.
Here's where it gets practical. In my repos, the first scan flagged three findings. Two were legitimate issues I hadn't considered: a chatbot feature that let users set a "persona" via a query parameter, which was concatenated directly into the system prompt, and a tool description that included a user-provided project name without sanitization. The third was a false positive where a hardcoded string was being assembled from config constants, not user input.
Remediating Findings: Three Approaches
When CodeQL flags a js/system-prompt-injection result, you generally have three remediation paths:
Move user input to a user-role message. If you're building something like a custom chatbot where users can influence behavior, their input belongs in a user message, not the system prompt. The model still sees it, but the trust boundary is explicit.
Use a fixed allowlist. If a dynamic value genuinely needs to modify trusted instructions — say, selecting between a handful of predefined personas — constrain it. Map user input to an enum or a lookup table of approved values. The Dante Blog notes that GitHub's own query documentation recommends this approach for cases where dynamic system prompt content is intentional.
Suppress with a CodeQL comment. For genuine false positives, you can add a // lgtm comment or use CodeQL's alert dismissal in the GitHub UI. I'd recommend the UI dismissal with a reason, since it creates an audit trail.
What It Catches and What It Misses
The query's precision is high — GitHub rates it as such, with a security severity of 7.8 — and in practice, that tracks. Across my two codebases, the false positive rate was low — one spurious finding out of three total. That's good enough to run in CI without drowning developers in noise.
But static analysis has inherent limitations here. The query can only trace data flows it can see at compile time. If user input arrives through a message queue, gets stored in a database, and is later retrieved by a different service that feeds it into a system prompt, CodeQL may not connect those dots unless the entire flow is in one analyzable codebase. Indirect prompt injection — where the malicious payload comes from a document the model retrieves, not from the user's direct input — is also outside the scope. This is a limitation of any static analysis tool, not a CodeQL-specific failing.
Simon Willison put the fundamental challenge well in a Simon Willison's Bay Area AI Security Meetup talk on prompt injection, describing prompt injection as "the original sin of AI engineering," rooted in the fact that these systems work through string concatenation of trusted instructions and untrusted input. Static analysis can catch the most obvious concatenation patterns, but the underlying architectural problem — that LLMs can't reliably distinguish instructions from data — remains unsolved.
The Bigger Picture: AI Security Detections on Pull Requests
CodeQL's new query is part of a broader push by GitHub to surface security findings where developers actually work. On July 14, GitHub announced that code scanning now shows AI-powered security detections directly on pull requests, expanding coverage to languages and frameworks that CodeQL doesn't natively support.
These AI-powered detections are separate from the CodeQL prompt injection query, but they share infrastructure. The AI detection engine requires CodeQL default analysis to be enabled on the repository, even though CodeQL itself isn't performing the AI analysis. Results appear as the engine returns them and are informational — they won't block merges. The feature is in public preview for GitHub Code Security customers and requires a Copilot license, consuming AI credits when detections run.
For teams building LLM-powered features, the combination is worth enabling. CodeQL gives you deterministic, high-precision findings for known SDK patterns. The AI detection layer adds probabilistic coverage for edge cases and unsupported languages. Neither is complete on its own.
Is This Worth Adopting?
If you're writing JavaScript or TypeScript that calls LLM APIs, yes. The setup cost is essentially zero if you're already using GitHub code scanning, and the query is precise enough to avoid alert fatigue. It won't catch every prompt injection vector — indirect injection, custom API clients, and cross-service data flows are all blind spots. But it catches the low-hanging fruit that's easy to miss in code review: the instructions: basePrompt + userInput pattern that looks harmless until it isn't.
The real value is cultural as much as technical. Having a CI check that flags prompt injection normalizes treating it as a real vulnerability class, not a theoretical concern. That's a shift worth making, even if the tooling still has gaps.
What I'd like to see next: sink coverage for Python SDKs, support for tracing through MCP tool definitions, and a way to model custom sanitization functions so the query understands when user input has been safely constrained. For now, though, this is the most practical prompt injection detection I've used in a production workflow.