Skip to content

Commit 34da804

Browse files
committed
Move structurally typed prompt injection sinks to Models as Data
Move OpenAI, Anthropic, Google GenAI, and LangChain sinks that are structurally typed (identified by API name alone) into MaD YAML files. Role-filtered sinks that require inspecting a sibling 'role' property remain in QL code since MaD cannot express conditional logic. Use two distinct sink kinds: - user-prompt-injection: picked up by UserPromptInjection.ql - system-prompt-injection: picked up by SystemPromptInjection.ql New files: - javascript/ql/lib/ext/openai.model.yml - javascript/ql/lib/ext/anthropic.model.yml - javascript/ql/lib/ext/google-genai.model.yml - javascript/ql/lib/ext/langchain.model.yml
1 parent 98379cf commit 34da804

9 files changed

Lines changed: 233 additions & 379 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/javascript-all
4+
extensible: typeModel
5+
data:
6+
- ["anthropic.Client", "@anthropic-ai/sdk", "Instance"]
7+
8+
- addsTo:
9+
pack: codeql/javascript-all
10+
extensible: sinkModel
11+
data:
12+
- ["anthropic.Client", "Member[messages].Member[create].Argument[0].Member[system]", "system-prompt-injection"]
13+
- ["anthropic.Client", "Member[messages].Member[create].Argument[0].Member[system].ArrayElement.Member[text]", "system-prompt-injection"]
14+
- ["anthropic.Client", "Member[beta].Member[messages].Member[create].Argument[0].Member[system]", "system-prompt-injection"]
15+
- ["anthropic.Client", "Member[beta].Member[messages].Member[create].Argument[0].Member[system].ArrayElement.Member[text]", "system-prompt-injection"]
16+
- ["anthropic.Client", "Member[beta].Member[agents].Member[create].Argument[0].Member[system]", "system-prompt-injection"]
17+
- ["anthropic.Client", "Member[beta].Member[agents].Member[update].Argument[1].Member[system]", "system-prompt-injection"]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/javascript-all
4+
extensible: typeModel
5+
data:
6+
- ["google-genai.Client", "@google/genai", "Member[GoogleGenAI].Instance"]
7+
8+
- addsTo:
9+
pack: codeql/javascript-all
10+
extensible: sinkModel
11+
data:
12+
- ["google-genai.Client", "Member[models].Member[generateContent,generateContentStream].Argument[0].Member[config].Member[systemInstruction]", "system-prompt-injection"]
13+
- ["google-genai.Client", "Member[chats].Member[create].Argument[0].Member[config].Member[systemInstruction]", "system-prompt-injection"]
14+
- ["google-genai.Client", "Member[chats].Member[create].ReturnValue.Member[sendMessage].Argument[0].Member[config].Member[systemInstruction]", "system-prompt-injection"]
15+
- ["google-genai.Client", "Member[live].Member[connect].Argument[0].Member[config].Member[systemInstruction]", "system-prompt-injection"]
16+
- ["google-genai.Client", "Member[models].Member[generateContent,generateContentStream].Argument[0].Member[contents]", "user-prompt-injection"]
17+
- ["google-genai.Client", "Member[models].Member[generateImages].Argument[0].Member[prompt]", "user-prompt-injection"]
18+
- ["google-genai.Client", "Member[models].Member[editImage].Argument[0].Member[prompt]", "user-prompt-injection"]
19+
- ["google-genai.Client", "Member[models].Member[generateVideos].Argument[0].Member[prompt]", "user-prompt-injection"]
20+
- ["google-genai.Client", "Member[chats].Member[create].ReturnValue.Member[sendMessage,sendMessageStream].Argument[0].Member[message]", "user-prompt-injection"]
21+
- ["google-genai.Client", "Member[chats].Member[create].ReturnValue.Member[sendMessage,sendMessageStream].Argument[0].Member[content]", "user-prompt-injection"]
22+
- ["google-genai.Client", "Member[models].Member[embedContent].Argument[0].Member[content]", "user-prompt-injection"]
23+
- ["google-genai.Client", "Member[interactions].Member[create].Argument[0].Member[input]", "user-prompt-injection"]
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/javascript-all
4+
extensible: typeModel
5+
data:
6+
- ["langchain.ChatModel", "@langchain/openai", "Member[ChatOpenAI].Instance"]
7+
- ["langchain.ChatModel", "@langchain/anthropic", "Member[ChatAnthropic].Instance"]
8+
- ["langchain.ChatModel", "@langchain/google-genai", "Member[ChatGoogleGenerativeAI].Instance"]
9+
- ["langchain.ChatModel", "@langchain/mistralai", "Member[ChatMistralAI].Instance"]
10+
- ["langchain.ChatModel", "@langchain/groq", "Member[ChatGroq].Instance"]
11+
- ["langchain.ChatModel", "@langchain/cohere", "Member[ChatCohere].Instance"]
12+
- ["langchain.ChatModel", "@langchain/community/chat_models/fireworks", "Member[ChatFireworks].Instance"]
13+
- ["langchain.ChatModel", "@langchain/ollama", "Member[ChatOllama].Instance"]
14+
- ["langchain.ChatModel", "@langchain/aws", "Member[BedrockChat,ChatBedrockConverse].Instance"]
15+
- ["langchain.ChatModel", "@langchain/community/chat_models/togetherai", "Member[ChatTogetherAI].Instance"]
16+
- ["langchain.ChatModel", "@langchain/xai", "Member[ChatXAI].Instance"]
17+
- ["langchain.ChatModel", "@langchain/openrouter", "Member[ChatOpenRouter].Instance"]
18+
- ["langchain.ChatModel", "langchain", "Member[initChatModel].ReturnValue.Awaited"]
19+
- ["langchain.AgentExecutor", "langchain/agents", "Member[AgentExecutor].Instance"]
20+
- ["langchain.AgentExecutor", "langchain/agents", "Member[AgentExecutor].Member[fromAgentAndTools].ReturnValue"]
21+
- ["langchain.Agent", "langchain", "Member[createAgent].ReturnValue"]
22+
- ["langchain.LLMChain", "langchain/chains", "Member[LLMChain].Instance"]
23+
24+
- addsTo:
25+
pack: codeql/javascript-all
26+
extensible: sinkModel
27+
data:
28+
- ["@langchain/core/messages", "Member[HumanMessage].Argument[0]", "user-prompt-injection"]
29+
- ["@langchain/core/messages", "Member[HumanMessage].Argument[0].Member[content]", "user-prompt-injection"]
30+
- ["langchain", "Member[HumanMessage].Argument[0]", "user-prompt-injection"]
31+
- ["langchain", "Member[HumanMessage].Argument[0].Member[content]", "user-prompt-injection"]
32+
- ["@langchain/core/messages", "Member[SystemMessage].Argument[0]", "system-prompt-injection"]
33+
- ["@langchain/core/messages", "Member[SystemMessage].Argument[0].Member[content]", "system-prompt-injection"]
34+
- ["langchain", "Member[SystemMessage].Argument[0]", "system-prompt-injection"]
35+
- ["langchain", "Member[SystemMessage].Argument[0].Member[content]", "system-prompt-injection"]
36+
- ["langchain.ChatModel", "Member[invoke].Argument[0]", "user-prompt-injection"]
37+
- ["langchain.ChatModel", "Member[stream].Argument[0]", "user-prompt-injection"]
38+
- ["langchain.ChatModel", "Member[call].Argument[0]", "user-prompt-injection"]
39+
- ["langchain.ChatModel", "Member[predict].Argument[0]", "user-prompt-injection"]
40+
- ["langchain.ChatModel", "Member[batch].Argument[0].ArrayElement", "user-prompt-injection"]
41+
- ["langchain.ChatModel", "Member[generate].Argument[0].ArrayElement.ArrayElement", "user-prompt-injection"]
42+
- ["langchain.AgentExecutor", "Member[invoke].Argument[0].Member[input]", "user-prompt-injection"]
43+
- ["langchain.Agent", "Member[invoke].Argument[0].Member[messages].ArrayElement.Member[content]", "user-prompt-injection"]
44+
- ["langchain.Agent", "Member[stream].Argument[0].Member[messages].ArrayElement.Member[content]", "user-prompt-injection"]
45+
- ["langchain", "Member[createAgent].Argument[0].Member[systemPrompt]", "system-prompt-injection"]
46+
- ["langchain.LLMChain", "Member[call,invoke].Argument[0].Member[input]", "user-prompt-injection"]
47+
- ["@langchain/core/prompts", "Member[ChatPromptTemplate].Member[fromMessages].Argument[0].ArrayElement.ArrayElement", "user-prompt-injection"]
48+
- ["@langchain/core/prompts", "Member[PromptTemplate].Instance.Member[format].Argument[0]", "user-prompt-injection"]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/javascript-all
4+
extensible: typeModel
5+
data:
6+
- ["openai.Client", "openai", "Instance"]
7+
- ["openai.Client", "openai", "Member[OpenAI,AzureOpenAI].Instance"]
8+
- ["openai.Client", "@openai/guardrails", "Member[GuardrailsOpenAI,GuardrailsAzureOpenAI].Member[create].ReturnValue.Awaited"]
9+
10+
- addsTo:
11+
pack: codeql/javascript-all
12+
extensible: sinkModel
13+
data:
14+
- ["openai.Client", "Member[responses].Member[create].Argument[0].Member[instructions]", "system-prompt-injection"]
15+
- ["openai.Client", "Member[beta].Member[assistants].Member[create,update].Argument[0].Member[instructions]", "system-prompt-injection"]
16+
- ["openai.Client", "Member[beta].Member[threads].Member[runs].Member[create].Argument[1].Member[instructions,additional_instructions]", "system-prompt-injection"]
17+
- ["@openai/agents", "Member[Agent].Argument[0].Member[instructions,handoffDescription]", "system-prompt-injection"]
18+
- ["@openai/guardrails", "Member[Agent].Argument[0].Member[instructions,handoffDescription]", "system-prompt-injection"]
19+
- ["@openai/agents", "Member[Agent].Instance.Member[asTool].Argument[0].Member[toolDescription]", "system-prompt-injection"]
20+
- ["@openai/guardrails", "Member[Agent].Instance.Member[asTool].Argument[0].Member[toolDescription]", "system-prompt-injection"]
21+
- ["@openai/agents", "Member[tool].Argument[0].Member[description]", "system-prompt-injection"]
22+
- ["@openai/guardrails", "Member[tool].Argument[0].Member[description]", "system-prompt-injection"]
23+
- ["@openai/guardrails", "Member[GuardrailAgent].Member[create].Argument[2]", "system-prompt-injection"]
24+
- ["openai.Client", "Member[responses].Member[create].Argument[0].Member[input]", "user-prompt-injection"]
25+
- ["openai.Client", "Member[completions].Member[create].Argument[0].Member[prompt]", "user-prompt-injection"]
26+
- ["openai.Client", "Member[images].Member[generate,edit].Argument[0].Member[prompt]", "user-prompt-injection"]
27+
- ["openai.Client", "Member[embeddings].Member[create].Argument[0].Member[input]", "user-prompt-injection"]
28+
- ["openai.Client", "Member[audio].Member[transcriptions,translations].Member[create].Argument[0].Member[prompt]", "user-prompt-injection"]
Lines changed: 33 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,55 @@
11
/**
22
* Provides classes modeling security-relevant aspects of the `@anthropic-ai/sdk` package.
33
* See https://github.com/anthropics/anthropic-sdk-typescript
4+
*
5+
* Structurally typed sinks (system, beta.agents) have been moved to
6+
* Models as Data: javascript/ql/lib/ext/anthropic.model.yml
7+
*
8+
* This file retains only role-filtered message sinks that require inspecting
9+
* a sibling `role` property, which MaD cannot express.
410
*/
511

