From 6c5c8e1c9b87f6461d7fe0e19a1abfd18f59cd06 Mon Sep 17 00:00:00 2001 From: BazookaMusic Date: Wed, 20 May 2026 10:48:07 +0200 Subject: [PATCH] move system prompt injection to non-experimental --- .../javascript/frameworks/Anthropic.qll | 0 .../javascript/frameworks/GoogleGenAI.qll | 0 .../semmle/javascript/frameworks/OpenAI.qll | 0 .../SystemPromptInjectionCustomizations.qll | 6 +-- .../dataflow}/SystemPromptInjectionQuery.qll | 0 .../CWE-1427/SystemPromptInjection.qhelp | 0 .../CWE-1427/SystemPromptInjection.ql | 5 +-- .../CWE-1427/examples/prompt-injection.js | 2 +- .../examples/prompt-injection_fixed.js | 0 .../CWE-1427/UserPromptInjection.qhelp | 41 +++++++++++++++++++ .../Security/CWE-1427/UserPromptInjection.ql | 4 +- .../examples/user-prompt-injection.js | 26 ++++++++++++ .../examples/user-prompt-injection_fixed.js | 32 +++++++++++++++ .../UserPromptInjectionCustomizations.qll | 6 +-- .../SystemPromptInjection.qlref | 2 +- 15 files changed, 111 insertions(+), 13 deletions(-) rename javascript/ql/{src/experimental => lib}/semmle/javascript/frameworks/Anthropic.qll (100%) rename javascript/ql/{src/experimental => lib}/semmle/javascript/frameworks/GoogleGenAI.qll (100%) rename javascript/ql/{src/experimental => lib}/semmle/javascript/frameworks/OpenAI.qll (100%) rename javascript/ql/{src/experimental/semmle/javascript/security/PromptInjection => lib/semmle/javascript/security/dataflow}/SystemPromptInjectionCustomizations.qll (94%) rename javascript/ql/{src/experimental/semmle/javascript/security/PromptInjection => lib/semmle/javascript/security/dataflow}/SystemPromptInjectionQuery.qll (100%) rename javascript/ql/src/{experimental => }/Security/CWE-1427/SystemPromptInjection.qhelp (100%) rename javascript/ql/src/{experimental => }/Security/CWE-1427/SystemPromptInjection.ql (78%) rename javascript/ql/src/{experimental => }/Security/CWE-1427/examples/prompt-injection.js (99%) rename javascript/ql/src/{experimental => }/Security/CWE-1427/examples/prompt-injection_fixed.js (100%) create mode 100644 javascript/ql/src/experimental/Security/CWE-1427/UserPromptInjection.qhelp create mode 100644 javascript/ql/src/experimental/Security/CWE-1427/examples/user-prompt-injection.js create mode 100644 javascript/ql/src/experimental/Security/CWE-1427/examples/user-prompt-injection_fixed.js diff --git a/javascript/ql/src/experimental/semmle/javascript/frameworks/Anthropic.qll b/javascript/ql/lib/semmle/javascript/frameworks/Anthropic.qll similarity index 100% rename from javascript/ql/src/experimental/semmle/javascript/frameworks/Anthropic.qll rename to javascript/ql/lib/semmle/javascript/frameworks/Anthropic.qll diff --git a/javascript/ql/src/experimental/semmle/javascript/frameworks/GoogleGenAI.qll b/javascript/ql/lib/semmle/javascript/frameworks/GoogleGenAI.qll similarity index 100% rename from javascript/ql/src/experimental/semmle/javascript/frameworks/GoogleGenAI.qll rename to javascript/ql/lib/semmle/javascript/frameworks/GoogleGenAI.qll diff --git a/javascript/ql/src/experimental/semmle/javascript/frameworks/OpenAI.qll b/javascript/ql/lib/semmle/javascript/frameworks/OpenAI.qll similarity index 100% rename from javascript/ql/src/experimental/semmle/javascript/frameworks/OpenAI.qll rename to javascript/ql/lib/semmle/javascript/frameworks/OpenAI.qll diff --git a/javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/SystemPromptInjectionCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/SystemPromptInjectionCustomizations.qll similarity index 94% rename from javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/SystemPromptInjectionCustomizations.qll rename to javascript/ql/lib/semmle/javascript/security/dataflow/SystemPromptInjectionCustomizations.qll index a367eea8b83..2679b742948 100644 --- a/javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/SystemPromptInjectionCustomizations.qll +++ b/javascript/ql/lib/semmle/javascript/security/dataflow/SystemPromptInjectionCustomizations.qll @@ -11,9 +11,9 @@ private import semmle.javascript.Concepts private import semmle.javascript.security.dataflow.RemoteFlowSources private import semmle.javascript.dataflow.internal.BarrierGuards private import semmle.javascript.frameworks.data.ModelsAsData -private import experimental.semmle.javascript.frameworks.OpenAI -private import experimental.semmle.javascript.frameworks.Anthropic -private import experimental.semmle.javascript.frameworks.GoogleGenAI +private import semmle.javascript.frameworks.OpenAI +private import semmle.javascript.frameworks.Anthropic +private import semmle.javascript.frameworks.GoogleGenAI /** * Provides default sources, sinks and sanitizers for detecting diff --git a/javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/SystemPromptInjectionQuery.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/SystemPromptInjectionQuery.qll similarity index 100% rename from javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/SystemPromptInjectionQuery.qll rename to javascript/ql/lib/semmle/javascript/security/dataflow/SystemPromptInjectionQuery.qll diff --git a/javascript/ql/src/experimental/Security/CWE-1427/SystemPromptInjection.qhelp b/javascript/ql/src/Security/CWE-1427/SystemPromptInjection.qhelp similarity index 100% rename from javascript/ql/src/experimental/Security/CWE-1427/SystemPromptInjection.qhelp rename to javascript/ql/src/Security/CWE-1427/SystemPromptInjection.qhelp diff --git a/javascript/ql/src/experimental/Security/CWE-1427/SystemPromptInjection.ql b/javascript/ql/src/Security/CWE-1427/SystemPromptInjection.ql similarity index 78% rename from javascript/ql/src/experimental/Security/CWE-1427/SystemPromptInjection.ql rename to javascript/ql/src/Security/CWE-1427/SystemPromptInjection.ql index 07da2f0cec3..0dc7786160c 100644 --- a/javascript/ql/src/experimental/Security/CWE-1427/SystemPromptInjection.ql +++ b/javascript/ql/src/Security/CWE-1427/SystemPromptInjection.ql @@ -4,14 +4,13 @@ * @problem.severity error * @security-severity 5.0 * @precision high - * @id js/prompt-injection + * @id js/system-prompt-injection * @tags security - * experimental * external/cwe/cwe-1427 */ import javascript -import experimental.semmle.javascript.security.PromptInjection.SystemPromptInjectionQuery +import semmle.javascript.security.dataflow.SystemPromptInjectionQuery import SystemPromptInjectionFlow::PathGraph from SystemPromptInjectionFlow::PathNode source, SystemPromptInjectionFlow::PathNode sink diff --git a/javascript/ql/src/experimental/Security/CWE-1427/examples/prompt-injection.js b/javascript/ql/src/Security/CWE-1427/examples/prompt-injection.js similarity index 99% rename from javascript/ql/src/experimental/Security/CWE-1427/examples/prompt-injection.js rename to javascript/ql/src/Security/CWE-1427/examples/prompt-injection.js index d124d147147..dba5bb57ace 100644 --- a/javascript/ql/src/experimental/Security/CWE-1427/examples/prompt-injection.js +++ b/javascript/ql/src/Security/CWE-1427/examples/prompt-injection.js @@ -23,4 +23,4 @@ app.get("/chat", async (req, res) => { }); res.json(response); -}); +}); \ No newline at end of file diff --git a/javascript/ql/src/experimental/Security/CWE-1427/examples/prompt-injection_fixed.js b/javascript/ql/src/Security/CWE-1427/examples/prompt-injection_fixed.js similarity index 100% rename from javascript/ql/src/experimental/Security/CWE-1427/examples/prompt-injection_fixed.js rename to javascript/ql/src/Security/CWE-1427/examples/prompt-injection_fixed.js diff --git a/javascript/ql/src/experimental/Security/CWE-1427/UserPromptInjection.qhelp b/javascript/ql/src/experimental/Security/CWE-1427/UserPromptInjection.qhelp new file mode 100644 index 00000000000..10f8bff31df --- /dev/null +++ b/javascript/ql/src/experimental/Security/CWE-1427/UserPromptInjection.qhelp @@ -0,0 +1,41 @@ + + + + +

