From bb18bb084c063287d27f90a4855dabb1fdb68e63 Mon Sep 17 00:00:00 2001 From: Josef Svenningsson Date: Thu, 2 Apr 2026 17:42:37 +0100 Subject: [PATCH 1/5] Improve prompt inject for Python --- .../python/frameworks/anthropic.model.yml | 17 +++++++++++ .../semmle/python/frameworks/openai.model.yml | 3 ++ .../semmle/python/frameworks/OpenAI.qll | 30 +++++++++++++++++++ .../PromptInjectionCustomizations.qll | 3 ++ 4 files changed, 53 insertions(+) create mode 100644 python/ql/lib/semmle/python/frameworks/anthropic.model.yml diff --git a/python/ql/lib/semmle/python/frameworks/anthropic.model.yml b/python/ql/lib/semmle/python/frameworks/anthropic.model.yml new file mode 100644 index 000000000000..b7ef32218ad7 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/anthropic.model.yml @@ -0,0 +1,17 @@ +extensions: + - addsTo: + pack: codeql/python-all + extensible: sinkModel + data: + - ['Anthropic', 'Member[messages].Member[create].Argument[system:]', 'prompt-injection'] + - ['Anthropic', 'Member[messages].Member[stream].Argument[system:]', 'prompt-injection'] + - ['Anthropic', 'Member[beta].Member[messages].Member[create].Argument[system:]', 'prompt-injection'] + - ['Anthropic', 'Member[messages].Member[create].Argument[messages:].ListElement.DictionaryElement[content]', 'prompt-injection'] + - ['Anthropic', 'Member[messages].Member[stream].Argument[messages:].ListElement.DictionaryElement[content]', 'prompt-injection'] + - ['Anthropic', 'Member[beta].Member[messages].Member[create].Argument[messages:].ListElement.DictionaryElement[content]', 'prompt-injection'] + + - addsTo: + pack: codeql/python-all + extensible: typeModel + data: + - ['Anthropic', 'anthropic', 'Member[Anthropic,AsyncAnthropic].ReturnValue'] diff --git a/python/ql/lib/semmle/python/frameworks/openai.model.yml b/python/ql/lib/semmle/python/frameworks/openai.model.yml index 245d390ab8eb..358039595e9d 100644 --- a/python/ql/lib/semmle/python/frameworks/openai.model.yml +++ b/python/ql/lib/semmle/python/frameworks/openai.model.yml @@ -4,6 +4,9 @@ extensions: extensible: sinkModel data: - ['OpenAI', 'Member[beta].Member[assistants].Member[create].Argument[instructions:]', 'prompt-injection'] + - ['OpenAI', 'Member[chat].Member[completions].Member[create].Argument[messages:].ListElement.DictionaryElement[content]', 'prompt-injection'] + - ['OpenAI', 'Member[responses].Member[create].Argument[instructions:]', 'prompt-injection'] + - ['OpenAI', 'Member[responses].Member[create].Argument[input:]', 'prompt-injection'] - addsTo: pack: codeql/python-all diff --git a/python/ql/src/experimental/semmle/python/frameworks/OpenAI.qll b/python/ql/src/experimental/semmle/python/frameworks/OpenAI.qll index 74614a739aa4..e5649716c8af 100644 --- a/python/ql/src/experimental/semmle/python/frameworks/OpenAI.qll +++ b/python/ql/src/experimental/semmle/python/frameworks/OpenAI.qll @@ -7,6 +7,7 @@ private import python private import semmle.python.ApiGraphs +private import semmle.python.dataflow.new.DataFlow /** * Provides models for agents SDK (instances of the `agents.Runner` class etc). @@ -86,3 +87,32 @@ module OpenAI { ) } } + +/** + * Provides attribute-name-based sink detection for `chat.completions.create` calls. + * This does not rely on API graph type resolution and thus works even when + * the receiver cannot be traced back to a known constructor (e.g. due to `or` expressions). + */ +module ChatCompletionsCreate { + /** + * Gets a `DataFlow::Node` that is the `content` value inside a message dict + * passed to a `*.chat.completions.create(messages=[{..., "content": }])` call, + * matched purely by attribute names in the call chain. + */ + DataFlow::Node getAMessageContentSink() { + exists( + DataFlow::MethodCallNode createCall, DataFlow::AttrRead completionsAttr, + DataFlow::AttrRead chatAttr + | + // Match *.chat.completions.create(...) + createCall.getMethodName() = "create" and + completionsAttr = createCall.getObject().getALocalSource() and + completionsAttr.getAttributeName() = "completions" and + chatAttr = completionsAttr.getObject().getALocalSource() and + chatAttr.getAttributeName() = "chat" + | + // The messages keyword argument value (the list itself, or individual dict content values) + result = createCall.getArgByName("messages") + ) + } +} diff --git a/python/ql/src/experimental/semmle/python/security/dataflow/PromptInjectionCustomizations.qll b/python/ql/src/experimental/semmle/python/security/dataflow/PromptInjectionCustomizations.qll index 181be6393956..fd2cfe4478fa 100644 --- a/python/ql/src/experimental/semmle/python/security/dataflow/PromptInjectionCustomizations.qll +++ b/python/ql/src/experimental/semmle/python/security/dataflow/PromptInjectionCustomizations.qll @@ -8,6 +8,7 @@ import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts private import experimental.semmle.python.Concepts +private import semmle.python.Frameworks private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.dataflow.new.BarrierGuards private import semmle.python.frameworks.data.ModelsAsData @@ -55,6 +56,8 @@ module PromptInjection { this = OpenAI::getContentNode().asSink() or this = AgentSDK::getContentNode().asSink() + or + this = ChatCompletionsCreate::getAMessageContentSink() } } From e069c9c2eec3555dd908ffe55de4e7f9bc872ad4 Mon Sep 17 00:00:00 2001 From: Josef Svenningsson Date: Thu, 2 Apr 2026 19:17:48 +0100 Subject: [PATCH 2/5] Fix tests --- .../PromptInjection.expected | 50 ++++++++++++++----- .../CWE-1427-PromptInjection/openai_test.py | 6 +-- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/PromptInjection.expected b/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/PromptInjection.expected index 1a899e7c82fe..b65cc3ceb0d5 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/PromptInjection.expected +++ b/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/PromptInjection.expected @@ -5,14 +5,17 @@ | 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 | | 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 | | 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 | +| 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 | | 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 | | 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 | | 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 | | 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 | | 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 | +| openai_test.py:60:18:73:9 | ControlFlowNode for List | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:60:18:73:9 | ControlFlowNode for List | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | | 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 | | 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 | | 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 | +| openai_test.py:77:18:86:9 | ControlFlowNode for List | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:77:18:86:9 | ControlFlowNode for List | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | | 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 | | 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 | | 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 | @@ -20,7 +23,7 @@ edges | agent_instructions.py:2:26:2:32 | ControlFlowNode for ImportMember | agent_instructions.py:2:26:2:32 | ControlFlowNode for request | provenance | | | agent_instructions.py:2:26:2:32 | ControlFlowNode for request | agent_instructions.py:7:13:7:19 | ControlFlowNode for request | provenance | | | agent_instructions.py:2:26:2:32 | ControlFlowNode for request | agent_instructions.py:17:13:17:19 | ControlFlowNode for request | provenance | | -| agent_instructions.py:7:5:7:9 | ControlFlowNode for input | agent_instructions.py:9:50:9:89 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:2 | +| agent_instructions.py:7:5:7:9 | ControlFlowNode for input | agent_instructions.py:9:50:9:89 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:5 | | agent_instructions.py:7:13:7:19 | ControlFlowNode for request | agent_instructions.py:7:13:7:24 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | agent_instructions.py:7:13:7:24 | ControlFlowNode for Attribute | agent_instructions.py:7:13:7:37 | ControlFlowNode for Attribute() | provenance | dict.get | | agent_instructions.py:7:13:7:37 | ControlFlowNode for Attribute() | agent_instructions.py:7:5:7:9 | ControlFlowNode for input | provenance | | @@ -32,30 +35,46 @@ edges | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:2:26:2:32 | ControlFlowNode for request | provenance | | | openai_test.py:2:26:2:32 | ControlFlowNode for request | openai_test.py:12:15:12:21 | ControlFlowNode for request | provenance | | | openai_test.py:2:26:2:32 | ControlFlowNode for request | openai_test.py:13:13:13:19 | ControlFlowNode for request | provenance | | -| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:17:22:17:46 | ControlFlowNode for BinaryExpr | provenance | | -| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:22:22:22:46 | ControlFlowNode for BinaryExpr | provenance | | +| openai_test.py:2:26:2:32 | ControlFlowNode for request | openai_test.py:14:12:14:18 | ControlFlowNode for request | provenance | | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:17:22:17:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:4 | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:22:22:22:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:4 | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:23:15:37:9 | ControlFlowNode for List | provenance | Sink:MaD:3 | | openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:26:28:26:51 | ControlFlowNode for BinaryExpr | provenance | | -| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:41:22:41:46 | ControlFlowNode for BinaryExpr | provenance | | -| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:63:28:63:51 | ControlFlowNode for BinaryExpr | provenance | | -| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:80:28:80:51 | ControlFlowNode for BinaryExpr | provenance | | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:41:22:41:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:4 | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:60:18:73:9 | ControlFlowNode for List | provenance | | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:63:28:63:51 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:2 | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:77:18:86:9 | ControlFlowNode for List | provenance | | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:80:28:80:51 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:2 | | openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:92:22:92:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:1 | | openai_test.py:12:15:12:21 | ControlFlowNode for request | openai_test.py:12:15:12:26 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | openai_test.py:12:15:12:21 | ControlFlowNode for request | openai_test.py:13:13:13:24 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| openai_test.py:12:15:12:21 | ControlFlowNode for request | openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | openai_test.py:12:15:12:26 | ControlFlowNode for Attribute | openai_test.py:12:15:12:41 | ControlFlowNode for Attribute() | provenance | dict.get | | openai_test.py:12:15:12:41 | ControlFlowNode for Attribute() | openai_test.py:12:5:12:11 | ControlFlowNode for persona | provenance | | -| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:18:15:18:19 | ControlFlowNode for query | provenance | | +| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:18:15:18:19 | ControlFlowNode for query | provenance | Sink:MaD:3 | +| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:23:15:37:9 | ControlFlowNode for List | provenance | Sink:MaD:3 | | openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:33:33:33:37 | ControlFlowNode for query | provenance | | -| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:42:15:42:19 | ControlFlowNode for query | provenance | | +| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:42:15:42:19 | ControlFlowNode for query | provenance | Sink:MaD:3 | | openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:53:33:53:37 | ControlFlowNode for query | provenance | | -| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:67:28:67:32 | ControlFlowNode for query | provenance | | -| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:71:28:71:32 | ControlFlowNode for query | provenance | | -| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:84:28:84:32 | ControlFlowNode for query | provenance | | +| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:60:18:73:9 | ControlFlowNode for List | provenance | | +| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:67:28:67:32 | ControlFlowNode for query | provenance | Sink:MaD:2 | +| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:71:28:71:32 | ControlFlowNode for query | provenance | Sink:MaD:2 | +| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:77:18:86:9 | ControlFlowNode for List | provenance | | +| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:84:28:84:32 | ControlFlowNode for query | provenance | Sink:MaD:2 | | openai_test.py:13:13:13:19 | ControlFlowNode for request | openai_test.py:13:13:13:24 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| openai_test.py:13:13:13:19 | ControlFlowNode for request | openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | openai_test.py:13:13:13:24 | ControlFlowNode for Attribute | openai_test.py:13:13:13:37 | ControlFlowNode for Attribute() | provenance | dict.get | | openai_test.py:13:13:13:37 | ControlFlowNode for Attribute() | openai_test.py:13:5:13:9 | ControlFlowNode for query | provenance | | +| openai_test.py:14:5:14:8 | ControlFlowNode for role | openai_test.py:60:18:73:9 | ControlFlowNode for List | provenance | | +| openai_test.py:14:12:14:18 | ControlFlowNode for request | openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | openai_test.py:14:12:14:35 | ControlFlowNode for Attribute() | provenance | dict.get | +| openai_test.py:14:12:14:35 | ControlFlowNode for Attribute() | openai_test.py:14:5:14:8 | ControlFlowNode for role | provenance | | models | 1 | Sink: OpenAI; Member[beta].Member[assistants].Member[create].Argument[instructions:]; prompt-injection | -| 2 | Sink: agents; Member[Agent].Argument[instructions:]; prompt-injection | +| 2 | Sink: OpenAI; Member[chat].Member[completions].Member[create].Argument[messages:].ListElement.DictionaryElement[content]; prompt-injection | +| 3 | Sink: OpenAI; Member[responses].Member[create].Argument[input:]; prompt-injection | +| 4 | Sink: OpenAI; Member[responses].Member[create].Argument[instructions:]; prompt-injection | +| 5 | Sink: agents; Member[Agent].Argument[instructions:]; prompt-injection | nodes | agent_instructions.py:2:26:2:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | | agent_instructions.py:2:26:2:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | @@ -80,17 +99,24 @@ nodes | openai_test.py:13:13:13:19 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | | openai_test.py:13:13:13:24 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | | openai_test.py:13:13:13:37 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| openai_test.py:14:5:14:8 | ControlFlowNode for role | semmle.label | ControlFlowNode for role | +| openai_test.py:14:12:14:18 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| openai_test.py:14:12:14:35 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | openai_test.py:17:22:17:46 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | openai_test.py:18:15:18:19 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | | openai_test.py:22:22:22:46 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | +| openai_test.py:23:15:37:9 | ControlFlowNode for List | semmle.label | ControlFlowNode for List | | openai_test.py:26:28:26:51 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | openai_test.py:33:33:33:37 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | | openai_test.py:41:22:41:46 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | openai_test.py:42:15:42:19 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | | openai_test.py:53:33:53:37 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | +| openai_test.py:60:18:73:9 | ControlFlowNode for List | semmle.label | ControlFlowNode for List | | openai_test.py:63:28:63:51 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | openai_test.py:67:28:67:32 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | | openai_test.py:71:28:71:32 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | +| openai_test.py:77:18:86:9 | ControlFlowNode for List | semmle.label | ControlFlowNode for List | | openai_test.py:80:28:80:51 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | openai_test.py:84:28:84:32 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | | openai_test.py:92:22:92:46 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | diff --git a/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/openai_test.py b/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/openai_test.py index 2b25609670c5..f074b203a135 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/openai_test.py +++ b/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/openai_test.py @@ -20,7 +20,7 @@ async def get_input_openai(): response2 = client.responses.create( instructions="Talks like a " + persona, # $ Alert[py/prompt-injection] - input=[ + input=[ # $ Alert[py/prompt-injection] { "role": "developer", "content": "Talk like a " + persona # $ Alert[py/prompt-injection] @@ -57,7 +57,7 @@ async def get_input_openai(): ) completion1 = client.chat.completions.create( - messages=[ + messages=[ # $ Alert[py/prompt-injection] { "role": "developer", "content": "Talk like a " + persona # $ Alert[py/prompt-injection] @@ -74,7 +74,7 @@ async def get_input_openai(): ) completion2 = azure_client.chat.completions.create( - messages=[ + messages=[ # $ Alert[py/prompt-injection] { "role": "developer", "content": "Talk like a " + persona # $ Alert[py/prompt-injection] From a05e19151811efa40ba60605bc8744c08a8e002f Mon Sep 17 00:00:00 2001 From: Josef Svenningsson Date: Tue, 28 Apr 2026 15:51:45 +0100 Subject: [PATCH 3/5] Add tests for anthropic prompt injection models --- .../anthropic_test.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/anthropic_test.py diff --git a/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/anthropic_test.py b/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/anthropic_test.py new file mode 100644 index 000000000000..f9e37e31b3c5 --- /dev/null +++ b/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/anthropic_test.py @@ -0,0 +1,63 @@ +from anthropic import Anthropic, AsyncAnthropic +from flask import Flask, request # $ Source + +app = Flask(__name__) +client = Anthropic() +async_client = AsyncAnthropic() + + +@app.route("/anthropic") +async def get_input_anthropic(): + persona = request.args.get("persona") + query = request.args.get("query") + + response1 = client.messages.create( + model="claude-sonnet-4-20250514", + max_tokens=256, + system="Talk like " + persona, # $ Alert[py/prompt-injection] + messages=[ + { + "role": "user", + "content": query, # $ Alert[py/prompt-injection] + } + ], + ) + + response2 = client.messages.stream( + model="claude-sonnet-4-20250514", + max_tokens=256, + system="Talk like " + persona, # $ Alert[py/prompt-injection] + messages=[ + { + "role": "user", + "content": query, # $ Alert[py/prompt-injection] + } + ], + ) + + response3 = await async_client.messages.create( + model="claude-sonnet-4-20250514", + max_tokens=256, + system="Talk like " + persona, # $ Alert[py/prompt-injection] + messages=[ + { + "role": "user", + "content": query, # $ Alert[py/prompt-injection] + } + ], + ) + + response4 = client.beta.messages.create( + model="claude-sonnet-4-20250514", + max_tokens=256, + system="Talk like " + persona, # $ Alert[py/prompt-injection] + messages=[ + { + "role": "user", + "content": query, # $ Alert[py/prompt-injection] + } + ], + betas=["prompt-caching-2024-07-31"], + ) + + print(response1, response2, response3, response4) \ No newline at end of file From 691aeb08150ff3a83e5642762581e069fb258993 Mon Sep 17 00:00:00 2001 From: Josef Svenningsson Date: Tue, 28 Apr 2026 18:13:45 +0100 Subject: [PATCH 4/5] Remove the chat completion create logic. --- .../semmle/python/frameworks/OpenAI.qll | 30 ------------------- .../PromptInjectionCustomizations.qll | 3 -- 2 files changed, 33 deletions(-) diff --git a/python/ql/src/experimental/semmle/python/frameworks/OpenAI.qll b/python/ql/src/experimental/semmle/python/frameworks/OpenAI.qll index e5649716c8af..74614a739aa4 100644 --- a/python/ql/src/experimental/semmle/python/frameworks/OpenAI.qll +++ b/python/ql/src/experimental/semmle/python/frameworks/OpenAI.qll @@ -7,7 +7,6 @@ private import python private import semmle.python.ApiGraphs -private import semmle.python.dataflow.new.DataFlow /** * Provides models for agents SDK (instances of the `agents.Runner` class etc). @@ -87,32 +86,3 @@ module OpenAI { ) } } - -/** - * Provides attribute-name-based sink detection for `chat.completions.create` calls. - * This does not rely on API graph type resolution and thus works even when - * the receiver cannot be traced back to a known constructor (e.g. due to `or` expressions). - */ -module ChatCompletionsCreate { - /** - * Gets a `DataFlow::Node` that is the `content` value inside a message dict - * passed to a `*.chat.completions.create(messages=[{..., "content": }])` call, - * matched purely by attribute names in the call chain. - */ - DataFlow::Node getAMessageContentSink() { - exists( - DataFlow::MethodCallNode createCall, DataFlow::AttrRead completionsAttr, - DataFlow::AttrRead chatAttr - | - // Match *.chat.completions.create(...) - createCall.getMethodName() = "create" and - completionsAttr = createCall.getObject().getALocalSource() and - completionsAttr.getAttributeName() = "completions" and - chatAttr = completionsAttr.getObject().getALocalSource() and - chatAttr.getAttributeName() = "chat" - | - // The messages keyword argument value (the list itself, or individual dict content values) - result = createCall.getArgByName("messages") - ) - } -} diff --git a/python/ql/src/experimental/semmle/python/security/dataflow/PromptInjectionCustomizations.qll b/python/ql/src/experimental/semmle/python/security/dataflow/PromptInjectionCustomizations.qll index fd2cfe4478fa..181be6393956 100644 --- a/python/ql/src/experimental/semmle/python/security/dataflow/PromptInjectionCustomizations.qll +++ b/python/ql/src/experimental/semmle/python/security/dataflow/PromptInjectionCustomizations.qll @@ -8,7 +8,6 @@ import python private import semmle.python.dataflow.new.DataFlow private import semmle.python.Concepts private import experimental.semmle.python.Concepts -private import semmle.python.Frameworks private import semmle.python.dataflow.new.RemoteFlowSources private import semmle.python.dataflow.new.BarrierGuards private import semmle.python.frameworks.data.ModelsAsData @@ -56,8 +55,6 @@ module PromptInjection { this = OpenAI::getContentNode().asSink() or this = AgentSDK::getContentNode().asSink() - or - this = ChatCompletionsCreate::getAMessageContentSink() } } From 25a8aa97b24865d768768d3c2d3c72f4d668aaaa Mon Sep 17 00:00:00 2001 From: Josef Svenningsson Date: Tue, 28 Apr 2026 18:23:42 +0100 Subject: [PATCH 5/5] Fix openai prompt injection tests --- .../PromptInjection.expected | 107 +++++++++++------- .../CWE-1427-PromptInjection/openai_test.py | 8 +- 2 files changed, 73 insertions(+), 42 deletions(-) diff --git a/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/PromptInjection.expected b/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/PromptInjection.expected index b65cc3ceb0d5..6acb03ce7f51 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/PromptInjection.expected +++ b/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/PromptInjection.expected @@ -2,6 +2,14 @@ | agent_instructions.py:9:50:9:89 | ControlFlowNode for BinaryExpr | agent_instructions.py:2:26:2:32 | ControlFlowNode for ImportMember | agent_instructions.py:9:50:9:89 | ControlFlowNode for BinaryExpr | This prompt construction depends on a $@. | agent_instructions.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | | agent_instructions.py:25:28:25:32 | ControlFlowNode for input | agent_instructions.py:2:26:2:32 | ControlFlowNode for ImportMember | agent_instructions.py:25:28:25:32 | ControlFlowNode for input | This prompt construction depends on a $@. | agent_instructions.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | | agent_instructions.py:35:28:35:32 | ControlFlowNode for input | agent_instructions.py:2:26:2:32 | ControlFlowNode for ImportMember | agent_instructions.py:35:28:35:32 | ControlFlowNode for input | This prompt construction depends on a $@. | agent_instructions.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | +| anthropic_test.py:17:16:17:37 | ControlFlowNode for BinaryExpr | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | anthropic_test.py:17:16:17:37 | ControlFlowNode for BinaryExpr | This prompt construction depends on a $@. | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | +| anthropic_test.py:21:28:21:32 | ControlFlowNode for query | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | anthropic_test.py:21:28:21:32 | ControlFlowNode for query | This prompt construction depends on a $@. | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | +| anthropic_test.py:29:16:29:37 | ControlFlowNode for BinaryExpr | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | anthropic_test.py:29:16:29:37 | ControlFlowNode for BinaryExpr | This prompt construction depends on a $@. | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | +| anthropic_test.py:33:28:33:32 | ControlFlowNode for query | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | anthropic_test.py:33:28:33:32 | ControlFlowNode for query | This prompt construction depends on a $@. | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | +| anthropic_test.py:41:16:41:37 | ControlFlowNode for BinaryExpr | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | anthropic_test.py:41:16:41:37 | ControlFlowNode for BinaryExpr | This prompt construction depends on a $@. | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | +| anthropic_test.py:45:28:45:32 | ControlFlowNode for query | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | anthropic_test.py:45:28:45:32 | ControlFlowNode for query | This prompt construction depends on a $@. | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | +| anthropic_test.py:53:16:53:37 | ControlFlowNode for BinaryExpr | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | anthropic_test.py:53:16:53:37 | ControlFlowNode for BinaryExpr | This prompt construction depends on a $@. | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | +| anthropic_test.py:57:28:57:32 | ControlFlowNode for query | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | anthropic_test.py:57:28:57:32 | ControlFlowNode for query | This prompt construction depends on a $@. | anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | | 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 | | 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 | | 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 | @@ -11,11 +19,9 @@ | 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 | | 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 | | 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 | -| openai_test.py:60:18:73:9 | ControlFlowNode for List | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:60:18:73:9 | ControlFlowNode for List | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | | 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 | | 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 | | 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 | -| openai_test.py:77:18:86:9 | ControlFlowNode for List | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:77:18:86:9 | ControlFlowNode for List | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | | 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 | | 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 | | 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 | @@ -23,7 +29,7 @@ edges | agent_instructions.py:2:26:2:32 | ControlFlowNode for ImportMember | agent_instructions.py:2:26:2:32 | ControlFlowNode for request | provenance | | | agent_instructions.py:2:26:2:32 | ControlFlowNode for request | agent_instructions.py:7:13:7:19 | ControlFlowNode for request | provenance | | | agent_instructions.py:2:26:2:32 | ControlFlowNode for request | agent_instructions.py:17:13:17:19 | ControlFlowNode for request | provenance | | -| agent_instructions.py:7:5:7:9 | ControlFlowNode for input | agent_instructions.py:9:50:9:89 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:5 | +| agent_instructions.py:7:5:7:9 | ControlFlowNode for input | agent_instructions.py:9:50:9:89 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:11 | | agent_instructions.py:7:13:7:19 | ControlFlowNode for request | agent_instructions.py:7:13:7:24 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | agent_instructions.py:7:13:7:24 | ControlFlowNode for Attribute | agent_instructions.py:7:13:7:37 | ControlFlowNode for Attribute() | provenance | dict.get | | agent_instructions.py:7:13:7:37 | ControlFlowNode for Attribute() | agent_instructions.py:7:5:7:9 | ControlFlowNode for input | provenance | | @@ -32,49 +38,62 @@ edges | agent_instructions.py:17:13:17:19 | ControlFlowNode for request | agent_instructions.py:17:13:17:24 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | agent_instructions.py:17:13:17:24 | ControlFlowNode for Attribute | agent_instructions.py:17:13:17:37 | ControlFlowNode for Attribute() | provenance | dict.get | | agent_instructions.py:17:13:17:37 | ControlFlowNode for Attribute() | agent_instructions.py:17:5:17:9 | ControlFlowNode for input | provenance | | +| anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | anthropic_test.py:2:26:2:32 | ControlFlowNode for request | provenance | | +| anthropic_test.py:2:26:2:32 | ControlFlowNode for request | anthropic_test.py:11:15:11:21 | ControlFlowNode for request | provenance | | +| anthropic_test.py:2:26:2:32 | ControlFlowNode for request | anthropic_test.py:12:13:12:19 | ControlFlowNode for request | provenance | | +| anthropic_test.py:11:5:11:11 | ControlFlowNode for persona | anthropic_test.py:17:16:17:37 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:4 | +| anthropic_test.py:11:5:11:11 | ControlFlowNode for persona | anthropic_test.py:29:16:29:37 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:6 | +| anthropic_test.py:11:5:11:11 | ControlFlowNode for persona | anthropic_test.py:41:16:41:37 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:4 | +| anthropic_test.py:11:5:11:11 | ControlFlowNode for persona | anthropic_test.py:53:16:53:37 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:2 | +| anthropic_test.py:11:15:11:21 | ControlFlowNode for request | anthropic_test.py:11:15:11:26 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| anthropic_test.py:11:15:11:21 | ControlFlowNode for request | anthropic_test.py:12:13:12:24 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| anthropic_test.py:11:15:11:26 | ControlFlowNode for Attribute | anthropic_test.py:11:15:11:41 | ControlFlowNode for Attribute() | provenance | dict.get | +| anthropic_test.py:11:15:11:41 | ControlFlowNode for Attribute() | anthropic_test.py:11:5:11:11 | ControlFlowNode for persona | provenance | | +| anthropic_test.py:12:5:12:9 | ControlFlowNode for query | anthropic_test.py:21:28:21:32 | ControlFlowNode for query | provenance | Sink:MaD:3 | +| anthropic_test.py:12:5:12:9 | ControlFlowNode for query | anthropic_test.py:33:28:33:32 | ControlFlowNode for query | provenance | Sink:MaD:5 | +| anthropic_test.py:12:5:12:9 | ControlFlowNode for query | anthropic_test.py:45:28:45:32 | ControlFlowNode for query | provenance | Sink:MaD:3 | +| anthropic_test.py:12:5:12:9 | ControlFlowNode for query | anthropic_test.py:57:28:57:32 | ControlFlowNode for query | provenance | Sink:MaD:1 | +| anthropic_test.py:12:13:12:19 | ControlFlowNode for request | anthropic_test.py:12:13:12:24 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| anthropic_test.py:12:13:12:24 | ControlFlowNode for Attribute | anthropic_test.py:12:13:12:37 | ControlFlowNode for Attribute() | provenance | dict.get | +| anthropic_test.py:12:13:12:37 | ControlFlowNode for Attribute() | anthropic_test.py:12:5:12:9 | ControlFlowNode for query | provenance | | | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:2:26:2:32 | ControlFlowNode for request | provenance | | | openai_test.py:2:26:2:32 | ControlFlowNode for request | openai_test.py:12:15:12:21 | ControlFlowNode for request | provenance | | | openai_test.py:2:26:2:32 | ControlFlowNode for request | openai_test.py:13:13:13:19 | ControlFlowNode for request | provenance | | -| openai_test.py:2:26:2:32 | ControlFlowNode for request | openai_test.py:14:12:14:18 | ControlFlowNode for request | provenance | | -| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:17:22:17:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:4 | -| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:22:22:22:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:4 | -| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:23:15:37:9 | ControlFlowNode for List | provenance | Sink:MaD:3 | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:17:22:17:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:10 | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:22:22:22:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:10 | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:23:15:37:9 | ControlFlowNode for List | provenance | Sink:MaD:9 | | openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:26:28:26:51 | ControlFlowNode for BinaryExpr | provenance | | -| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:41:22:41:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:4 | -| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:60:18:73:9 | ControlFlowNode for List | provenance | | -| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:63:28:63:51 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:2 | -| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:77:18:86:9 | ControlFlowNode for List | provenance | | -| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:80:28:80:51 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:2 | -| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:92:22:92:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:1 | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:41:22:41:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:10 | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:63:28:63:51 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:8 | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:80:28:80:51 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:8 | +| openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:92:22:92:46 | ControlFlowNode for BinaryExpr | provenance | Sink:MaD:7 | | openai_test.py:12:15:12:21 | ControlFlowNode for request | openai_test.py:12:15:12:26 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | openai_test.py:12:15:12:21 | ControlFlowNode for request | openai_test.py:13:13:13:24 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | -| openai_test.py:12:15:12:21 | ControlFlowNode for request | openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | openai_test.py:12:15:12:26 | ControlFlowNode for Attribute | openai_test.py:12:15:12:41 | ControlFlowNode for Attribute() | provenance | dict.get | | openai_test.py:12:15:12:41 | ControlFlowNode for Attribute() | openai_test.py:12:5:12:11 | ControlFlowNode for persona | provenance | | -| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:18:15:18:19 | ControlFlowNode for query | provenance | Sink:MaD:3 | -| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:23:15:37:9 | ControlFlowNode for List | provenance | Sink:MaD:3 | +| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:18:15:18:19 | ControlFlowNode for query | provenance | Sink:MaD:9 | +| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:23:15:37:9 | ControlFlowNode for List | provenance | Sink:MaD:9 | | openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:33:33:33:37 | ControlFlowNode for query | provenance | | -| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:42:15:42:19 | ControlFlowNode for query | provenance | Sink:MaD:3 | +| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:42:15:42:19 | ControlFlowNode for query | provenance | Sink:MaD:9 | | openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:53:33:53:37 | ControlFlowNode for query | provenance | | -| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:60:18:73:9 | ControlFlowNode for List | provenance | | -| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:67:28:67:32 | ControlFlowNode for query | provenance | Sink:MaD:2 | -| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:71:28:71:32 | ControlFlowNode for query | provenance | Sink:MaD:2 | -| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:77:18:86:9 | ControlFlowNode for List | provenance | | -| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:84:28:84:32 | ControlFlowNode for query | provenance | Sink:MaD:2 | +| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:67:28:67:32 | ControlFlowNode for query | provenance | Sink:MaD:8 | +| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:71:28:71:32 | ControlFlowNode for query | provenance | Sink:MaD:8 | +| openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:84:28:84:32 | ControlFlowNode for query | provenance | Sink:MaD:8 | | openai_test.py:13:13:13:19 | ControlFlowNode for request | openai_test.py:13:13:13:24 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | -| openai_test.py:13:13:13:19 | ControlFlowNode for request | openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | openai_test.py:13:13:13:24 | ControlFlowNode for Attribute | openai_test.py:13:13:13:37 | ControlFlowNode for Attribute() | provenance | dict.get | | openai_test.py:13:13:13:37 | ControlFlowNode for Attribute() | openai_test.py:13:5:13:9 | ControlFlowNode for query | provenance | | -| openai_test.py:14:5:14:8 | ControlFlowNode for role | openai_test.py:60:18:73:9 | ControlFlowNode for List | provenance | | -| openai_test.py:14:12:14:18 | ControlFlowNode for request | openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | -| openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | openai_test.py:14:12:14:35 | ControlFlowNode for Attribute() | provenance | dict.get | -| openai_test.py:14:12:14:35 | ControlFlowNode for Attribute() | openai_test.py:14:5:14:8 | ControlFlowNode for role | provenance | | models -| 1 | Sink: OpenAI; Member[beta].Member[assistants].Member[create].Argument[instructions:]; prompt-injection | -| 2 | Sink: OpenAI; Member[chat].Member[completions].Member[create].Argument[messages:].ListElement.DictionaryElement[content]; prompt-injection | -| 3 | Sink: OpenAI; Member[responses].Member[create].Argument[input:]; prompt-injection | -| 4 | Sink: OpenAI; Member[responses].Member[create].Argument[instructions:]; prompt-injection | -| 5 | Sink: agents; Member[Agent].Argument[instructions:]; prompt-injection | +| 1 | Sink: Anthropic; Member[beta].Member[messages].Member[create].Argument[messages:].ListElement.DictionaryElement[content]; prompt-injection | +| 2 | Sink: Anthropic; Member[beta].Member[messages].Member[create].Argument[system:]; prompt-injection | +| 3 | Sink: Anthropic; Member[messages].Member[create].Argument[messages:].ListElement.DictionaryElement[content]; prompt-injection | +| 4 | Sink: Anthropic; Member[messages].Member[create].Argument[system:]; prompt-injection | +| 5 | Sink: Anthropic; Member[messages].Member[stream].Argument[messages:].ListElement.DictionaryElement[content]; prompt-injection | +| 6 | Sink: Anthropic; Member[messages].Member[stream].Argument[system:]; prompt-injection | +| 7 | Sink: OpenAI; Member[beta].Member[assistants].Member[create].Argument[instructions:]; prompt-injection | +| 8 | Sink: OpenAI; Member[chat].Member[completions].Member[create].Argument[messages:].ListElement.DictionaryElement[content]; prompt-injection | +| 9 | Sink: OpenAI; Member[responses].Member[create].Argument[input:]; prompt-injection | +| 10 | Sink: OpenAI; Member[responses].Member[create].Argument[instructions:]; prompt-injection | +| 11 | Sink: agents; Member[Agent].Argument[instructions:]; prompt-injection | nodes | agent_instructions.py:2:26:2:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | | agent_instructions.py:2:26:2:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | @@ -89,6 +108,24 @@ nodes | agent_instructions.py:17:13:17:37 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | agent_instructions.py:25:28:25:32 | ControlFlowNode for input | semmle.label | ControlFlowNode for input | | agent_instructions.py:35:28:35:32 | ControlFlowNode for input | semmle.label | ControlFlowNode for input | +| anthropic_test.py:2:26:2:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | +| anthropic_test.py:2:26:2:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| anthropic_test.py:11:5:11:11 | ControlFlowNode for persona | semmle.label | ControlFlowNode for persona | +| anthropic_test.py:11:15:11:21 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| anthropic_test.py:11:15:11:26 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| anthropic_test.py:11:15:11:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| anthropic_test.py:12:5:12:9 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | +| anthropic_test.py:12:13:12:19 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| anthropic_test.py:12:13:12:24 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| anthropic_test.py:12:13:12:37 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| anthropic_test.py:17:16:17:37 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | +| anthropic_test.py:21:28:21:32 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | +| anthropic_test.py:29:16:29:37 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | +| anthropic_test.py:33:28:33:32 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | +| anthropic_test.py:41:16:41:37 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | +| anthropic_test.py:45:28:45:32 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | +| anthropic_test.py:53:16:53:37 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | +| anthropic_test.py:57:28:57:32 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | | openai_test.py:2:26:2:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | | openai_test.py:12:5:12:11 | ControlFlowNode for persona | semmle.label | ControlFlowNode for persona | @@ -99,10 +136,6 @@ nodes | openai_test.py:13:13:13:19 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | | openai_test.py:13:13:13:24 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | | openai_test.py:13:13:13:37 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | -| openai_test.py:14:5:14:8 | ControlFlowNode for role | semmle.label | ControlFlowNode for role | -| openai_test.py:14:12:14:18 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | -| openai_test.py:14:12:14:23 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | -| openai_test.py:14:12:14:35 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | openai_test.py:17:22:17:46 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | openai_test.py:18:15:18:19 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | | openai_test.py:22:22:22:46 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | @@ -112,11 +145,9 @@ nodes | openai_test.py:41:22:41:46 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | openai_test.py:42:15:42:19 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | | openai_test.py:53:33:53:37 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | -| openai_test.py:60:18:73:9 | ControlFlowNode for List | semmle.label | ControlFlowNode for List | | openai_test.py:63:28:63:51 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | openai_test.py:67:28:67:32 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | | openai_test.py:71:28:71:32 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | -| openai_test.py:77:18:86:9 | ControlFlowNode for List | semmle.label | ControlFlowNode for List | | openai_test.py:80:28:80:51 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | openai_test.py:84:28:84:32 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | | openai_test.py:92:22:92:46 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | diff --git a/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/openai_test.py b/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/openai_test.py index f074b203a135..8ea014c62b49 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/openai_test.py +++ b/python/ql/test/experimental/query-tests/Security/CWE-1427-PromptInjection/openai_test.py @@ -20,7 +20,7 @@ async def get_input_openai(): response2 = client.responses.create( instructions="Talks like a " + persona, # $ Alert[py/prompt-injection] - input=[ # $ Alert[py/prompt-injection] + input=[ { "role": "developer", "content": "Talk like a " + persona # $ Alert[py/prompt-injection] @@ -34,7 +34,7 @@ async def get_input_openai(): } ] } - ] + ] # $ Alert[py/prompt-injection] ) response3 = await async_client.responses.create( @@ -57,7 +57,7 @@ async def get_input_openai(): ) completion1 = client.chat.completions.create( - messages=[ # $ Alert[py/prompt-injection] + messages=[ { "role": "developer", "content": "Talk like a " + persona # $ Alert[py/prompt-injection] @@ -74,7 +74,7 @@ async def get_input_openai(): ) completion2 = azure_client.chat.completions.create( - messages=[ # $ Alert[py/prompt-injection] + messages=[ { "role": "developer", "content": "Talk like a " + persona # $ Alert[py/prompt-injection]