-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Models openai for Prompt injection
#21086
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
005db5b
7a9e03d
b30444b
6c5c87e
616698c
942834d
df979da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| category: minorAnalysis | ||
| --- | ||
| * Added propmpt injection query | ||
| * Added taint flow model and type model for `agents` and `openai` modules. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| /** | ||
| * Provides classes modeling security-relevant aspects of the `openAI`Agents SDK package. | ||
| * See https://github.com/openai/openai-agents-python. | ||
| */ | ||
|
|
||
| private import python | ||
| private import semmle.python.ApiGraphs | ||
|
|
||
| /** | ||
| * Provides models for Agent (instances of the `agents.Agent` class). | ||
| * | ||
| * See https://github.com/openai/openai-agents-python. | ||
| */ | ||
| module Agent { | ||
| /** Gets a reference to the `agents.Agent` class. */ | ||
| API::Node classRef() { result = API::moduleImport("agents").getMember("Agent") } | ||
|
|
||
| /** Gets a reference to a potential property of `agents.Agent` called instructions which refers to the system prompt. */ | ||
| API::Node sink() { result = classRef().getACall().getKeywordParameter("instructions") } | ||
| } | ||
|
|
||
| /** | ||
| * Provides models for OpenAI (instances of `openai` classes). | ||
| * | ||
| * See https://github.com/openai/openai-python. | ||
| */ | ||
| module OpenAI { | ||
| API::Node sink() { | ||
| result = | ||
| classRef() | ||
| .getReturn() | ||
| .getMember("responses") | ||
| .getMember("create") | ||
| .getKeywordParameter(["input", "instructions"]) or | ||
| result = | ||
| classRef() | ||
| .getReturn() | ||
| .getMember("realtime") | ||
| .getMember("connect") | ||
| .getReturn() | ||
| .getMember("conversation") | ||
| .getMember("item") | ||
| .getMember("create") | ||
| .getKeywordParameter("item") or | ||
| result = | ||
| classRef() | ||
| .getReturn() | ||
| .getMember("chat") | ||
| .getMember("completions") | ||
| .getMember("create") | ||
| .getKeywordParameter("messages") | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| /** | ||
| * Provides default sources, sinks and sanitizers for detecting | ||
| * "prompt injection" | ||
| * vulnerabilities, as well as extension points for adding your own. | ||
| */ | ||
|
|
||
| import python | ||
| private import semmle.python.dataflow.new.DataFlow | ||
| private import semmle.python.Concepts | ||
| private import semmle.python.dataflow.new.RemoteFlowSources | ||
| private import semmle.python.dataflow.new.BarrierGuards | ||
| private import semmle.python.frameworks.OpenAI | ||
|
|
||
| module PromptInjection { | ||
| /** | ||
| * A data flow source for "prompt injection" vulnerabilities. | ||
| */ | ||
| abstract class Source extends DataFlow::Node { } | ||
|
|
||
| /** | ||
| * A data flow sink for "prompt injection" vulnerabilities. | ||
| */ | ||
| abstract class Sink extends DataFlow::Node { } | ||
|
|
||
| /** | ||
| * A sanitizer for "prompt injection" vulnerabilities. | ||
| */ | ||
| abstract class Sanitizer extends DataFlow::Node { } | ||
|
|
||
| /** | ||
| * An active threat-model source, considered as a flow source. | ||
| */ | ||
| private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { } | ||
|
|
||
| /** | ||
| * Agent prompt sinks, considered as a flow sink. | ||
| */ | ||
|
Comment on lines
+35
to
+37
Check warningCode scanning / CodeQL Class QLDoc style Warning
The QLDoc for a class should start with 'A', 'An', or 'The'.
|
||
| class SystemPromptSink extends Sink { | ||
| SystemPromptSink() { this = Agent::sink().asSink() or this = OpenAI::sink().asSink() } | ||
| } | ||
|
|
||
| private import semmle.python.frameworks.data.ModelsAsData | ||
|
|
||
| private class DataAsPromptSink extends Sink { | ||
| DataAsPromptSink() { this = ModelOutput::getASinkNode("prompt-injection").asSink() } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,28 @@ | ||||
| /** | ||||
| * Provides taint-tracking configurations for detecting "prompt injection" vulnerabilities. | ||||
| * | ||||
| * Note, for performance reasons: only import this file if | ||||
| * `PromptInjection::Configuration` is needed, otherwise | ||||
| * `PromptInjectionCustomizations` should be imported instead. | ||||
| */ | ||||
|
|
||||
| private import python | ||||
| import semmle.python.dataflow.new.DataFlow | ||||
| import semmle.python.dataflow.new.TaintTracking | ||||
| import PromptInjectionCustomizations::PromptInjection | ||||
|
|
||||
| private module PromptInjectionConfig implements DataFlow::ConfigSig { | ||||
| predicate isSource(DataFlow::Node node) { node instanceof Source } | ||||
|
|
||||
| predicate isSink(DataFlow::Node node) { | ||||
| node instanceof Sink | ||||
| //any() | ||||
|
||||
| //any() |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,24 @@ | ||||
| <!DOCTYPE qhelp PUBLIC | ||||
| "-//Semmle//qhelp//EN" | ||||
| "qhelp.dtd"> | ||||
| <qhelp> | ||||
|
|
||||
| <overview> | ||||
| <p>Prompts can be constructed to bypass the original purposes of an agent and lead to sensitive data leak or | ||||
| operations that were not intended.</p> | ||||
| </overview> | ||||
|
|
||||
| <recommendation> | ||||
| <p>Sanitize user input and also avoid using user input in developer or system level prompts.</p> | ||||
| </recommendation> | ||||
|
|
||||
| <example> | ||||
| <p>In the following examples, the cases marked GOOD show secure prompt construction; whereas in the case marked BAD they may be susceptible to prompt injection.</p> | ||||
| <sample src="examples/TODO.py" /> | ||||
|
||||
| <sample src="examples/TODO.py" /> |
mbaluda marked this conversation as resolved.
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /** | ||
| * @kind path-problem | ||
| * @problem.severity error | ||
| * @security-severity 5.0 | ||
| * @precision high | ||
| * @id py/prompt-injection | ||
| * @tags security | ||
| * external/cwe/cwe-1427 | ||
| */ | ||
|
|
||
| import python | ||
| import semmle.python.security.dataflow.PromptInjectionQuery | ||
| import PromptInjectionFlow::PathGraph | ||
|
|
||
| from PromptInjectionFlow::PathNode source, PromptInjectionFlow::PathNode sink | ||
| where PromptInjectionFlow::flowPath(source, sink) | ||
| select sink.getNode(), source, sink, "This prompt construction depends on a $@.", source.getNode(), | ||
| "user-provided value" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| 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:7:5:7:9 | ControlFlowNode for input | agent_instructions.py:9:50:9:89 | ControlFlowNode for BinaryExpr | provenance | | | ||
| | 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 | | | ||
| | 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 | | | ||
| | openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:22:22:22:46 | ControlFlowNode for BinaryExpr | provenance | | | ||
| | openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:23:15:36:9 | ControlFlowNode for List | provenance | | | ||
| | openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:40:22:40:46 | ControlFlowNode for BinaryExpr | provenance | | | ||
| | openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:58:18:69:9 | ControlFlowNode for List | provenance | | | ||
| | openai_test.py:12:5:12:11 | ControlFlowNode for persona | openai_test.py:73:18:82:9 | ControlFlowNode for List | provenance | | | ||
| | 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:23:15:36:9 | ControlFlowNode for List | provenance | | | ||
| | openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:41:15:41:19 | ControlFlowNode for query | provenance | | | ||
| | openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:46:18:54:13 | ControlFlowNode for Dict | provenance | | | ||
| | openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:58:18:69:9 | ControlFlowNode for List | provenance | | | ||
| | openai_test.py:13:5:13:9 | ControlFlowNode for query | openai_test.py:73:18:82:9 | ControlFlowNode for List | provenance | | | ||
| | 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:46:18:54:13 | ControlFlowNode for Dict | provenance | | | ||
| | openai_test.py:14:5:14:8 | ControlFlowNode for role | openai_test.py:58:18:69: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 | | | ||
| 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 | | ||
| | agent_instructions.py:7:5:7:9 | ControlFlowNode for input | semmle.label | ControlFlowNode for input | | ||
| | agent_instructions.py:7:13:7:19 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | | ||
| | agent_instructions.py:7:13:7:24 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | | ||
| | agent_instructions.py:7:13:7:37 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | ||
| | agent_instructions.py:9:50:9:89 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | ||
| | 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 | | ||
| | openai_test.py:12:15:12:21 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | | ||
| | openai_test.py:12:15:12:26 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | | ||
| | openai_test.py:12:15:12:41 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | ||
| | openai_test.py:13:5:13:9 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | | ||
| | 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:36:9 | ControlFlowNode for List | semmle.label | ControlFlowNode for List | | ||
| | openai_test.py:40:22:40:46 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr | | ||
| | openai_test.py:41:15:41:19 | ControlFlowNode for query | semmle.label | ControlFlowNode for query | | ||
| | openai_test.py:46:18:54:13 | ControlFlowNode for Dict | semmle.label | ControlFlowNode for Dict | | ||
| | openai_test.py:58:18:69:9 | ControlFlowNode for List | semmle.label | ControlFlowNode for List | | ||
| | openai_test.py:73:18:82:9 | ControlFlowNode for List | semmle.label | ControlFlowNode for List | | ||
| subpaths | ||
| #select | ||
| | 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 | | ||
| | 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:36:9 | ControlFlowNode for List | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:23:15:36: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:40:22:40:46 | ControlFlowNode for BinaryExpr | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:40:22:40: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:41:15:41:19 | ControlFlowNode for query | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:41:15:41: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:46:18:54:13 | ControlFlowNode for Dict | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:46:18:54:13 | ControlFlowNode for Dict | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | | ||
| | openai_test.py:58:18:69:9 | ControlFlowNode for List | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:58:18:69: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:73:18:82:9 | ControlFlowNode for List | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | openai_test.py:73:18:82:9 | ControlFlowNode for List | This prompt construction depends on a $@. | openai_test.py:2:26:2:32 | ControlFlowNode for ImportMember | user-provided value | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| query: Security/CWE-1427/PromptInjection.ql | ||
Check warningCode scanning / CodeQL Query test without inline test expectations Warning test
Query test does not use inline test expectations.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| from agents import Agent, Runner | ||
| from flask import Flask, request # $ Source=flask | ||
| app = Flask(__name__) | ||
|
|
||
| @app.route("/parameter-route") | ||
| def get_input(): | ||
| input = request.args.get("input") | ||
|
|
||
| agent = Agent(name="Assistant", instructions="This prompt is customized for " + input) # $Alert[py/prompt-injection] | ||
|
|
||
| result = Runner.run_sync(agent, "This is a user message.") | ||
| print(result.final_output) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a typo in the comment. "openAI" should be "OpenAI" with a capital "O" and capital "A" and capital "I".