Skip to content

Commit 1a0feb4

Browse files
committed
precise models for experimental query
1 parent c7d99a1 commit 1a0feb4

File tree

10 files changed

+128
-50
lines changed

10 files changed

+128
-50
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
---
22
category: minorAnalysis
33
---
4+
* Added experimental query `py/prompt-injection` to detect potential prompt injection vulnerabilities in code using LLMs.
45
* Added taint flow model and type model for `agents` and `openai` modules.

python/ql/lib/semmle/python/frameworks/openai.model.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ extensions:
33
pack: codeql/python-all
44
extensible: sinkModel
55
data:
6-
- ['OpenAI', 'Member[responses].Member[create].Argument[input:,instructions:]', 'prompt-injection']
7-
- ['OpenAI', 'Member[realtime].Member[connect].ReturnValue.Member[conversation].Member[item].Member[create].Argument[item:]', 'prompt-injection']
8-
- ['OpenAI', 'Member[chat].Member[completions].Member[create].Argument[messages:]', 'prompt-injection']
6+
- ['OpenAI', 'Member[beta].Member[assistants].Member[create].Argument[instructions:]', 'prompt-injection']
97

108
- addsTo:
119
pack: codeql/python-all

python/ql/lib/semmle/python/security/dataflow/PromptInjectionCustomizations.qll

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ private import semmle.python.Concepts
1010
private import semmle.python.dataflow.new.RemoteFlowSources
1111
private import semmle.python.dataflow.new.BarrierGuards
1212
private import semmle.python.frameworks.data.ModelsAsData
13+
private import semmle.python.ApiGraphs
1314

1415
/**
1516
* Provides default sources, sinks and sanitizers for detecting
@@ -48,6 +49,53 @@ module PromptInjection {
4849
SinkFromModel() { this = ModelOutput::getASinkNode("prompt-injection").asSink() }
4950
}
5051

52+
private class PromptContentSink extends Sink {
53+
PromptContentSink() {
54+
exists(API::Node openai, API::Node content |
55+
openai =
56+
API::moduleImport("openai")
57+
.getMember(["OpenAI", "AsyncOpenAI", "AzureOpenAI"])
58+
.getReturn() and
59+
content =
60+
[
61+
openai
62+
.getMember("responses")
63+
.getMember("create")
64+
.getKeywordParameter(["input", "instructions"]),
65+
openai
66+
.getMember("responses")
67+
.getMember("create")
68+
.getKeywordParameter(["input", "instructions"])
69+
.getASubscript()
70+
.getSubscript("content"),
71+
openai
72+
.getMember("realtime")
73+
.getMember("connect")
74+
.getReturn()
75+
.getMember("conversation")
76+
.getMember("item")
77+
.getMember("create")
78+
.getKeywordParameter("item")
79+
.getSubscript("content"),
80+
openai
81+
.getMember("chat")
82+
.getMember("completions")
83+
.getMember("create")
84+
.getKeywordParameter("messages")
85+
.getASubscript()
86+
.getSubscript("content")
87+
]
88+
|
89+
// content
90+
if not exists(content.getASubscript())
91+
then this = content.asSink()
92+
else
93+
// content.text
94+
this = content.getASubscript().getSubscript("text").asSink()
95+
)
96+
}
97+
}
98+
5199
/**
52100
* A comparison with a constant, considered as a sanitizer-guard.
53101
*/

python/ql/src/Security/CWE-1427/PromptInjection.qhelp renamed to python/ql/src/experimental/Security/CWE-1427/PromptInjection.qhelp

File renamed without changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @name Prompt injection
3+
* @kind path-problem
4+
* @problem.severity error
5+
* @security-severity 5.0
6+
* @precision high
7+
* @id py/prompt-injection
8+
* @tags security
9+
* experimental
10+
* external/cwe/cwe-1427
11+
*/
12+
13+
import python
14+
import semmle.python.security.dataflow.PromptInjectionQuery
15+
import PromptInjectionFlow::PathGraph
16+
17+
from PromptInjectionFlow::PathNode source, PromptInjectionFlow::PathNode sink
18+
where PromptInjectionFlow::flowPath(source, sink)
19+
select sink.getNode(), source, sink, "This prompt construction depends on a $@.", source.getNode(),
20+
"user-provided value"

python/ql/src/Security/CWE-1427/examples/example.py renamed to python/ql/src/experimental/Security/CWE-1427/examples/example.py

File renamed without changes.

