Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0b7133c
JS: Add prompt injection detection (CWE-1427) for OpenAI, Anthropic, …
BazookaMusic Apr 30, 2026
74a3ba1
changes for spliting into system and user
BazookaMusic May 4, 2026
9006ddb
default threat model
BazookaMusic May 12, 2026
98379cf
Documentation
BazookaMusic May 12, 2026
34da804
Move structurally typed prompt injection sinks to Models as Data
BazookaMusic May 13, 2026
9c13626
remove guardrails sanitizer for now
BazookaMusic May 13, 2026
535adc7
add barrier when data flows into user messages for system prompt dete…
BazookaMusic May 15, 2026
fe7eabd
Add run from agents into the user prompt and fix an issue with classi…
BazookaMusic May 15, 2026
5ef09a1
add tests for langchain and remove wrong model for guardrails agent
BazookaMusic May 15, 2026
6c5c8e1
move system prompt injection to non-experimental
BazookaMusic May 20, 2026
078d15e
add openrouter support
BazookaMusic Jun 4, 2026
da05992
Better document the new queries
BazookaMusic Jun 8, 2026
61be37d
Formatting
BazookaMusic Jun 8, 2026
e370af6
QLDoc + include the queries in the correct expected files per query s…
BazookaMusic Jun 8, 2026
2cb0851
1. Rename AgentSDK -> AgentSdk
BazookaMusic Jun 8, 2026
b6c951e
Remove redundant file
BazookaMusic Jun 8, 2026
d0ffde8
Em-dash - of course :D
BazookaMusic Jun 8, 2026
e612db2
Promote user prompt injection query to stable security
BazookaMusic Jun 11, 2026
7bd5abf
Refine SystemPromptInjection alert message and move test to stable
BazookaMusic Jun 11, 2026
ef56787
Update not_included_in_qls.expected for promoted prompt injection que…
BazookaMusic Jun 11, 2026
17dbf03
Merge branch 'main' into bazookamusic/cwe-1427
BazookaMusic Jun 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ ql/javascript/ql/src/Security/CWE-116/IncompleteMultiCharacterSanitization.ql
ql/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql
ql/javascript/ql/src/Security/CWE-116/UnsafeHtmlExpansion.ql
ql/javascript/ql/src/Security/CWE-134/TaintedFormatString.ql
ql/javascript/ql/src/Security/CWE-1427/SystemPromptInjection.ql
ql/javascript/ql/src/Security/CWE-178/CaseSensitiveMiddlewarePath.ql
ql/javascript/ql/src/Security/CWE-200/PrivateFileExposure.ql
ql/javascript/ql/src/Security/CWE-201/PostMessageStar.ql
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ ql/javascript/ql/src/Security/CWE-116/UnsafeHtmlExpansion.ql
ql/javascript/ql/src/Security/CWE-117/LogInjection.ql
ql/javascript/ql/src/Security/CWE-1275/SameSiteNoneCookie.ql
ql/javascript/ql/src/Security/CWE-134/TaintedFormatString.ql
ql/javascript/ql/src/Security/CWE-1427/SystemPromptInjection.ql
ql/javascript/ql/src/Security/CWE-178/CaseSensitiveMiddlewarePath.ql
ql/javascript/ql/src/Security/CWE-200/FileAccessToHttp.ql
ql/javascript/ql/src/Security/CWE-200/PrivateFileExposure.ql
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ ql/javascript/ql/src/Security/CWE-116/UnsafeHtmlExpansion.ql
ql/javascript/ql/src/Security/CWE-117/LogInjection.ql
ql/javascript/ql/src/Security/CWE-1275/SameSiteNoneCookie.ql
ql/javascript/ql/src/Security/CWE-134/TaintedFormatString.ql
ql/javascript/ql/src/Security/CWE-1427/SystemPromptInjection.ql
ql/javascript/ql/src/Security/CWE-178/CaseSensitiveMiddlewarePath.ql
ql/javascript/ql/src/Security/CWE-200/FileAccessToHttp.ql
ql/javascript/ql/src/Security/CWE-200/PrivateFileExposure.ql
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ ql/javascript/ql/src/Performance/NonLocalForIn.ql
ql/javascript/ql/src/RegExp/MalformedRegExp.ql
ql/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql
ql/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql
ql/javascript/ql/src/Security/CWE-1427/UserPromptInjection.ql
ql/javascript/ql/src/Security/CWE-313/PasswordInConfigurationFile.ql
ql/javascript/ql/src/Security/CWE-451/MissingXFrameOptions.ql
ql/javascript/ql/src/Security/CWE-798/HardcodedCredentials.ql
Expand Down
17 changes: 17 additions & 0 deletions javascript/ql/lib/ext/anthropic.model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: typeModel
data:
- ["anthropic.Client", "@anthropic-ai/sdk", "Instance"]

