Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -57,6 +57,7 @@ ql/javascript/ql/src/definitions.ql
ql/javascript/ql/src/experimental/Security/CWE-094-dataURL/CodeInjection.ql
ql/javascript/ql/src/experimental/Security/CWE-099/EnvValueAndKeyInjection.ql
ql/javascript/ql/src/experimental/Security/CWE-099/EnvValueInjection.ql
ql/javascript/ql/src/experimental/Security/CWE-1427/UserPromptInjection.ql
ql/javascript/ql/src/experimental/Security/CWE-340/TokenBuiltFromUUID.ql
ql/javascript/ql/src/experimental/Security/CWE-347/decodeJwtWithoutVerification.ql
ql/javascript/ql/src/experimental/Security/CWE-347/decodeJwtWithoutVerificationLocalSource.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