python/ql/test/query-tests/Security/CWE-1427-PromptInjection/PromptInjection.expected renamed to python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/PromptInjection.expected

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@
33
| openai_test.py:17:22:17:46 | ControlFlowNode for BinaryExpr | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:17:22:17:46 | ControlFlowNode for BinaryExpr | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
44
| openai_test.py:18:15:18:19 | ControlFlowNode for query | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:18:15:18:19 | ControlFlowNode for query | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
55
| openai_test.py:22:22:22:46 | ControlFlowNode for BinaryExpr | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:22:22:22:46 | ControlFlowNode for BinaryExpr | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
6-
| openai_test.py:23:15:37:9 | ControlFlowNode for List | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:23:15:37:9 | ControlFlowNode for List | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
6+
| openai_test.py:26:28:26:51 | ControlFlowNode for BinaryExpr | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:26:28:26:51 | ControlFlowNode for BinaryExpr | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
7+
| openai_test.py:33:33:33:37 | ControlFlowNode for query | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:33:33:33:37 | ControlFlowNode for query | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
78
| openai_test.py:41:22:41:46 | ControlFlowNode for BinaryExpr | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:41:22:41:46 | ControlFlowNode for BinaryExpr | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
89
| openai_test.py:42:15:42:19 | ControlFlowNode for query | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:42:15:42:19 | ControlFlowNode for query | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
9-
| openai_test.py:47:18:55:13 | ControlFlowNode for Dict | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:47:18:55:13 | ControlFlowNode for Dict | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
10-
| openai_test.py:59:18:70:9 | ControlFlowNode for List | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:59:18:70:9 | ControlFlowNode for List | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
11-
| openai_test.py:74:18:83:9 | ControlFlowNode for List | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:74:18:83:9 | ControlFlowNode for List | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
10+
| openai_test.py:53:33:53:37 | ControlFlowNode for query | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:53:33:53:37 | ControlFlowNode for query | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
11+
| openai_test.py:63:28:63:51 | ControlFlowNode for BinaryExpr | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:63:28:63:51 | ControlFlowNode for BinaryExpr | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
12+
| openai_test.py:67:28:67:32 | ControlFlowNode for query | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:67:28:67:32 | ControlFlowNode for query | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
13+
| openai_test.py:71:28:71:32 | ControlFlowNode for query | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:71:28:71:32 | ControlFlowNode for query | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
14+
| openai_test.py:80:28:80:51 | ControlFlowNode for BinaryExpr | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:80:28:80:51 | ControlFlowNode for BinaryExpr | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
15+
| openai_test.py:84:28:84:32 | ControlFlowNode for query | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:84:28:84:32 | ControlFlowNode for query | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
16+
| openai_test.py:92:22:92:46 | ControlFlowNode for BinaryExpr | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:92:22:92:46 | ControlFlowNode for BinaryExpr | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value |
1217
edges
1318
| agent_instructions.py:2:26:2:32 | ControlFlowNode for ImportMember | agent_instructions.py:2:26:2:32 | ControlFlowNode for request | provenance | |
1419
| agent_instructions.py:2:26:2:32 | ControlFlowNode for request | agent_instructions.py:7:13:7:19 | ControlFlowNode for request | provenance | |
@@ -19,33 +24,27 @@ edges
1924
| openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:2:26:2:32 | ControlFlowNode for request | provenance | |
2025
| openai_test.py:2:26:2:32 | ControlFlowNode for request | openai_test.py:12:15:12:21 | ControlFlowNode for request | provenance | |
2126
| openai_test.py:2:26:2:32 | ControlFlowNode for request | openai_test.py:13:13:13:19 | ControlFlowNode for request | provenance | |
22-
| openai_test.py:2:26:2:32 | ControlFlowNode for request | openai_test.py:14:12:14:18 | ControlFlowNode for request | provenance | |
23-
| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:17:22:17:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:58613 |
24-
| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:22:22:22:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:58613 |
25-
| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:23:15:37:9 | ControlFlowNode for List | provenance | Sink:MaD:58613 |
26-
| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:41:22:41:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:58613 |
27-
| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:59:18:70:9 | ControlFlowNode for List | provenance | Sink:MaD:58615 |
28-
| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:74:18:83:9 | ControlFlowNode for List | provenance | Sink:MaD:58615 |
27+
| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:17:22:17:46 | ControlFlowNode for BinaryExpr | provenance | |
28+
| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:22:22:22:46 | ControlFlowNode for BinaryExpr | provenance | |
29+
| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:26:28:26:51 | ControlFlowNode for BinaryExpr | provenance | |
30+
| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:41:22:41:46 | ControlFlowNode for BinaryExpr | provenance | |
31+
| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:63:28:63:51 | ControlFlowNode for BinaryExpr | provenance | |
32+
| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:80:28:80:51 | ControlFlowNode for BinaryExpr | provenance | |
33+
| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:92:22:92:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:58613 |
2934
| openai_test.py:12:15:12:21 | ControlFlowNode for request | openai_test.py:12:15:12:26 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
3035
| openai_test.py:12:15:12:21 | ControlFlowNode for request | openai_test.py:13:13:13:24 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
31-
| openai_test.py:12:15:12:21 | ControlFlowNode for request | openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
3236
| openai_test.py:12:15:12:26 | ControlFlowNode for Attribute | openai_test.py:12:15:12:41 | ControlFlowNode for Attribute() | provenance | dict.get |
3337
| openai_test.py:12:15:12:41 | ControlFlowNode for Attribute() | openai_test.py:12:5:12:11 | ControlFlowNode for persona | provenance | |
34-
| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:18:15:18:19 | ControlFlowNode for query | provenance | Sink:MaD:58613 |
35-
| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:23:15:37:9 | ControlFlowNode for List | provenance | Sink:MaD:58613 |
36-
| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:42:15:42:19 | ControlFlowNode for query | provenance | Sink:MaD:58613 |
37-
| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:47:18:55:13 | ControlFlowNode for Dict | provenance | Sink:MaD:58614 |
38-
| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:59:18:70:9 | ControlFlowNode for List | provenance | Sink:MaD:58615 |
39-
| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:74:18:83:9 | ControlFlowNode for List | provenance | Sink:MaD:58615 |
38+
| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:18:15:18:19 | ControlFlowNode for query | provenance | |
39+
| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:33:33:33:37 | ControlFlowNode for query | provenance | |
40+
| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:42:15:42:19 | ControlFlowNode for query | provenance | |
41+
| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:53:33:53:37 | ControlFlowNode for query | provenance | |
42+
| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:67:28:67:32 | ControlFlowNode for query | provenance | |
43+
| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:71:28:71:32 | ControlFlowNode for query | provenance | |
44+
| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:84:28:84:32 | ControlFlowNode for query | provenance | |
4045
| openai_test.py:13:13:13:19 | ControlFlowNode for request | openai_test.py:13:13:13:24 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
41-
| openai_test.py:13:13:13:19 | ControlFlowNode for request | openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
4246
| openai_test.py:13:13:13:24 | ControlFlowNode for Attribute | openai_test.py:13:13:13:37 | ControlFlowNode for Attribute() | provenance | dict.get |
4347
| openai_test.py:13:13:13:37 | ControlFlowNode for Attribute() | openai_test.py:13:5:13:9 | ControlFlowNode for query | provenance | |
44-
| openai_test.py:14:5:14:8 | ControlFlowNode for role | openai_test.py:47:18:55:13 | ControlFlowNode for Dict | provenance | Sink:MaD:58614 |
45-
| openai_test.py:14:5:14:8 | ControlFlowNode for role | openai_test.py:59:18:70:9 | ControlFlowNode for List | provenance | Sink:MaD:58615 |
46-
| openai_test.py:14:12:14:18 | ControlFlowNode for request | openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
47-
| openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | openai_test.py:14:12:14:35 | ControlFlowNode for Attribute() | provenance | dict.get |
48-
| openai_test.py:14:12:14:35 | ControlFlowNode for Attribute() | openai_test.py:14:5:14:8 | ControlFlowNode for role | provenance | |
4948
nodes
5049
| agent_instructions.py:2:26:2:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
5150
| agent_instructions.py:2:26:2:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
@@ -64,17 +63,18 @@ nodes
6463
| openai_test.py:13:13:13:19 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
6564
| openai_test.py:13:13:13:24 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
6665
| openai_test.py:13:13:13:37 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
67-
| openai_test.py:14:5:14:8 | ControlFlowNode for role | semmle.label | ControlFlowNode for role |
68-
| openai_test.py:14:12:14:18 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
69-
| openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
70-
| openai_test.py:14:12:14:35 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
7166
| openai_test.py:17:22:17:46 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
7267
| openai_test.py:18:15:18:19 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
7368
| openai_test.py:22:22:22:46 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
74-
| openai_test.py:23:15:37:9 | ControlFlowNode for List | semmle.label | ControlFlowNode for List |
69+
| openai_test.py:26:28:26:51 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
70+
| openai_test.py:33:33:33:37 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
7571
| openai_test.py:41:22:41:46 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
7672
| openai_test.py:42:15:42:19 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
77-
| openai_test.py:47:18:55:13 | ControlFlowNode for Dict | semmle.label | ControlFlowNode for Dict |
78-
| openai_test.py:59:18:70:9 | ControlFlowNode for List | semmle.label | ControlFlowNode for List |
79-
| openai_test.py:74:18:83:9 | ControlFlowNode for List | semmle.label | ControlFlowNode for List |
73+
| openai_test.py:53:33:53:37 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
74+
| openai_test.py:63:28:63:51 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
75+
| openai_test.py:67:28:67:32 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
76+
| openai_test.py:71:28:71:32 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
77+
| openai_test.py:80:28:80:51 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
78+
| openai_test.py:84:28:84:32 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
79+
| openai_test.py:92:22:92:46 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
8080
subpaths
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
query: Security/CWE-1427/PromptInjection.ql
2+
postprocess: utils/test/InlineExpectationsTestQuery.ql

python/ql/test/query-tests/Security/CWE-1427-PromptInjection/agent_instructions.py renamed to python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/agent_instructions.py

File renamed without changes.

0 commit comments

Comments
 (0)