- addsTo:
pack: codeql/javascript-all
extensible: sinkModel
data:
- ["anthropic.Client", "Member[messages].Member[create].Argument[0].Member[system]", "system-prompt-injection"]
- ["anthropic.Client", "Member[messages].Member[create].Argument[0].Member[system].ArrayElement.Member[text]", "system-prompt-injection"]
- ["anthropic.Client", "Member[beta].Member[messages].Member[create].Argument[0].Member[system]", "system-prompt-injection"]
- ["anthropic.Client", "Member[beta].Member[messages].Member[create].Argument[0].Member[system].ArrayElement.Member[text]", "system-prompt-injection"]
- ["anthropic.Client", "Member[beta].Member[agents].Member[create].Argument[0].Member[system]", "system-prompt-injection"]
- ["anthropic.Client", "Member[beta].Member[agents].Member[update].Argument[1].Member[system]", "system-prompt-injection"]
22 changes: 22 additions & 0 deletions javascript/ql/lib/ext/google-genai.model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: typeModel
data:
- ["google-genai.Client", "@google/genai", "Member[GoogleGenAI].Instance"]

- addsTo:
pack: codeql/javascript-all
extensible: sinkModel
data:
- ["google-genai.Client", "Member[models].Member[generateContent,generateContentStream].Argument[0].Member[config].Member[systemInstruction]", "system-prompt-injection"]
- ["google-genai.Client", "Member[chats].Member[create].Argument[0].Member[config].Member[systemInstruction]", "system-prompt-injection"]
- ["google-genai.Client", "Member[chats].Member[create].ReturnValue.Member[sendMessage].Argument[0].Member[config].Member[systemInstruction]", "system-prompt-injection"]
- ["google-genai.Client", "Member[live].Member[connect].Argument[0].Member[config].Member[systemInstruction]", "system-prompt-injection"]
- ["google-genai.Client", "Member[models].Member[generateContent,generateContentStream].Argument[0].Member[contents]", "user-prompt-injection"]
- ["google-genai.Client", "Member[models].Member[generateImages].Argument[0].Member[prompt]", "user-prompt-injection"]
- ["google-genai.Client", "Member[models].Member[editImage].Argument[0].Member[prompt]", "user-prompt-injection"]
- ["google-genai.Client", "Member[models].Member[generateVideos].Argument[0].Member[prompt]", "user-prompt-injection"]
- ["google-genai.Client", "Member[chats].Member[create].ReturnValue.Member[sendMessage,sendMessageStream].Argument[0].Member[message]", "user-prompt-injection"]
- ["google-genai.Client", "Member[chats].Member[create].ReturnValue.Member[sendMessage,sendMessageStream].Argument[0].Member[content]", "user-prompt-injection"]
- ["google-genai.Client", "Member[interactions].Member[create].Argument[0].Member[input]", "user-prompt-injection"]
48 changes: 48 additions & 0 deletions javascript/ql/lib/ext/langchain.model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: typeModel
data:
- ["langchain.ChatModel", "@langchain/openai", "Member[ChatOpenAI].Instance"]
- ["langchain.ChatModel", "@langchain/anthropic", "Member[ChatAnthropic].Instance"]
- ["langchain.ChatModel", "@langchain/google-genai", "Member[ChatGoogleGenerativeAI].Instance"]
- ["langchain.ChatModel", "@langchain/mistralai", "Member[ChatMistralAI].Instance"]
- ["langchain.ChatModel", "@langchain/groq", "Member[ChatGroq].Instance"]
- ["langchain.ChatModel", "@langchain/cohere", "Member[ChatCohere].Instance"]
- ["langchain.ChatModel", "@langchain/community/chat_models/fireworks", "Member[ChatFireworks].Instance"]
- ["langchain.ChatModel", "@langchain/ollama", "Member[ChatOllama].Instance"]
- ["langchain.ChatModel", "@langchain/aws", "Member[BedrockChat,ChatBedrockConverse].Instance"]
- ["langchain.ChatModel", "@langchain/community/chat_models/togetherai", "Member[ChatTogetherAI].Instance"]
- ["langchain.ChatModel", "@langchain/xai", "Member[ChatXAI].Instance"]
- ["langchain.ChatModel", "@langchain/openrouter", "Member[ChatOpenRouter].Instance"]
- ["langchain.ChatModel", "langchain", "Member[initChatModel].ReturnValue.Awaited"]
- ["langchain.AgentExecutor", "langchain/agents", "Member[AgentExecutor].Instance"]
- ["langchain.AgentExecutor", "langchain/agents", "Member[AgentExecutor].Member[fromAgentAndTools].ReturnValue"]
- ["langchain.Agent", "langchain", "Member[createAgent].ReturnValue"]
- ["langchain.LLMChain", "langchain/chains", "Member[LLMChain].Instance"]