If untrusted input is included in a user-role prompt sent to an AI model, an attacker can inject +instructions that manipulate the model's behavior. This is known as indirect prompt injection +when the malicious content arrives through data the model processes, or direct prompt injection +when the attacker controls the prompt directly.

+ +

Unlike system prompt injection, user prompt injection targets the user-role messages. Although +user messages are expected to carry user input, passing unsanitized data directly into structured +prompt templates can still allow an attacker to override intended instructions, extract sensitive +context, or trigger unintended tool calls.

+
+ + +

To mitigate user prompt injection:

+
    +
  • Validate user input against a fixed allowlist of permitted values before including it in a prompt.
  • +
  • Use parameterized prompt templates that clearly separate instructions from user data.
  • +
  • Apply output filtering to detect and block responses that indicate prompt injection attempts.
  • +
+
+ + +

In the following example, user-controlled data is inserted directly into a user-role prompt +without any validation, allowing an attacker to inject arbitrary instructions.

+ +

The fix validates the user input against a fixed allowlist of permitted values before +including it in the prompt.

+ +
+ + +
  • OWASP: LLM01: Prompt Injection.
  • +
  • MITRE CWE: CWE-1427: Improper Neutralization of Input Used for LLM Prompting.
  • +
    + +
    diff --git a/javascript/ql/src/experimental/Security/CWE-1427/UserPromptInjection.ql b/javascript/ql/src/experimental/Security/CWE-1427/UserPromptInjection.ql index 57c9ffa987d..ba71fd66b90 100644 --- a/javascript/ql/src/experimental/Security/CWE-1427/UserPromptInjection.ql +++ b/javascript/ql/src/experimental/Security/CWE-1427/UserPromptInjection.ql @@ -3,9 +3,9 @@ * @description Untrusted input flowing into a user-role prompt of an AI model * may allow an attacker to manipulate the model's behavior. * @kind path-problem - * @problem.severity error + * @problem.severity warning * @security-severity 5.0 - * @precision high + * @precision medium * @id js/user-prompt-injection * @tags security * experimental diff --git a/javascript/ql/src/experimental/Security/CWE-1427/examples/user-prompt-injection.js b/javascript/ql/src/experimental/Security/CWE-1427/examples/user-prompt-injection.js new file mode 100644 index 00000000000..3d1dc32c413 --- /dev/null +++ b/javascript/ql/src/experimental/Security/CWE-1427/examples/user-prompt-injection.js @@ -0,0 +1,26 @@ +const express = require("express"); +const OpenAI = require("openai"); + +const app = express(); +const client = new OpenAI(); + +app.get("/chat", async (req, res) => { + let topic = req.query.topic; + + // BAD: user input is used directly in a user-role prompt + const response = await client.chat.completions.create({ + model: "gpt-4.1", + messages: [ + { + role: "system", + content: "You are a helpful assistant that summarizes topics.", + }, + { + role: "user", + content: "Summarize the following topic: " + topic, + }, + ], + }); + + res.json(response); +}); diff --git a/javascript/ql/src/experimental/Security/CWE-1427/examples/user-prompt-injection_fixed.js b/javascript/ql/src/experimental/Security/CWE-1427/examples/user-prompt-injection_fixed.js new file mode 100644 index 00000000000..455afeecd6c --- /dev/null +++ b/javascript/ql/src/experimental/Security/CWE-1427/examples/user-prompt-injection_fixed.js @@ -0,0 +1,32 @@ +const express = require("express"); +const OpenAI = require("openai"); + +const app = express(); +const client = new OpenAI(); + +const ALLOWED_TOPICS = ["science", "history", "technology"]; + +app.get("/chat", async (req, res) => { + let topic = req.query.topic; + + // GOOD: user input is validated against a fixed allowlist before use in a prompt + if (!ALLOWED_TOPICS.includes(topic)) { + return res.status(400).json({ error: "Invalid topic" }); + } + + const response = await client.chat.completions.create({ + model: "gpt-4.1", + messages: [ + { + role: "system", + content: "You are a helpful assistant that summarizes topics.", + }, + { + role: "user", + content: "Summarize the following topic: " + topic, + }, + ], + }); + + res.json(response); +}); diff --git a/javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/UserPromptInjectionCustomizations.qll b/javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/UserPromptInjectionCustomizations.qll index 0de238a41c1..c30d7b49cfe 100644 --- a/javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/UserPromptInjectionCustomizations.qll +++ b/javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/UserPromptInjectionCustomizations.qll @@ -11,9 +11,9 @@ private import semmle.javascript.Concepts private import semmle.javascript.security.dataflow.RemoteFlowSources private import semmle.javascript.dataflow.internal.BarrierGuards private import semmle.javascript.frameworks.data.ModelsAsData -private import experimental.semmle.javascript.frameworks.OpenAI -private import experimental.semmle.javascript.frameworks.Anthropic -private import experimental.semmle.javascript.frameworks.GoogleGenAI +private import semmle.javascript.frameworks.OpenAI +private import semmle.javascript.frameworks.Anthropic +private import semmle.javascript.frameworks.GoogleGenAI /** * Provides default sources, sinks and sanitizers for detecting diff --git a/javascript/ql/test/experimental/Security/CWE-1427/SystemPromptInjection/SystemPromptInjection.qlref b/javascript/ql/test/experimental/Security/CWE-1427/SystemPromptInjection/SystemPromptInjection.qlref index c2ab6756b61..d8ef59e125f 100644 --- a/javascript/ql/test/experimental/Security/CWE-1427/SystemPromptInjection/SystemPromptInjection.qlref +++ b/javascript/ql/test/experimental/Security/CWE-1427/SystemPromptInjection/SystemPromptInjection.qlref @@ -1 +1 @@ -experimental/Security/CWE-1427/SystemPromptInjection.ql +Security/CWE-1427/SystemPromptInjection.ql