DEV Community

Ahab
Ahab

Posted on • Originally published at indieseek.co

CodeQL system prompt injection: a JavaScript fix checklist

Quick answer

GitHub released CodeQL 2.26.0 with a new high-precision JavaScript and TypeScript query, js/system-prompt-injection. It reports data-flow paths where an untrusted value reaches a system prompt, developer prompt, or AI tool description.

The shortest safe response to an alert is:

confirm the untrusted source
-> identify the privileged prompt or tool-description sink
-> move open-ended input into a user message or tool argument
-> allowlist the value if it must affect trusted instructions
-> rerun CodeQL and add a regression test
-> review runtime permissions separately
Enter fullscreen mode Exit fullscreen mode

Do not treat escaping or a stronger warning inside the system prompt as the primary fix. The trust boundary is the issue: attacker-controlled text should not become privileged instructions.

Who this is for

This checklist is for indie developers building JavaScript or TypeScript products with OpenAI, Anthropic, Google GenAI, agent frameworks, or custom tool calling. It is especially relevant when request parameters, database content, uploaded files, retrieved pages, workspace instructions, or tenant configuration can influence a system message or tool description.

This is application-code review, not a guide to safely opening an unknown repository. Use the untrusted repository sandbox checklist before installing or running third-party code, and the npm package vetting checklist before adding a new AI dependency.

What changed, and why now

GitHub announced CodeQL 2.26.0 on July 10, 2026. The release adds js/system-prompt-injection, which detects untrusted, user-provided values flowing into an AI model's system prompt. The underlying query metadata marks it as high precision, an error, security severity 7.8, and CWE-1427.

The same release expands JavaScript and TypeScript prompt-injection sinks across OpenAI, Anthropic, and Google GenAI SDK APIs. GitHub's query help also makes an important boundary explicit: tool descriptions are privileged instructions too. A user-controlled value in a tool description can manipulate model behavior even when the system prompt itself is static.

CodeQL is useful here because it turns one prompt-security problem into a reviewable source-to-sink path. It does not prove that an LLM application is safe. It will not replace adversarial tests, authorization checks, tool permission limits, output review, or isolation. It finds a specific code pattern that should usually be removed.

The review workflow

1. Inventory privileged strings before scanning

Search for every place the application constructs trusted model context:

system messages
developer messages
agent instructions
tool names and descriptions
handoff or subagent instructions
cached system content
realtime session instructions
Enter fullscreen mode Exit fullscreen mode

Then label each interpolated value by origin: constant, developer-controlled configuration, tenant administrator setting, database record, retrieved content, or direct user input. Do not classify a value as trusted just because it passed through your database.

2. Run CodeQL on the real JavaScript/TypeScript path

For an eligible GitHub repository, default setup is the lowest-maintenance starting point: open Settings -> Advanced Security -> CodeQL analysis -> Set up -> Default, confirm JavaScript/TypeScript is included, and run the initial analysis.

If code scanning already runs, verify that the latest analysis uses a CodeQL release that contains 2.26.0. Do not assume an old successful workflow includes the new query. For advanced setup, keep the standard JavaScript/TypeScript query pack current and avoid pinning an obsolete CodeQL bundle.

Create a small test branch with a representative flow if you need to confirm coverage. Use synthetic input, not production secrets or a live attack payload.

3. Triage the complete path, not only the highlighted line

For each js/system-prompt-injection alert, record four things:

Evidence Question
Source Which request, file, database field, or retrieved value is attacker-controlled?
Transform Was the value only concatenated, formatted, decoded, or weakly checked?
Sink Did it reach a system/developer message, tool description, or equivalent privileged field?
Capability What data or tools can the model access if its behavior changes?

The capability column sets priority. A prompt-only assistant with no sensitive context is different from an agent that can read private files, query customer data, send messages, or execute tools. Both should be fixed, but the second path has a larger blast radius.

4. Choose the fix by data shape

Use this decision table instead of trying to sanitize arbitrary language:

Input shape Preferred fix Example
Open-ended user text Move it to a user message Question, document text, requested persona
Small closed set Validate against a fixed allowlist teacher, editor, support
Tool-specific data Keep description static; pass data as typed arguments Topic, record ID, filename
Tenant policy Store structured policy fields and render only reviewed templates Allowed tone, locale, approved tools
Retrieved external content Mark it as untrusted data and restrict tool authority Web page, issue, email, RAG chunk

A useful code-review rule is: trusted prompts define policy; untrusted messages provide data. If user input must select a trusted behavior, map an allowlisted identifier to a server-owned template rather than inserting the raw value.

5. Fix the adjacent runtime boundary

Removing the static data flow is necessary but not sufficient. After the CodeQL alert is gone, check:

  • system and developer prompts contain no credentials or private authorization data;
  • tool descriptions are constants or server-owned templates;
  • tool arguments use schemas and server-side validation;
  • authorization is enforced outside the model;
  • each tool has the minimum data and action scope needed;
  • destructive or external side effects require an approval gate;
  • logs record decisions without storing secrets or unnecessary prompt content.

If the model can request a sensitive action, assume a future prompt injection may influence that request. The server still decides whether it is allowed.

Alert triage matrix

Use this compact matrix in a pull request or security issue:

Alert path Priority Required evidence before closure
Request value -> system/developer prompt -> model with tools Critical review Data-flow fix, permission review, adversarial regression test
Retrieved content -> tool description -> agent Critical review Static description, typed argument, tool authorization test
Tenant setting -> system prompt High Admin trust model, fixed schema or allowlist, tenant-isolation test
User value -> system prompt -> text-only model High Move to user role or allowlist, output regression test
Constant -> system prompt Not this vulnerability Confirm source is immutable and dismiss with evidence

Do not dismiss an alert only because the current model refused your test string. Model behavior changes; the unsafe source-to-sink relationship remains in code.

Common mistakes

  • Escaping quotes or braces as if prompt injection were SQL injection.
  • Adding "ignore malicious instructions" while leaving user text inside the system prompt.
  • Moving the same untrusted text from a system prompt into a dynamic tool description.
  • Allowlisting with substring matching instead of exact server-owned values.
  • Closing the alert after a model refusal without changing the data flow.
  • Treating database or RAG content as trusted because it was not typed into the current request.
  • Assuming CodeQL covers runtime authorization, indirect prompt injection, and every framework integration.

FAQ

Does CodeQL 2.26.0 block prompt injection at runtime?

No. CodeQL performs static analysis and reports supported source-to-sink paths. Your application must still enforce authorization, tool schemas, isolation, approval gates, and runtime tests.

Should every dynamic system prompt be removed?

No. Server-owned templates and values selected from a strict allowlist can be valid. The dangerous case is open-ended or attacker-controlled content becoming trusted instructions.

Is moving content to the user role a complete defense?

It fixes the privilege-boundary error targeted by this query, but it does not make the model immune to prompt injection. Keep tool permissions narrow and enforce all security decisions in deterministic application code.

What if CodeQL does not recognize my AI framework?

Review the same trust boundary manually and consider a CodeQL model pack or custom query for the missing source or sink. A clean scan is only evidence for the code and frameworks the analysis modeled.

Sources

Top comments (0)