- addsTo:
pack: codeql/javascript-all
extensible: sinkModel
data:
- ["@langchain/core/messages", "Member[HumanMessage].Argument[0]", "user-prompt-injection"]
- ["@langchain/core/messages", "Member[HumanMessage].Argument[0].Member[content]", "user-prompt-injection"]
- ["langchain", "Member[HumanMessage].Argument[0]", "user-prompt-injection"]
- ["langchain", "Member[HumanMessage].Argument[0].Member[content]", "user-prompt-injection"]
- ["@langchain/core/messages", "Member[SystemMessage].Argument[0]", "system-prompt-injection"]
- ["@langchain/core/messages", "Member[SystemMessage].Argument[0].Member[content]", "system-prompt-injection"]
- ["langchain", "Member[SystemMessage].Argument[0]", "system-prompt-injection"]
- ["langchain", "Member[SystemMessage].Argument[0].Member[content]", "system-prompt-injection"]
- ["langchain.ChatModel", "Member[invoke].Argument[0]", "user-prompt-injection"]
- ["langchain.ChatModel", "Member[stream].Argument[0]", "user-prompt-injection"]
- ["langchain.ChatModel", "Member[call].Argument[0]", "user-prompt-injection"]
- ["langchain.ChatModel", "Member[predict].Argument[0]", "user-prompt-injection"]
- ["langchain.ChatModel", "Member[batch].Argument[0].ArrayElement", "user-prompt-injection"]
- ["langchain.ChatModel", "Member[generate].Argument[0].ArrayElement.ArrayElement", "user-prompt-injection"]
- ["langchain.AgentExecutor", "Member[invoke].Argument[0].Member[input]", "user-prompt-injection"]
- ["langchain.Agent", "Member[invoke].Argument[0].Member[messages].ArrayElement.Member[content]", "user-prompt-injection"]
- ["langchain.Agent", "Member[stream].Argument[0].Member[messages].ArrayElement.Member[content]", "user-prompt-injection"]
- ["langchain", "Member[createAgent].Argument[0].Member[systemPrompt]", "system-prompt-injection"]
- ["langchain.LLMChain", "Member[call,invoke].Argument[0].Member[input]", "user-prompt-injection"]
- ["@langchain/core/prompts", "Member[ChatPromptTemplate].Member[fromMessages].Argument[0].ArrayElement.ArrayElement", "user-prompt-injection"]
- ["@langchain/core/prompts", "Member[PromptTemplate].Instance.Member[format].Argument[0]", "user-prompt-injection"]
25 changes: 25 additions & 0 deletions javascript/ql/lib/ext/openai.model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: typeModel
data:
- ["openai.Client", "openai", "Instance"]
- ["openai.Client", "openai", "Member[OpenAI,AzureOpenAI].Instance"]
- ["openai.Client", "@openai/guardrails", "Member[GuardrailsOpenAI,GuardrailsAzureOpenAI].Member[create].ReturnValue.Awaited"]