612
private import javascript
713

814
module Anthropic {
915
/** Gets a reference to the `Anthropic` client instance. */
10-
API::Node classRef() {
11-
// Default export: import Anthropic from '@anthropic-ai/sdk'; new Anthropic()
16+
private API::Node classRef() {
1217
result = API::moduleImport("@anthropic-ai/sdk").getInstance()
1318
}
1419

15-
/** Gets a reference to a sink for the system prompt in the Anthropic messages API. */
20+
/** Gets a reference to the messages.create params (both stable and beta). */
21+
private API::Node messagesCreateParams() {
22+
result = classRef().getMember("messages").getMember("create").getParameter(0)
23+
or
24+
result =
25+
classRef().getMember("beta").getMember("messages").getMember("create").getParameter(0)
26+
}
27+
28+
/**
29+
* Gets role-filtered system/assistant message sinks.
30+
* These require checking a sibling `role` property and cannot be expressed in MaD.
31+
*/
1632
API::Node getSystemOrAssistantPromptNode() {
17-
exists(API::Node createParams |
18-
// client.messages.create({ ... })
19-
createParams = classRef()
20-
.getMember("messages")
21-
.getMember("create")
22-
.getParameter(0)
23-
or
24-
// client.beta.messages.create({ ... })
25-
createParams = classRef()
26-
.getMember("beta")
27-
.getMember("messages")
28-
.getMember("create")
29-
.getParameter(0)
33+
// messages: [{ role: "assistant", content: "..." }]
34+
exists(API::Node msg |
35+
msg = messagesCreateParams().getMember("messages").getArrayElement() and
36+
msg.getMember("role").asSink().mayHaveStringValue("assistant")
3037
|
31-
// system: "string"
32-
result = createParams.getMember("system")
33-
or
34-
// system: [{ type: "text", text: "..." }]
35-
result = createParams.getMember("system").getArrayElement().getMember("text")
36-
or
37-
// messages: [{ role: "assistant", content: "..." }]
38-
// Injecting content into what the model said from external sources is very likely an injection.
39-
exists(API::Node msg |
40-
msg = createParams.getMember("messages").getArrayElement() and
41-
msg.getMember("role").asSink().mayHaveStringValue("assistant")
42-
|
43-
result = msg.getMember("content")
44-
)
38+
result = msg.getMember("content")
4539
)
46-
or
47-
// client.beta.agents.create({ system: "..." })
48-
result = classRef()
49-
.getMember("beta")
50-
.getMember("agents")
51-
.getMember("create")
52-
.getParameter(0)
53-
.getMember("system")
54-
or
55-
// client.beta.agents.update(agentId, { system: "..." })
56-
result = classRef()
57-
.getMember("beta")
58-
.getMember("agents")
59-
.getMember("update")
60-
.getParameter(1)
61-
.getMember("system")
6240
}
6341

64-
/** Gets a reference to nodes where potential user input can land. */
42+
/**
43+
* Gets role-filtered user message sinks.
44+
* These require checking a sibling `role` property and cannot be expressed in MaD.
45+
*/
6546
API::Node getUserPromptNode() {
66-
exists(API::Node createParams |
67-
// client.messages.create({ ... })
68-
createParams = classRef()
69-
.getMember("messages")
70-
.getMember("create")
71-
.getParameter(0)
72-
or
73-
// client.beta.messages.create({ ... })
74-
createParams = classRef()
75-
.getMember("beta")
76-
.getMember("messages")
77-
.getMember("create")
78-
.getParameter(0)
47+
// messages: [{ role: "user", content: "..." }]
48+
exists(API::Node msg |
49+
msg = messagesCreateParams().getMember("messages").getArrayElement() and
50+
not msg.getMember("role").asSink().mayHaveStringValue("assistant")
7951
|
80-
// messages: [{ role: "user", content: "..." }]
81-
exists(API::Node msg |
82-
msg = createParams.getMember("messages").getArrayElement() and
83-
not msg.getMember("role").asSink().mayHaveStringValue("assistant")
84-
|
85-
result = msg.getMember("content")
86-
)
52+
result = msg.getMember("content")
8753
)
8854
}
8955
}

0 commit comments

Comments
 (0)