- addsTo:
pack: codeql/javascript-all
extensible: sinkModel
data:
- ["openai.Client", "Member[responses].Member[create].Argument[0].Member[instructions]", "system-prompt-injection"]
- ["openai.Client", "Member[beta].Member[assistants].Member[create,update].Argument[0].Member[instructions]", "system-prompt-injection"]
- ["openai.Client", "Member[beta].Member[threads].Member[runs].Member[create].Argument[1].Member[instructions,additional_instructions]", "system-prompt-injection"]
- ["@openai/agents", "Member[Agent].Argument[0].Member[instructions,handoffDescription]", "system-prompt-injection"]
- ["@openai/guardrails", "Member[Agent].Argument[0].Member[instructions,handoffDescription]", "system-prompt-injection"]
- ["@openai/agents", "Member[Agent].Instance.Member[asTool].Argument[0].Member[toolDescription]", "system-prompt-injection"]
- ["@openai/guardrails", "Member[Agent].Instance.Member[asTool].Argument[0].Member[toolDescription]", "system-prompt-injection"]
- ["@openai/agents", "Member[tool].Argument[0].Member[description]", "system-prompt-injection"]
- ["@openai/guardrails", "Member[tool].Argument[0].Member[description]", "system-prompt-injection"]
- ["@openai/guardrails", "Member[GuardrailAgent].Member[create].Argument[2]", "system-prompt-injection"]
- ["@openai/agents", "Member[run].Argument[1]", "user-prompt-injection"]
- ["@openai/agents", "Member[Runner].Instance.Member[run].Argument[1]", "user-prompt-injection"]
19 changes: 19 additions & 0 deletions javascript/ql/lib/ext/openrouter.model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
extensions:
- addsTo:
pack: codeql/javascript-all
extensible: typeModel
data:
- ["openrouter.Client", "@openrouter/sdk", "Instance"]
- ["openrouter.Client", "@openrouter/sdk", "Member[OpenRouter].Instance"]
- ["openrouter.Agent", "@openrouter/agent", "Member[OpenRouter].Instance"]

- addsTo:
pack: codeql/javascript-all
extensible: sinkModel
data:
- ["@openrouter/agent", "Member[callModel].Argument[0].Member[instructions]", "system-prompt-injection"]
- ["openrouter.Agent", "Member[callModel].Argument[0].Member[instructions]", "system-prompt-injection"]
- ["@openrouter/agent", "Member[tool].Argument[0].Member[description]", "system-prompt-injection"]
- ["openrouter.Client", "Member[embeddings].Member[create].Argument[0].Member[input]", "user-prompt-injection"]
- ["@openrouter/agent", "Member[callModel].Argument[0].Member[input]", "user-prompt-injection"]
- ["openrouter.Agent", "Member[callModel].Argument[0].Member[input]", "user-prompt-injection"]
25 changes: 25 additions & 0 deletions javascript/ql/lib/semmle/javascript/Concepts.qll
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,28 @@ module Cryptography {

class CryptographicAlgorithm = SC::CryptographicAlgorithm;
}

/**
* A data-flow node that prompts an AI model.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `AIPrompt::Range` instead.
*/
class AIPrompt extends DataFlow::Node instanceof AIPrompt::Range {
/** Gets an input that is used as AI prompt. */
DataFlow::Node getAPrompt() { result = super.getAPrompt() }
}

/** Provides a class for modeling new AI prompting mechanisms. */
module AIPrompt {
/**
* A data-flow node that prompts an AI model.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `AIPrompt` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets an input that is used as AI prompt. */
abstract DataFlow::Node getAPrompt();
}
}
53 changes: 53 additions & 0 deletions javascript/ql/lib/semmle/javascript/frameworks/Anthropic.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Provides classes modeling security-relevant aspects of the `@anthropic-ai/sdk` package.
* See https://github.com/anthropics/anthropic-sdk-typescript
*
* Structurally typed sinks (system, beta.agents) have been moved to
* Models as Data: javascript/ql/lib/ext/anthropic.model.yml
*
* This file retains only role-filtered message sinks that require inspecting
* a sibling `role` property, which MaD cannot express.
*/

private import javascript

/** Provides classes modeling prompt-injection sources of the `@anthropic-ai/sdk` package. */
module Anthropic {
/** Gets a reference to the `Anthropic` client instance. */
private API::Node classRef() { result = API::moduleImport("@anthropic-ai/sdk").getInstance() }

/** Gets a reference to the messages.create params (both stable and beta). */
private API::Node messagesCreateParams() {
result = classRef().getMember("messages").getMember("create").getParameter(0)
or
result = classRef().getMember("beta").getMember("messages").getMember("create").getParameter(0)
}

/**
* Gets role-filtered system/assistant message sinks.
* These require checking a sibling `role` property and cannot be expressed in MaD.
*/
API::Node getSystemOrAssistantPromptNode() {
// messages: [{ role: "assistant", content: "..." }]
exists(API::Node msg |
msg = messagesCreateParams().getMember("messages").getArrayElement() and
msg.getMember("role").asSink().mayHaveStringValue(["system", "assistant"])
|
result = msg.getMember("content")
)
}

/**
* Gets role-filtered user message sinks.
* These require checking a sibling `role` property and cannot be expressed in MaD.
*/
API::Node getUserPromptNode() {
// messages: [{ role: "user", content: "..." }]
exists(API::Node msg |
msg = messagesCreateParams().getMember("messages").getArrayElement() and
not msg.getMember("role").asSink().mayHaveStringValue(["system", "assistant"])
|
result = msg.getMember("content")
)
}
}
61 changes: 61 additions & 0 deletions javascript/ql/lib/semmle/javascript/frameworks/GoogleGenAI.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Provides classes modeling security-relevant aspects of the `@google/genai` package.
* See https://github.com/googleapis/js-genai
*
* Structurally typed sinks (systemInstruction, prompt, message, etc.) have been
* moved to Models as Data: javascript/ql/lib/ext/google-genai.model.yml
*
* This file retains only role-filtered content sinks that require inspecting
* a sibling `role` property, which MaD cannot express.
*/

private import javascript

/** Provides classes modeling prompt-injection sources of the `@google/genai` package. */
module GoogleGenAI {
/** Gets a reference to the `GoogleGenAI` client instance. */
private API::Node clientRef() {
result = API::moduleImport("@google/genai").getMember("GoogleGenAI").getInstance()
}

/**
* Gets role-filtered system/model message sinks.
* These require checking a sibling `role` property and cannot be expressed in MaD.
*/
API::Node getSystemOrAssistantPromptNode() {
// contents: [{ role: "model", parts: [{ text: "..." }] }]
// Gemini uses "model" role instead of "assistant"
exists(API::Node msg |
msg =
clientRef()
.getMember("models")
.getMember(["generateContent", "generateContentStream"])
.getParameter(0)
.getMember("contents")
.getArrayElement() and
msg.getMember("role").asSink().mayHaveStringValue(["system", "model"])
|
result = msg.getMember("parts").getArrayElement().getMember("text")
)
}

/**
* Gets role-filtered user message sinks.
* These require checking a sibling `role` property and cannot be expressed in MaD.
*/
API::Node getUserPromptNode() {
// contents: [{ role: "user", parts: [{ text: "..." }] }]
exists(API::Node msg |
msg =
clientRef()
.getMember("models")
.getMember(["generateContent", "generateContentStream"])
.getParameter(0)
.getMember("contents")
.getArrayElement() and
not msg.getMember("role").asSink().mayHaveStringValue(["system", "model"])
|
result = msg.getMember("parts").getArrayElement().getMember("text")
)
}
}
Loading
Loading