From 0b7133c4ce28c1dd46eca96b9fcc0bdc93165469 Mon Sep 17 00:00:00 2001 From: BazookaMusic Date: Thu, 30 Apr 2026 17:39:09 +0200 Subject: [PATCH] JS: Add prompt injection detection (CWE-1427) for OpenAI, Anthropic, and Google GenAI SDKs Add experimental CodeQL query detecting prompt injection vulnerabilities in JavaScript/TypeScript applications using AI SDK libraries. Modeled frameworks: - openai (OpenAI, AzureOpenAI): responses, chat.completions, completions, images, embeddings, beta.assistants, beta.threads, audio APIs - @openai/agents: Agent instructions, handoffDescription, run/Runner.run, asTool, tool() - @anthropic-ai/sdk: messages.create, beta.messages.create, beta.agents.create/update - @google/genai (GoogleGenAI): generateContent, generateContentStream, generateImages, editImage, chats, live.connect Includes role-based filtering (system/developer/assistant/model roles) and constant-comparison sanitizer guard. --- .../ql/lib/semmle/javascript/Concepts.qll | 25 ++ .../Security/CWE-1427/PromptInjection.qhelp | 24 ++ .../Security/CWE-1427/PromptInjection.ql | 20 ++ .../Security/CWE-1427/examples/example.py | 17 ++ .../javascript/frameworks/Anthropic.qll | 64 +++++ .../javascript/frameworks/GoogleGenAI.qll | 85 ++++++ .../semmle/javascript/frameworks/OpenAI.qll | 199 ++++++++++++++ .../PromptInjectionCustomizations.qll | 93 +++++++ .../PromptInjection/PromptInjectionQuery.qll | 25 ++ .../CWE-1427/PromptInjection.expected | 245 ++++++++++++++++++ .../Security/CWE-1427/PromptInjection.qlref | 1 + .../Security/CWE-1427/agents_test.js | 110 ++++++++ .../Security/CWE-1427/anthropic_test.js | 133 ++++++++++ .../Security/CWE-1427/gemini_test.js | 126 +++++++++ .../Security/CWE-1427/openai_test.js | 215 +++++++++++++++ 15 files changed, 1382 insertions(+) create mode 100644 javascript/ql/src/experimental/Security/CWE-1427/PromptInjection.qhelp create mode 100644 javascript/ql/src/experimental/Security/CWE-1427/PromptInjection.ql create mode 100644 javascript/ql/src/experimental/Security/CWE-1427/examples/example.py create mode 100644 javascript/ql/src/experimental/semmle/javascript/frameworks/Anthropic.qll create mode 100644 javascript/ql/src/experimental/semmle/javascript/frameworks/GoogleGenAI.qll create mode 100644 javascript/ql/src/experimental/semmle/javascript/frameworks/OpenAI.qll create mode 100644 javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/PromptInjectionCustomizations.qll create mode 100644 javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/PromptInjectionQuery.qll create mode 100644 javascript/ql/test/experimental/Security/CWE-1427/PromptInjection.expected create mode 100644 javascript/ql/test/experimental/Security/CWE-1427/PromptInjection.qlref create mode 100644 javascript/ql/test/experimental/Security/CWE-1427/agents_test.js create mode 100644 javascript/ql/test/experimental/Security/CWE-1427/anthropic_test.js create mode 100644 javascript/ql/test/experimental/Security/CWE-1427/gemini_test.js create mode 100644 javascript/ql/test/experimental/Security/CWE-1427/openai_test.js diff --git a/javascript/ql/lib/semmle/javascript/Concepts.qll b/javascript/ql/lib/semmle/javascript/Concepts.qll index 70fe76ae5f13..25341c916d78 100644 --- a/javascript/ql/lib/semmle/javascript/Concepts.qll +++ b/javascript/ql/lib/semmle/javascript/Concepts.qll @@ -226,3 +226,28 @@ module Cryptography { class CryptographicAlgorithm = SC::CryptographicAlgorithm; } + +/** + * A data-flow node that prompts an AI model. + * + * Extend this class to refine existing API models. If you want to model new APIs, + * extend `AIPrompt::Range` instead. + */ +class AIPrompt extends DataFlow::Node instanceof AIPrompt::Range { + /** Gets an input that is used as AI prompt. */ + DataFlow::Node getAPrompt() { result = super.getAPrompt() } +} + +/** Provides a class for modeling new AI prompting mechanisms. */ +module AIPrompt { + /** + * A data-flow node that prompts an AI model. + * + * Extend this class to model new APIs. If you want to refine existing API models, + * extend `AIPrompt` instead. + */ + abstract class Range extends DataFlow::Node { + /** Gets an input that is used as AI prompt. */ + abstract DataFlow::Node getAPrompt(); + } +} diff --git a/javascript/ql/src/experimental/Security/CWE-1427/PromptInjection.qhelp b/javascript/ql/src/experimental/Security/CWE-1427/PromptInjection.qhelp new file mode 100644 index 000000000000..ef6b9c83ac26 --- /dev/null +++ b/javascript/ql/src/experimental/Security/CWE-1427/PromptInjection.qhelp @@ -0,0 +1,24 @@ + + + + +

Prompts can be constructed to bypass the original purposes of an agent and lead to sensitive data leak or +operations that were not intended.

+
+ + +

Sanitize user input and also avoid using user input in developer or system level prompts.

+
+ + +

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.

+ +
+ + +
  • OpenAI: Guardrails.
  • +
    + +
    diff --git a/javascript/ql/src/experimental/Security/CWE-1427/PromptInjection.ql b/javascript/ql/src/experimental/Security/CWE-1427/PromptInjection.ql new file mode 100644 index 000000000000..69f5f7e836c1 --- /dev/null +++ b/javascript/ql/src/experimental/Security/CWE-1427/PromptInjection.ql @@ -0,0 +1,20 @@ +/** + * @name Prompt injection + * @kind path-problem + * @problem.severity error + * @security-severity 5.0 + * @precision high + * @id js/prompt-injection + * @tags security + * experimental + * external/cwe/cwe-1427 + */ + +import javascript +import experimental.semmle.javascript.security.PromptInjection.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" diff --git a/javascript/ql/src/experimental/Security/CWE-1427/examples/example.py b/javascript/ql/src/experimental/Security/CWE-1427/examples/example.py new file mode 100644 index 000000000000..a049f727b37a --- /dev/null +++ b/javascript/ql/src/experimental/Security/CWE-1427/examples/example.py @@ -0,0 +1,17 @@ +from flask import Flask, request +from agents import Agent +from guardrails import GuardrailAgent + +@app.route("/parameter-route") +def get_input(): + input = request.args.get("input") + + goodAgent = GuardrailAgent( # GOOD: Agent created with guardrails automatically configured. + config=Path("guardrails_config.json"), + name="Assistant", + instructions="This prompt is customized for " + input) + + badAgent = Agent( + name="Assistant", + instructions="This prompt is customized for " + input # BAD: user input in agent instruction. + ) diff --git a/javascript/ql/src/experimental/semmle/javascript/frameworks/Anthropic.qll b/javascript/ql/src/experimental/semmle/javascript/frameworks/Anthropic.qll new file mode 100644 index 000000000000..be500876c75f --- /dev/null +++ b/javascript/ql/src/experimental/semmle/javascript/frameworks/Anthropic.qll @@ -0,0 +1,64 @@ +/** + * Provides classes modeling security-relevant aspects of the `@anthropic-ai/sdk` package. + * See https://github.com/anthropics/anthropic-sdk-typescript + */ + +private import javascript + +module Anthropic { + /** Gets a reference to the `Anthropic` client instance. */ + API::Node classRef() { + // Default export: import Anthropic from '@anthropic-ai/sdk'; new Anthropic() + result = API::moduleImport("@anthropic-ai/sdk").getInstance() + } + + + /** Gets a reference to a sink for the system prompt in the Anthropic messages API. */ + API::Node getContentNode() { + exists(API::Node createParams | + // client.messages.create({ ... }) + createParams = classRef() + .getMember("messages") + .getMember("create") + .getParameter(0) + or + // client.beta.messages.create({ ... }) + createParams = classRef() + .getMember("beta") + .getMember("messages") + .getMember("create") + .getParameter(0) + | + // system: "string" + result = createParams.getMember("system") + or + // system: [{ type: "text", text: "..." }] + result = createParams.getMember("system").getArrayElement().getMember("text") + or + // messages: [{ role: "assistant", content: "..." }] + // Injecting content into what the model said from external sources is very likely an injection. + exists(API::Node msg | + msg = createParams.getMember("messages").getArrayElement() and + msg.getMember("role").asSink().mayHaveStringValue("assistant") + | + result = msg.getMember("content") + ) + ) + or + // client.beta.agents.create({ system: "..." }) + result = classRef() + .getMember("beta") + .getMember("agents") + .getMember("create") + .getParameter(0) + .getMember("system") + or + // client.beta.agents.update(agentId, { system: "..." }) + result = classRef() + .getMember("beta") + .getMember("agents") + .getMember("update") + .getParameter(1) + .getMember("system") + } +} \ No newline at end of file diff --git a/javascript/ql/src/experimental/semmle/javascript/frameworks/GoogleGenAI.qll b/javascript/ql/src/experimental/semmle/javascript/frameworks/GoogleGenAI.qll new file mode 100644 index 000000000000..c6f119f00f70 --- /dev/null +++ b/javascript/ql/src/experimental/semmle/javascript/frameworks/GoogleGenAI.qll @@ -0,0 +1,85 @@ +/** + * Provides classes modeling security-relevant aspects of the `@google/genai` package. + * See https://github.com/googleapis/js-genai + */ + +private import javascript + +module GoogleGenAI { + /** Gets a reference to the `GoogleGenAI` client instance. */ + API::Node clientRef() { + // import { GoogleGenAI } from '@google/genai'; const ai = new GoogleGenAI(...) + result = + API::moduleImport("@google/genai").getMember("GoogleGenAI").getInstance() + } + + /** Gets a reference to a sink for prompt content in the Google GenAI SDK. */ + API::Node getContentNode() { + exists(API::Node params | + // ai.models.generateContent({ contents, config }) + // ai.models.generateContentStream({ contents, config }) + params = + clientRef() + .getMember("models") + .getMember(["generateContent", "generateContentStream"]) + .getParameter(0) + | + // config.systemInstruction + result = params.getMember("config").getMember("systemInstruction") + or + // contents: [{ role: "model", parts: [{ text: "..." }] }] + // Gemini uses "model" role instead of "assistant" + exists(API::Node msg | + msg = params.getMember("contents").getArrayElement() and + msg.getMember("role").asSink().mayHaveStringValue("model") + | + result = msg.getMember("parts").getArrayElement().getMember("text") + ) + ) + or + // ai.models.generateImages({ prompt, config }) + result = + clientRef() + .getMember("models") + .getMember("generateImages") + .getParameter(0) + .getMember("prompt") + or + // ai.models.editImage({ prompt, referenceImages, config }) + result = + clientRef() + .getMember("models") + .getMember("editImage") + .getParameter(0) + .getMember("prompt") + or + // ai.chats.create({ config: { systemInstruction: ... } }) + result = + clientRef() + .getMember("chats") + .getMember("create") + .getParameter(0) + .getMember("config") + .getMember("systemInstruction") + or + // chat.sendMessage({ config: { systemInstruction: ... } }) + result = + clientRef() + .getMember("chats") + .getMember("create") + .getReturn() + .getMember("sendMessage") + .getParameter(0) + .getMember("config") + .getMember("systemInstruction") + or + // ai.live.connect({ config: { systemInstruction: ... } }) + result = + clientRef() + .getMember("live") + .getMember("connect") + .getParameter(0) + .getMember("config") + .getMember("systemInstruction") + } +} diff --git a/javascript/ql/src/experimental/semmle/javascript/frameworks/OpenAI.qll b/javascript/ql/src/experimental/semmle/javascript/frameworks/OpenAI.qll new file mode 100644 index 000000000000..4704fae2081d --- /dev/null +++ b/javascript/ql/src/experimental/semmle/javascript/frameworks/OpenAI.qll @@ -0,0 +1,199 @@ +/** + * Provides classes modeling security-relevant aspects of the `openAI-Node` package. + * See https://github.com/openai/openai-node + */ + +private import javascript + + /** Holds if `msg` is a message array element with a privileged role. */ +private predicate isSystemOrDevMessage(API::Node msg) { + msg.getMember("role").asSink().mayHaveStringValue(["system", "developer", "assistant"]) +} + +module OpenAI { + /** Gets a reference to the `openai.OpenAI` class. */ + API::Node classRef() { + // Default export: import OpenAI from 'openai'; new OpenAI() + result = API::moduleImport("openai").getInstance() + or + // Named import: import { OpenAI, AzureOpenAI } from 'openai'; new AzureOpenAI() + result = API::moduleImport("openai").getMember(["OpenAI", "AzureOpenAI"]).getInstance() + } + + + /** Gets a reference to a potential property of `openai.OpenAI` called instructions which refers to the system prompt. */ + API::Node getContentNode() { + // responses.create({ input: ..., instructions: ... }) + // input can be a string or an array of message objects + exists(API::Node responsesCreate | + responsesCreate = + classRef() + .getMember("responses") + .getMember("create") + .getParameter(0) + | + // instructions: "string" + result = responsesCreate.getMember("instructions") + // intended that user data can flow into input + // or + // // input: "string" + // result = responsesCreate.getMember("input") + or + // input: [{ role: "system"/"developer", content: "..." }] + exists(API::Node msg | + msg = responsesCreate.getMember("input").getArrayElement() and + isSystemOrDevMessage(msg) + | + result = msg.getMember("content") + ) + ) + or + // chat.completions.create({ messages: [{ role: "system"/"developer", content: ... }] }) + // content can be a string or an array of content parts + exists(API::Node msg, API::Node content | + msg = + classRef() + .getMember("chat") + .getMember("completions") + .getMember("create") + .getParameter(0) + .getMember("messages") + .getArrayElement() and + isSystemOrDevMessage(msg) and + content = msg.getMember("content") + | + // content: "string" + result = content + or + // content: [{ type: "text", text: "..." }] + result = content.getArrayElement().getMember("text") + ) + or + // Legacy completions API: completions.create({ prompt: ... }) + result = + classRef() + .getMember("completions") + .getMember("create") + .getParameter(0) + .getMember("prompt") + or + // images.generate({ prompt: ... }) and images.edit({ prompt: ... }) + result = + classRef() + .getMember("images") + .getMember(["generate", "edit"]) + .getParameter(0) + .getMember("prompt") + or + // embeddings.create({ input: ... }) + result = + classRef() + .getMember("embeddings") + .getMember("create") + .getParameter(0) + .getMember("input") + or + // beta.assistants.create({ instructions: ... }) and beta.assistants.update(id, { instructions: ... }) + result = + classRef() + .getMember("beta") + .getMember("assistants") + .getMember(["create", "update"]) + .getParameter(0) + .getMember("instructions") + or + // beta.threads.runs.create(threadId, { instructions: ..., additional_instructions: ... }) + result = + classRef() + .getMember("beta") + .getMember("threads") + .getMember("runs") + .getMember("create") + .getParameter(1) + .getMember(["instructions", "additional_instructions"]) + or + // beta.threads.messages.create(threadId, { role: "system"/"developer", content: ... }) + exists(API::Node msg | + msg = + classRef() + .getMember("beta") + .getMember("threads") + .getMember("messages") + .getMember("create") + .getParameter(1) and + isSystemOrDevMessage(msg) + | + result = msg.getMember("content") + ) + or + // audio.transcriptions.create({ prompt: ... }) and audio.translations.create({ prompt: ... }) + result = + classRef() + .getMember("audio") + .getMember(["transcriptions", "translations"]) + .getMember("create") + .getParameter(0) + .getMember("prompt") + } +} + +/** + * Provides models for agents SDK (instances of the `agents` class etc). + * + * See https://github.com/openai/openai-agents-js. + */ +module AgentSDK { + API::Node moduleRef() { result = API::moduleImport("@openai/agents") } + + /** Gets a reference to the `agents.Runner` class. */ + API::Node agentConstructor() { result = moduleRef().getMember("Agent") } + + API::Node classInstance() { result = agentConstructor().getInstance() } + + /** Gets a reference to the top-level run() or Runner.run() functions. */ + API::Node run() { + // import { run } from '@openai/agents'; run(agent, input) + result = moduleRef().getMember("run") + or + // const runner = new Runner(); runner.run(agent, input) + result = moduleRef().getMember("Runner").getInstance().getMember("run") + } + + API::Node asTool() { result = classInstance().getMember("asTool")} + + API::Node toolFunction() { result = moduleRef().getMember("tool") } + + /** Gets a reference to a potential property of `agents.Runner` called input which can refer to a system prompt depending on the role specified. */ + API::Node getContentNode() { + // Agent({ instructions: ... }) + result = agentConstructor() + .getParameter(0) + .getMember(["instructions", "handoffDescription"]) + or + // Agent({ instructions: (runContext) => returnValue }) + result = agentConstructor() + .getParameter(0) + .getMember("instructions") + .getReturn() + or + // run(agent, input) or runner.run(agent, input) — string input + result = run() + .getParameter(1) + or + // run(agent, [{ role: "system"/"developer", content: ... }]) + exists(API::Node msg | + msg = run() + .getParameter(1) + .getArrayElement() and + isSystemOrDevMessage(msg) + | + result = msg.getMember("content") + ) + or + // agent.asTool({..., toolDescription: ...}) + result = asTool().getParameter(0).getMember("toolDescription") + or + // tool({..., description: ...}) + result = toolFunction().getParameter(0).getMember("description") + } +} diff --git a/javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/PromptInjectionCustomizations.qll b/javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/PromptInjectionCustomizations.qll new file mode 100644 index 000000000000..ea769b860865 --- /dev/null +++ b/javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/PromptInjectionCustomizations.qll @@ -0,0 +1,93 @@ +/** + * Provides default sources, sinks and sanitizers for detecting + * "prompt injection" + * vulnerabilities, as well as extension points for adding your own. + */ + +import javascript + +private import semmle.javascript.dataflow.DataFlow +private import semmle.javascript.Concepts +private import semmle.javascript.security.dataflow.RemoteFlowSources +private import semmle.javascript.dataflow.internal.BarrierGuards +private import semmle.javascript.frameworks.data.ModelsAsData +private import experimental.semmle.javascript.frameworks.OpenAI +private import experimental.semmle.javascript.frameworks.Anthropic +private import experimental.semmle.javascript.frameworks.GoogleGenAI + +/** + * Provides default sources, sinks and sanitizers for detecting + * "prompt injection" + * vulnerabilities, as well as extension points for adding your own. + */ +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 { } + + /** + * A prompt to an AI model, considered as a flow sink. + */ + class AIPromptAsSink extends Sink { + AIPromptAsSink() { this = any(AIPrompt p).getAPrompt() } + } + + private class SinkFromModel extends Sink { + SinkFromModel() { this = ModelOutput::getASinkNode("prompt-injection").asSink() } + } + + private class PromptContentSink extends Sink { + PromptContentSink() { + this = OpenAI::getContentNode().asSink() + or + this = AgentSDK::getContentNode().asSink() + or + this = Anthropic::getContentNode().asSink() + or + this = GoogleGenAI::getContentNode().asSink() + } + } + + private class ConstCompareAsSanitizerGuard extends Sanitizer { + ConstCompareAsSanitizerGuard() + { + this = DataFlow::MakeBarrierGuard::getABarrierNode() + } + } + + /** + * A comparison with a constant, considered as a sanitizer-guard. + */ + private class ConstCompareBarrierGuard extends DataFlow::ValueNode + { + override EqualityTest astNode; + + ConstCompareBarrierGuard() + { + astNode.hasOperands(_, any(ConstantString cs)) + } + + predicate blocksExpr(boolean outcome, Expr e) { + outcome = astNode.getPolarity() and + e = astNode.getLeftOperand() and + e = astNode.getAnOperand() and + not e instanceof ConstantString + } + } +} diff --git a/javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/PromptInjectionQuery.qll b/javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/PromptInjectionQuery.qll new file mode 100644 index 000000000000..473461c3bb3e --- /dev/null +++ b/javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/PromptInjectionQuery.qll @@ -0,0 +1,25 @@ +/** + * Provides a taint-tracking configuration 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 javascript +import semmle.javascript.dataflow.DataFlow +import semmle.javascript.dataflow.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 } + + predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } + + predicate observeDiffInformedIncrementalMode() { any() } +} + +/** Global taint-tracking for detecting "prompt injection" vulnerabilities. */ +module PromptInjectionFlow = TaintTracking::Global; diff --git a/javascript/ql/test/experimental/Security/CWE-1427/PromptInjection.expected b/javascript/ql/test/experimental/Security/CWE-1427/PromptInjection.expected new file mode 100644 index 000000000000..810b4522755a --- /dev/null +++ b/javascript/ql/test/experimental/Security/CWE-1427/PromptInjection.expected @@ -0,0 +1,245 @@ +edges +| agents_test.js:8:9:8:15 | persona | agents_test.js:16:36:16:42 | persona | provenance | | +| agents_test.js:8:9:8:15 | persona | agents_test.js:43:38:43:44 | persona | provenance | | +| agents_test.js:8:9:8:15 | persona | agents_test.js:51:37:51:43 | persona | provenance | | +| agents_test.js:8:9:8:15 | persona | agents_test.js:59:42:59:48 | persona | provenance | | +| agents_test.js:8:9:8:15 | persona | agents_test.js:73:49:73:55 | persona | provenance | | +| agents_test.js:8:9:8:15 | persona | agents_test.js:81:52:81:58 | persona | provenance | | +| agents_test.js:8:9:8:15 | persona | agents_test.js:96:49:96:55 | persona | provenance | | +| agents_test.js:8:19:8:35 | req.query.persona | agents_test.js:8:9:8:15 | persona | provenance | | +| agents_test.js:9:9:9:13 | query | agents_test.js:67:32:67:36 | query | provenance | | +| agents_test.js:9:17:9:31 | req.query.query | agents_test.js:9:9:9:13 | query | provenance | | +| agents_test.js:16:36:16:42 | persona | agents_test.js:16:19:16:42 | "Talk l ... persona | provenance | | +| agents_test.js:16:36:16:42 | persona | agents_test.js:25:31:25:37 | persona | provenance | | +| agents_test.js:16:36:16:42 | persona | agents_test.js:33:31:33:37 | persona | provenance | | +| agents_test.js:16:36:16:42 | persona | agents_test.js:43:38:43:44 | persona | provenance | | +| agents_test.js:25:31:25:37 | persona | agents_test.js:25:14:25:37 | "Talk l ... persona | provenance | | +| agents_test.js:33:14:33:37 | "Talk l ... persona | agents_test.js:32:19:34:5 | return of method instructions | provenance | | +| agents_test.js:33:31:33:37 | persona | agents_test.js:33:14:33:37 | "Talk l ... persona | provenance | | +| agents_test.js:43:38:43:44 | persona | agents_test.js:43:25:43:44 | "Handles " + persona | provenance | | +| agents_test.js:43:38:43:44 | persona | agents_test.js:51:37:51:43 | persona | provenance | | +| agents_test.js:51:37:51:43 | persona | agents_test.js:51:22:51:43 | "Ask ab ... persona | provenance | | +| agents_test.js:51:37:51:43 | persona | agents_test.js:59:42:59:48 | persona | provenance | | +| agents_test.js:59:42:59:48 | persona | agents_test.js:59:18:59:48 | "Look u ... persona | provenance | | +| agents_test.js:59:42:59:48 | persona | agents_test.js:73:49:73:55 | persona | provenance | | +| agents_test.js:73:49:73:55 | persona | agents_test.js:73:32:73:55 | "Talk l ... persona | provenance | | +| agents_test.js:73:49:73:55 | persona | agents_test.js:81:52:81:58 | persona | provenance | | +| agents_test.js:81:52:81:58 | persona | agents_test.js:81:35:81:58 | "Talk l ... persona | provenance | | +| agents_test.js:81:52:81:58 | persona | agents_test.js:96:49:96:55 | persona | provenance | | +| agents_test.js:96:49:96:55 | persona | agents_test.js:96:32:96:55 | "Talk l ... persona | provenance | | +| anthropic_test.js:8:9:8:15 | persona | anthropic_test.js:17:30:17:36 | persona | provenance | | +| anthropic_test.js:8:9:8:15 | persona | anthropic_test.js:30:32:30:38 | persona | provenance | | +| anthropic_test.js:8:9:8:15 | persona | anthropic_test.js:45:35:45:41 | persona | provenance | | +| anthropic_test.js:8:9:8:15 | persona | anthropic_test.js:71:30:71:36 | persona | provenance | | +| anthropic_test.js:8:9:8:15 | persona | anthropic_test.js:84:32:84:38 | persona | provenance | | +| anthropic_test.js:8:9:8:15 | persona | anthropic_test.js:99:35:99:41 | persona | provenance | | +| anthropic_test.js:8:9:8:15 | persona | anthropic_test.js:110:30:110:36 | persona | provenance | | +| anthropic_test.js:8:9:8:15 | persona | anthropic_test.js:117:30:117:36 | persona | provenance | | +| anthropic_test.js:8:19:8:35 | req.query.persona | anthropic_test.js:8:9:8:15 | persona | provenance | | +| anthropic_test.js:17:30:17:36 | persona | anthropic_test.js:17:13:17:36 | "Talk l ... persona | provenance | | +| anthropic_test.js:30:32:30:38 | persona | anthropic_test.js:30:15:30:38 | "Talk l ... persona | provenance | | +| anthropic_test.js:45:35:45:41 | persona | anthropic_test.js:45:18:45:41 | "Talk l ... persona | provenance | | +| anthropic_test.js:71:30:71:36 | persona | anthropic_test.js:71:13:71:36 | "Talk l ... persona | provenance | | +| anthropic_test.js:84:32:84:38 | persona | anthropic_test.js:84:15:84:38 | "Talk l ... persona | provenance | | +| anthropic_test.js:99:35:99:41 | persona | anthropic_test.js:99:18:99:41 | "Talk l ... persona | provenance | | +| anthropic_test.js:110:30:110:36 | persona | anthropic_test.js:110:13:110:36 | "Talk l ... persona | provenance | | +| anthropic_test.js:117:30:117:36 | persona | anthropic_test.js:117:13:117:36 | "Talk l ... persona | provenance | | +| gemini_test.js:8:9:8:15 | persona | gemini_test.js:18:43:18:49 | persona | provenance | | +| gemini_test.js:8:9:8:15 | persona | gemini_test.js:30:42:30:48 | persona | provenance | | +| gemini_test.js:8:9:8:15 | persona | gemini_test.js:59:43:59:49 | persona | provenance | | +| gemini_test.js:8:9:8:15 | persona | gemini_test.js:68:36:68:42 | persona | provenance | | +| gemini_test.js:8:9:8:15 | persona | gemini_test.js:76:36:76:42 | persona | provenance | | +| gemini_test.js:8:9:8:15 | persona | gemini_test.js:85:43:85:49 | persona | provenance | | +| gemini_test.js:8:9:8:15 | persona | gemini_test.js:95:43:95:49 | persona | provenance | | +| gemini_test.js:8:9:8:15 | persona | gemini_test.js:105:43:105:49 | persona | provenance | | +| gemini_test.js:8:19:8:35 | req.query.persona | gemini_test.js:8:9:8:15 | persona | provenance | | +| gemini_test.js:18:43:18:49 | persona | gemini_test.js:18:26:18:49 | "Talk l ... persona | provenance | | +| gemini_test.js:30:42:30:48 | persona | gemini_test.js:30:25:30:48 | "Talk l ... persona | provenance | | +| gemini_test.js:59:43:59:49 | persona | gemini_test.js:59:26:59:49 | "Talk l ... persona | provenance | | +| gemini_test.js:68:36:68:42 | persona | gemini_test.js:68:13:68:42 | "Draw a ... persona | provenance | | +| gemini_test.js:76:36:76:42 | persona | gemini_test.js:76:13:76:42 | "Edit t ... persona | provenance | | +| gemini_test.js:85:43:85:49 | persona | gemini_test.js:85:26:85:49 | "Talk l ... persona | provenance | | +| gemini_test.js:95:43:95:49 | persona | gemini_test.js:95:26:95:49 | "Talk l ... persona | provenance | | +| gemini_test.js:105:43:105:49 | persona | gemini_test.js:105:26:105:49 | "Talk l ... persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:19:36:19:42 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:29:35:29:41 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:44:35:44:41 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:68:35:68:41 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:83:35:83:41 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:97:36:97:42 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:110:35:110:41 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:120:30:120:36 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:127:36:127:42 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:132:36:132:42 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:140:29:140:35 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:149:36:149:42 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:160:36:160:42 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:166:52:166:58 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:172:31:172:37 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:187:35:187:41 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:194:34:194:40 | persona | provenance | | +| openai_test.js:11:9:11:15 | persona | openai_test.js:200:49:200:55 | persona | provenance | | +| openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:11:9:11:15 | persona | provenance | | +| openai_test.js:19:36:19:42 | persona | openai_test.js:19:19:19:42 | "Talk l ... persona | provenance | | +| openai_test.js:29:35:29:41 | persona | openai_test.js:29:18:29:41 | "Talk l ... persona | provenance | | +| openai_test.js:44:35:44:41 | persona | openai_test.js:44:18:44:41 | "Talk l ... persona | provenance | | +| openai_test.js:68:35:68:41 | persona | openai_test.js:68:18:68:41 | "Talk l ... persona | provenance | | +| openai_test.js:83:35:83:41 | persona | openai_test.js:83:18:83:41 | "Talk l ... persona | provenance | | +| openai_test.js:97:36:97:42 | persona | openai_test.js:97:19:97:42 | "Talk l ... persona | provenance | | +| openai_test.js:110:35:110:41 | persona | openai_test.js:110:18:110:41 | "Talk l ... persona | provenance | | +| openai_test.js:120:30:120:36 | persona | openai_test.js:120:13:120:36 | "Talk l ... persona | provenance | | +| openai_test.js:127:36:127:42 | persona | openai_test.js:127:13:127:42 | "Draw a ... persona | provenance | | +| openai_test.js:132:36:132:42 | persona | openai_test.js:132:13:132:42 | "Edit t ... persona | provenance | | +| openai_test.js:140:29:140:35 | persona | openai_test.js:140:12:140:35 | "Embed ... persona | provenance | | +| openai_test.js:149:36:149:42 | persona | openai_test.js:149:19:149:42 | "Talk l ... persona | provenance | | +| openai_test.js:160:36:160:42 | persona | openai_test.js:160:19:160:42 | "Talk l ... persona | provenance | | +| openai_test.js:166:52:166:58 | persona | openai_test.js:166:30:166:58 | "Also t ... persona | provenance | | +| openai_test.js:172:31:172:37 | persona | openai_test.js:172:14:172:37 | "Talk l ... persona | provenance | | +| openai_test.js:187:35:187:41 | persona | openai_test.js:187:13:187:41 | "Transc ... persona | provenance | | +| openai_test.js:194:34:194:40 | persona | openai_test.js:194:13:194:40 | "Transl ... persona | provenance | | +| openai_test.js:200:49:200:55 | persona | openai_test.js:200:32:200:55 | "Talk l ... persona | provenance | | +nodes +| agents_test.js:8:9:8:15 | persona | semmle.label | persona | +| agents_test.js:8:19:8:35 | req.query.persona | semmle.label | req.query.persona | +| agents_test.js:9:9:9:13 | query | semmle.label | query | +| agents_test.js:9:17:9:31 | req.query.query | semmle.label | req.query.query | +| agents_test.js:16:19:16:42 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| agents_test.js:16:36:16:42 | persona | semmle.label | persona | +| agents_test.js:25:14:25:37 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| agents_test.js:25:31:25:37 | persona | semmle.label | persona | +| agents_test.js:32:19:34:5 | return of method instructions | semmle.label | return of method instructions | +| agents_test.js:33:14:33:37 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| agents_test.js:33:31:33:37 | persona | semmle.label | persona | +| agents_test.js:43:25:43:44 | "Handles " + persona | semmle.label | "Handles " + persona | +| agents_test.js:43:38:43:44 | persona | semmle.label | persona | +| agents_test.js:51:22:51:43 | "Ask ab ... persona | semmle.label | "Ask ab ... persona | +| agents_test.js:51:37:51:43 | persona | semmle.label | persona | +| agents_test.js:59:18:59:48 | "Look u ... persona | semmle.label | "Look u ... persona | +| agents_test.js:59:42:59:48 | persona | semmle.label | persona | +| agents_test.js:67:32:67:36 | query | semmle.label | query | +| agents_test.js:73:32:73:55 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| agents_test.js:73:49:73:55 | persona | semmle.label | persona | +| agents_test.js:81:35:81:58 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| agents_test.js:81:52:81:58 | persona | semmle.label | persona | +| agents_test.js:96:32:96:55 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| agents_test.js:96:49:96:55 | persona | semmle.label | persona | +| anthropic_test.js:8:9:8:15 | persona | semmle.label | persona | +| anthropic_test.js:8:19:8:35 | req.query.persona | semmle.label | req.query.persona | +| anthropic_test.js:17:13:17:36 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| anthropic_test.js:17:30:17:36 | persona | semmle.label | persona | +| anthropic_test.js:30:15:30:38 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| anthropic_test.js:30:32:30:38 | persona | semmle.label | persona | +| anthropic_test.js:45:18:45:41 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| anthropic_test.js:45:35:45:41 | persona | semmle.label | persona | +| anthropic_test.js:71:13:71:36 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| anthropic_test.js:71:30:71:36 | persona | semmle.label | persona | +| anthropic_test.js:84:15:84:38 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| anthropic_test.js:84:32:84:38 | persona | semmle.label | persona | +| anthropic_test.js:99:18:99:41 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| anthropic_test.js:99:35:99:41 | persona | semmle.label | persona | +| anthropic_test.js:110:13:110:36 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| anthropic_test.js:110:30:110:36 | persona | semmle.label | persona | +| anthropic_test.js:117:13:117:36 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| anthropic_test.js:117:30:117:36 | persona | semmle.label | persona | +| gemini_test.js:8:9:8:15 | persona | semmle.label | persona | +| gemini_test.js:8:19:8:35 | req.query.persona | semmle.label | req.query.persona | +| gemini_test.js:18:26:18:49 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| gemini_test.js:18:43:18:49 | persona | semmle.label | persona | +| gemini_test.js:30:25:30:48 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| gemini_test.js:30:42:30:48 | persona | semmle.label | persona | +| gemini_test.js:59:26:59:49 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| gemini_test.js:59:43:59:49 | persona | semmle.label | persona | +| gemini_test.js:68:13:68:42 | "Draw a ... persona | semmle.label | "Draw a ... persona | +| gemini_test.js:68:36:68:42 | persona | semmle.label | persona | +| gemini_test.js:76:13:76:42 | "Edit t ... persona | semmle.label | "Edit t ... persona | +| gemini_test.js:76:36:76:42 | persona | semmle.label | persona | +| gemini_test.js:85:26:85:49 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| gemini_test.js:85:43:85:49 | persona | semmle.label | persona | +| gemini_test.js:95:26:95:49 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| gemini_test.js:95:43:95:49 | persona | semmle.label | persona | +| gemini_test.js:105:26:105:49 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| gemini_test.js:105:43:105:49 | persona | semmle.label | persona | +| openai_test.js:11:9:11:15 | persona | semmle.label | persona | +| openai_test.js:11:19:11:35 | req.query.persona | semmle.label | req.query.persona | +| openai_test.js:19:19:19:42 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| openai_test.js:19:36:19:42 | persona | semmle.label | persona | +| openai_test.js:29:18:29:41 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| openai_test.js:29:35:29:41 | persona | semmle.label | persona | +| openai_test.js:44:18:44:41 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| openai_test.js:44:35:44:41 | persona | semmle.label | persona | +| openai_test.js:68:18:68:41 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| openai_test.js:68:35:68:41 | persona | semmle.label | persona | +| openai_test.js:83:18:83:41 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| openai_test.js:83:35:83:41 | persona | semmle.label | persona | +| openai_test.js:97:19:97:42 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| openai_test.js:97:36:97:42 | persona | semmle.label | persona | +| openai_test.js:110:18:110:41 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| openai_test.js:110:35:110:41 | persona | semmle.label | persona | +| openai_test.js:120:13:120:36 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| openai_test.js:120:30:120:36 | persona | semmle.label | persona | +| openai_test.js:127:13:127:42 | "Draw a ... persona | semmle.label | "Draw a ... persona | +| openai_test.js:127:36:127:42 | persona | semmle.label | persona | +| openai_test.js:132:13:132:42 | "Edit t ... persona | semmle.label | "Edit t ... persona | +| openai_test.js:132:36:132:42 | persona | semmle.label | persona | +| openai_test.js:140:12:140:35 | "Embed ... persona | semmle.label | "Embed ... persona | +| openai_test.js:140:29:140:35 | persona | semmle.label | persona | +| openai_test.js:149:19:149:42 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| openai_test.js:149:36:149:42 | persona | semmle.label | persona | +| openai_test.js:160:19:160:42 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| openai_test.js:160:36:160:42 | persona | semmle.label | persona | +| openai_test.js:166:30:166:58 | "Also t ... persona | semmle.label | "Also t ... persona | +| openai_test.js:166:52:166:58 | persona | semmle.label | persona | +| openai_test.js:172:14:172:37 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| openai_test.js:172:31:172:37 | persona | semmle.label | persona | +| openai_test.js:187:13:187:41 | "Transc ... persona | semmle.label | "Transc ... persona | +| openai_test.js:187:35:187:41 | persona | semmle.label | persona | +| openai_test.js:194:13:194:40 | "Transl ... persona | semmle.label | "Transl ... persona | +| openai_test.js:194:34:194:40 | persona | semmle.label | persona | +| openai_test.js:200:32:200:55 | "Talk l ... persona | semmle.label | "Talk l ... persona | +| openai_test.js:200:49:200:55 | persona | semmle.label | persona | +subpaths +#select +| agents_test.js:16:19:16:42 | "Talk l ... persona | agents_test.js:8:19:8:35 | req.query.persona | agents_test.js:16:19:16:42 | "Talk l ... persona | This prompt construction depends on a $@. | agents_test.js:8:19:8:35 | req.query.persona | user-provided value | +| agents_test.js:25:14:25:37 | "Talk l ... persona | agents_test.js:8:19:8:35 | req.query.persona | agents_test.js:25:14:25:37 | "Talk l ... persona | This prompt construction depends on a $@. | agents_test.js:8:19:8:35 | req.query.persona | user-provided value | +| agents_test.js:32:19:34:5 | return of method instructions | agents_test.js:8:19:8:35 | req.query.persona | agents_test.js:32:19:34:5 | return of method instructions | This prompt construction depends on a $@. | agents_test.js:8:19:8:35 | req.query.persona | user-provided value | +| agents_test.js:43:25:43:44 | "Handles " + persona | agents_test.js:8:19:8:35 | req.query.persona | agents_test.js:43:25:43:44 | "Handles " + persona | This prompt construction depends on a $@. | agents_test.js:8:19:8:35 | req.query.persona | user-provided value | +| agents_test.js:51:22:51:43 | "Ask ab ... persona | agents_test.js:8:19:8:35 | req.query.persona | agents_test.js:51:22:51:43 | "Ask ab ... persona | This prompt construction depends on a $@. | agents_test.js:8:19:8:35 | req.query.persona | user-provided value | +| agents_test.js:59:18:59:48 | "Look u ... persona | agents_test.js:8:19:8:35 | req.query.persona | agents_test.js:59:18:59:48 | "Look u ... persona | This prompt construction depends on a $@. | agents_test.js:8:19:8:35 | req.query.persona | user-provided value | +| agents_test.js:67:32:67:36 | query | agents_test.js:9:17:9:31 | req.query.query | agents_test.js:67:32:67:36 | query | This prompt construction depends on a $@. | agents_test.js:9:17:9:31 | req.query.query | user-provided value | +| agents_test.js:73:32:73:55 | "Talk l ... persona | agents_test.js:8:19:8:35 | req.query.persona | agents_test.js:73:32:73:55 | "Talk l ... persona | This prompt construction depends on a $@. | agents_test.js:8:19:8:35 | req.query.persona | user-provided value | +| agents_test.js:81:35:81:58 | "Talk l ... persona | agents_test.js:8:19:8:35 | req.query.persona | agents_test.js:81:35:81:58 | "Talk l ... persona | This prompt construction depends on a $@. | agents_test.js:8:19:8:35 | req.query.persona | user-provided value | +| agents_test.js:96:32:96:55 | "Talk l ... persona | agents_test.js:8:19:8:35 | req.query.persona | agents_test.js:96:32:96:55 | "Talk l ... persona | This prompt construction depends on a $@. | agents_test.js:8:19:8:35 | req.query.persona | user-provided value | +| anthropic_test.js:17:13:17:36 | "Talk l ... persona | anthropic_test.js:8:19:8:35 | req.query.persona | anthropic_test.js:17:13:17:36 | "Talk l ... persona | This prompt construction depends on a $@. | anthropic_test.js:8:19:8:35 | req.query.persona | user-provided value | +| anthropic_test.js:30:15:30:38 | "Talk l ... persona | anthropic_test.js:8:19:8:35 | req.query.persona | anthropic_test.js:30:15:30:38 | "Talk l ... persona | This prompt construction depends on a $@. | anthropic_test.js:8:19:8:35 | req.query.persona | user-provided value | +| anthropic_test.js:45:18:45:41 | "Talk l ... persona | anthropic_test.js:8:19:8:35 | req.query.persona | anthropic_test.js:45:18:45:41 | "Talk l ... persona | This prompt construction depends on a $@. | anthropic_test.js:8:19:8:35 | req.query.persona | user-provided value | +| anthropic_test.js:71:13:71:36 | "Talk l ... persona | anthropic_test.js:8:19:8:35 | req.query.persona | anthropic_test.js:71:13:71:36 | "Talk l ... persona | This prompt construction depends on a $@. | anthropic_test.js:8:19:8:35 | req.query.persona | user-provided value | +| anthropic_test.js:84:15:84:38 | "Talk l ... persona | anthropic_test.js:8:19:8:35 | req.query.persona | anthropic_test.js:84:15:84:38 | "Talk l ... persona | This prompt construction depends on a $@. | anthropic_test.js:8:19:8:35 | req.query.persona | user-provided value | +| anthropic_test.js:99:18:99:41 | "Talk l ... persona | anthropic_test.js:8:19:8:35 | req.query.persona | anthropic_test.js:99:18:99:41 | "Talk l ... persona | This prompt construction depends on a $@. | anthropic_test.js:8:19:8:35 | req.query.persona | user-provided value | +| anthropic_test.js:110:13:110:36 | "Talk l ... persona | anthropic_test.js:8:19:8:35 | req.query.persona | anthropic_test.js:110:13:110:36 | "Talk l ... persona | This prompt construction depends on a $@. | anthropic_test.js:8:19:8:35 | req.query.persona | user-provided value | +| anthropic_test.js:117:13:117:36 | "Talk l ... persona | anthropic_test.js:8:19:8:35 | req.query.persona | anthropic_test.js:117:13:117:36 | "Talk l ... persona | This prompt construction depends on a $@. | anthropic_test.js:8:19:8:35 | req.query.persona | user-provided value | +| gemini_test.js:18:26:18:49 | "Talk l ... persona | gemini_test.js:8:19:8:35 | req.query.persona | gemini_test.js:18:26:18:49 | "Talk l ... persona | This prompt construction depends on a $@. | gemini_test.js:8:19:8:35 | req.query.persona | user-provided value | +| gemini_test.js:30:25:30:48 | "Talk l ... persona | gemini_test.js:8:19:8:35 | req.query.persona | gemini_test.js:30:25:30:48 | "Talk l ... persona | This prompt construction depends on a $@. | gemini_test.js:8:19:8:35 | req.query.persona | user-provided value | +| gemini_test.js:59:26:59:49 | "Talk l ... persona | gemini_test.js:8:19:8:35 | req.query.persona | gemini_test.js:59:26:59:49 | "Talk l ... persona | This prompt construction depends on a $@. | gemini_test.js:8:19:8:35 | req.query.persona | user-provided value | +| gemini_test.js:68:13:68:42 | "Draw a ... persona | gemini_test.js:8:19:8:35 | req.query.persona | gemini_test.js:68:13:68:42 | "Draw a ... persona | This prompt construction depends on a $@. | gemini_test.js:8:19:8:35 | req.query.persona | user-provided value | +| gemini_test.js:76:13:76:42 | "Edit t ... persona | gemini_test.js:8:19:8:35 | req.query.persona | gemini_test.js:76:13:76:42 | "Edit t ... persona | This prompt construction depends on a $@. | gemini_test.js:8:19:8:35 | req.query.persona | user-provided value | +| gemini_test.js:85:26:85:49 | "Talk l ... persona | gemini_test.js:8:19:8:35 | req.query.persona | gemini_test.js:85:26:85:49 | "Talk l ... persona | This prompt construction depends on a $@. | gemini_test.js:8:19:8:35 | req.query.persona | user-provided value | +| gemini_test.js:95:26:95:49 | "Talk l ... persona | gemini_test.js:8:19:8:35 | req.query.persona | gemini_test.js:95:26:95:49 | "Talk l ... persona | This prompt construction depends on a $@. | gemini_test.js:8:19:8:35 | req.query.persona | user-provided value | +| gemini_test.js:105:26:105:49 | "Talk l ... persona | gemini_test.js:8:19:8:35 | req.query.persona | gemini_test.js:105:26:105:49 | "Talk l ... persona | This prompt construction depends on a $@. | gemini_test.js:8:19:8:35 | req.query.persona | user-provided value | +| openai_test.js:19:19:19:42 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:19:19:19:42 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:29:18:29:41 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:29:18:29:41 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:44:18:44:41 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:44:18:44:41 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:68:18:68:41 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:68:18:68:41 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:83:18:83:41 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:83:18:83:41 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:97:19:97:42 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:97:19:97:42 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:110:18:110:41 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:110:18:110:41 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:120:13:120:36 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:120:13:120:36 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:127:13:127:42 | "Draw a ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:127:13:127:42 | "Draw a ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:132:13:132:42 | "Edit t ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:132:13:132:42 | "Edit t ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:140:12:140:35 | "Embed ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:140:12:140:35 | "Embed ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:149:19:149:42 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:149:19:149:42 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:160:19:160:42 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:160:19:160:42 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:166:30:166:58 | "Also t ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:166:30:166:58 | "Also t ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:172:14:172:37 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:172:14:172:37 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:187:13:187:41 | "Transc ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:187:13:187:41 | "Transc ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:194:13:194:40 | "Transl ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:194:13:194:40 | "Transl ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | +| openai_test.js:200:32:200:55 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:200:32:200:55 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value | diff --git a/javascript/ql/test/experimental/Security/CWE-1427/PromptInjection.qlref b/javascript/ql/test/experimental/Security/CWE-1427/PromptInjection.qlref new file mode 100644 index 000000000000..317f26764f22 --- /dev/null +++ b/javascript/ql/test/experimental/Security/CWE-1427/PromptInjection.qlref @@ -0,0 +1 @@ +./experimental/Security/CWE-1427/PromptInjection.ql diff --git a/javascript/ql/test/experimental/Security/CWE-1427/agents_test.js b/javascript/ql/test/experimental/Security/CWE-1427/agents_test.js new file mode 100644 index 000000000000..ce988bcfa11e --- /dev/null +++ b/javascript/ql/test/experimental/Security/CWE-1427/agents_test.js @@ -0,0 +1,110 @@ +const express = require("express"); +const { Agent, run, Runner, tool } = require("@openai/agents"); +const { z } = require("zod"); + +const app = express(); + +app.get("/agents", async (req, res) => { + const persona = req.query.persona; + const query = req.query.query; + + // === Agent constructor: instructions as string === + + // SHOULD ALERT + const agent1 = new Agent({ + name: "Assistant", + instructions: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }); + + // === Agent constructor: instructions as lambda === + + // SHOULD ALERT + const agent2 = new Agent({ + name: "Dynamic", + instructions: (runContext) => { + return "Talk like a " + persona; // $ Alert[js/prompt-injection] + }, + }); + + // SHOULD ALERT (async lambda) + const agent3 = new Agent({ + name: "AsyncDynamic", + instructions: async (runContext) => { + return "Talk like a " + persona; // $ Alert[js/prompt-injection] + }, + }); + + // === Agent constructor: handoffDescription === + + // SHOULD ALERT + const agent4 = new Agent({ + name: "Specialist", + instructions: "Help with refunds", + handoffDescription: "Handles " + persona, // $ Alert[js/prompt-injection] + }); + + // === agent.asTool(): toolDescription === + + // SHOULD ALERT + agent1.asTool({ + toolName: "helper", + toolDescription: "Ask about " + persona, // $ Alert[js/prompt-injection] + }); + + // === tool(): description === + + // SHOULD ALERT + const myTool = tool({ + name: "lookup", + description: "Look up info about " + persona, // $ Alert[js/prompt-injection] + parameters: z.object({ query: z.string() }), + execute: async ({ query }) => "result", + }); + + // === run() with string input === + + // SHOULD ALERT - string input to run() is used as a prompt + const r1 = await run(agent1, query); // $ Alert[js/prompt-injection] + + // === run() with array input: system role === + + // SHOULD ALERT + const r2 = await run(agent1, [ + { role: "system", content: "Talk like a " + persona }, // $ Alert[js/prompt-injection] + { role: "user", content: query }, + ]); + + // === run() with array input: developer role === + + // SHOULD ALERT + const r3 = await run(agent1, [ + { role: "developer", content: "Talk like a " + persona }, // $ Alert[js/prompt-injection] + ]); + + // === run() with array input: user role === + + // SHOULD NOT ALERT + const r4 = await run(agent1, [ + { role: "user", content: query }, // OK - user role + ]); + + // === Runner instance: run() with system role === + + // SHOULD ALERT + const runner = new Runner(); + const r5 = await runner.run(agent1, [ + { role: "system", content: "Talk like a " + persona }, // $ Alert[js/prompt-injection] + ]); + + // === Sanitizer: constant comparison === + + // SHOULD NOT ALERT + if (persona === "pirate") { + const agent5 = new Agent({ + name: "Pirate", + instructions: "Talk like a " + persona, // OK - sanitized by constant check + }); + } + + res.send("done"); +}); diff --git a/javascript/ql/test/experimental/Security/CWE-1427/anthropic_test.js b/javascript/ql/test/experimental/Security/CWE-1427/anthropic_test.js new file mode 100644 index 000000000000..656179601f8d --- /dev/null +++ b/javascript/ql/test/experimental/Security/CWE-1427/anthropic_test.js @@ -0,0 +1,133 @@ +const express = require("express"); +const Anthropic = require("@anthropic-ai/sdk"); + +const app = express(); +const client = new Anthropic(); + +app.get("/test", async (req, res) => { + const persona = req.query.persona; + const query = req.query.query; + + // === messages.create: system as string === + + // SHOULD ALERT + const m1 = await client.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 1024, + system: "Talk like a " + persona, // $ Alert[js/prompt-injection] + messages: [{ role: "user", content: query }], + }); + + // === messages.create: system as TextBlockParam array === + + // SHOULD ALERT + const m2 = await client.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 1024, + system: [ + { + type: "text", + text: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + ], + messages: [{ role: "user", content: query }], + }); + + // === messages.create: assistant role content === + + // SHOULD ALERT + const m3 = await client.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 1024, + messages: [ + { + role: "assistant", + content: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + { role: "user", content: query }, + ], + }); + + // === messages.create: user role content === + + // SHOULD NOT ALERT + const m4 = await client.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 1024, + messages: [ + { + role: "user", + content: query, // OK - user role + }, + ], + }); + + // === beta.messages.create: system as string === + + // SHOULD ALERT + const bm1 = await client.beta.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 1024, + system: "Talk like a " + persona, // $ Alert[js/prompt-injection] + messages: [{ role: "user", content: query }], + }); + + // === beta.messages.create: system as TextBlockParam array === + + // SHOULD ALERT + const bm2 = await client.beta.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 1024, + system: [ + { + type: "text", + text: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + ], + messages: [{ role: "user", content: query }], + }); + + // === beta.messages.create: assistant role content === + + // SHOULD ALERT + const bm3 = await client.beta.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 1024, + messages: [ + { + role: "assistant", + content: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + { role: "user", content: query }, + ], + }); + + // === beta.agents.create: system === + + // SHOULD ALERT + const ba1 = await client.beta.agents.create({ + model: "claude-sonnet-4-20250514", + system: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }); + + // === beta.agents.update: system === + + // SHOULD ALERT + await client.beta.agents.update("agent_123", { + system: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }); + + // === Sanitizer: constant comparison === + + // SHOULD NOT ALERT + if (persona === "pirate") { + const m5 = await client.messages.create({ + model: "claude-sonnet-4-20250514", + max_tokens: 1024, + system: "Talk like a " + persona, // OK - sanitized by constant check + messages: [{ role: "user", content: query }], + }); + } + + res.send("done"); +}); diff --git a/javascript/ql/test/experimental/Security/CWE-1427/gemini_test.js b/javascript/ql/test/experimental/Security/CWE-1427/gemini_test.js new file mode 100644 index 000000000000..a3858858e132 --- /dev/null +++ b/javascript/ql/test/experimental/Security/CWE-1427/gemini_test.js @@ -0,0 +1,126 @@ +const express = require("express"); +const { GoogleGenAI } = require("@google/genai"); + +const app = express(); +const ai = new GoogleGenAI({ apiKey: "test-key" }); + +app.get("/test", async (req, res) => { + const persona = req.query.persona; + const query = req.query.query; + + // === generateContent: systemInstruction === + + // SHOULD ALERT + const g1 = await ai.models.generateContent({ + model: "gemini-2.0-flash", + contents: "Hello", + config: { + systemInstruction: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + }); + + // === generateContent: contents with model role === + + // SHOULD ALERT + const g2 = await ai.models.generateContent({ + model: "gemini-2.0-flash", + contents: [ + { + role: "model", + parts: [{ text: "Talk like a " + persona }], // $ Alert[js/prompt-injection] + }, + { + role: "user", + parts: [{ text: query }], + }, + ], + }); + + // === generateContent: contents with user role === + + // SHOULD NOT ALERT + const g3 = await ai.models.generateContent({ + model: "gemini-2.0-flash", + contents: [ + { + role: "user", + parts: [{ text: query }], // OK - user role + }, + ], + }); + + // === generateContentStream: systemInstruction === + + // SHOULD ALERT + const g4 = await ai.models.generateContentStream({ + model: "gemini-2.0-flash", + contents: "Hello", + config: { + systemInstruction: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + }); + + // === generateImages: prompt === + + // SHOULD ALERT + const g5 = await ai.models.generateImages({ + model: "imagen-3.0-generate-002", + prompt: "Draw a picture of " + persona, // $ Alert[js/prompt-injection] + }); + + // === editImage: prompt === + + // SHOULD ALERT + const g6 = await ai.models.editImage({ + model: "imagen-3.0-capability-001", + prompt: "Edit to look like " + persona, // $ Alert[js/prompt-injection] + }); + + // === chats.create: systemInstruction === + + // SHOULD ALERT + const chat = ai.chats.create({ + model: "gemini-2.0-flash", + config: { + systemInstruction: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + }); + + // === chat.sendMessage: per-request systemInstruction === + + // SHOULD ALERT + await chat.sendMessage({ + message: query, + config: { + systemInstruction: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + }); + + // === live.connect: systemInstruction === + + // SHOULD ALERT + const session = await ai.live.connect({ + model: "gemini-2.0-flash-live-001", + config: { + systemInstruction: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + callbacks: { + onmessage: (msg) => {}, + }, + }); + + // === Sanitizer: constant comparison === + + // SHOULD NOT ALERT + if (persona === "pirate") { + const g7 = await ai.models.generateContent({ + model: "gemini-2.0-flash", + contents: "Hello", + config: { + systemInstruction: "Talk like a " + persona, // OK - sanitized by constant check + }, + }); + } + + res.send("done"); +}); diff --git a/javascript/ql/test/experimental/Security/CWE-1427/openai_test.js b/javascript/ql/test/experimental/Security/CWE-1427/openai_test.js new file mode 100644 index 000000000000..fcf7096b0753 --- /dev/null +++ b/javascript/ql/test/experimental/Security/CWE-1427/openai_test.js @@ -0,0 +1,215 @@ +const express = require("express"); +const OpenAI = require("openai"); +const { AzureOpenAI } = require("openai"); +const { Agent, run, Runner, tool } = require("@openai/agents"); + +const app = express(); +const client = new OpenAI(); +const azureClient = new AzureOpenAI(); + +app.get("/test", async (req, res) => { + const persona = req.query.persona; + const query = req.query.query; + + // === OpenAI Responses API === + + // instructions: tainted string (SHOULD ALERT) + const r1 = await client.responses.create({ + model: "gpt-4.1", + instructions: "Talk like a " + persona, // $ Alert[js/prompt-injection] + input: "Hello", + }); + + // input as array with system role (SHOULD ALERT) + const r2 = await client.responses.create({ + model: "gpt-4.1", + input: [ + { + role: "system", + content: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + { + role: "user", + content: query, // OK - user role + }, + ], + }); + + // input as array with developer role (SHOULD ALERT) + const r3 = await client.responses.create({ + model: "gpt-4.1", + input: [ + { + role: "developer", + content: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + ], + }); + + // input as array with user role (SHOULD NOT ALERT) + const r4 = await client.responses.create({ + model: "gpt-4.1", + input: [ + { + role: "user", + content: query, // OK - user role is expected to carry user input + }, + ], + }); + + // === Chat Completions API === + + // messages with system role (SHOULD ALERT) + const c1 = await client.chat.completions.create({ + model: "gpt-4.1", + messages: [ + { + role: "system", + content: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + { + role: "user", + content: query, // OK - user role + }, + ], + }); + + // messages with developer role (SHOULD ALERT) + const c2 = await client.chat.completions.create({ + model: "gpt-4.1", + messages: [ + { + role: "developer", + content: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + ], + }); + + // messages with content as array of content parts (SHOULD ALERT) + const c3 = await client.chat.completions.create({ + model: "gpt-4.1", + messages: [ + { + role: "system", + content: [ + { + type: "text", + text: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + ], + }, + ], + }); + + // Azure client (SHOULD ALERT) + const c4 = await azureClient.chat.completions.create({ + model: "gpt-4.1", + messages: [ + { + role: "developer", + content: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }, + ], + }); + + // === Legacy Completions API === + + // prompt (SHOULD ALERT) + const l1 = await client.completions.create({ + model: "gpt-3.5-turbo-instruct", + prompt: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }); + + // === Images API === + + // images.generate (SHOULD ALERT) + const i1 = await client.images.generate({ + prompt: "Draw a picture of " + persona, // $ Alert[js/prompt-injection] + }); + + // images.edit (SHOULD ALERT) + const i2 = await client.images.edit({ + prompt: "Edit to look like " + persona, // $ Alert[js/prompt-injection] + }); + + // === Embeddings API === + + // embeddings.create (SHOULD ALERT) + const e1 = await client.embeddings.create({ + model: "text-embedding-3-small", + input: "Embed this: " + persona, // $ Alert[js/prompt-injection] + }); + + // === Assistants API (beta) === + + // assistants.create (SHOULD ALERT) + const a1 = await client.beta.assistants.create({ + name: "Test Agent", + model: "gpt-4.1", + instructions: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }); + + // assistants.update (SHOULD ALERT) + await client.beta.assistants.update("asst_123", { + instructions: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }); + + // threads.runs.create (SHOULD ALERT) + const tr1 = await client.beta.threads.runs.create("thread_123", { + assistant_id: "asst_123", + instructions: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }); + + // threads.runs.create with additional_instructions (SHOULD ALERT) + const tr2 = await client.beta.threads.runs.create("thread_123", { + assistant_id: "asst_123", + additional_instructions: "Also talk like a " + persona, // $ Alert[js/prompt-injection] + }); + + // threads.messages.create with system role (SHOULD ALERT) + await client.beta.threads.messages.create("thread_123", { + role: "system", + content: "Talk like a " + persona, // $ Alert[js/prompt-injection] + }); + + // threads.messages.create with user role (SHOULD NOT ALERT) + await client.beta.threads.messages.create("thread_123", { + role: "user", + content: query, // OK - user role + }); + + // === Audio API === + + // audio.transcriptions.create (SHOULD ALERT) + const at1 = await client.audio.transcriptions.create({ + file: "audio.mp3", + model: "whisper-1", + prompt: "Transcribe about " + persona, // $ Alert[js/prompt-injection] + }); + + // audio.translations.create (SHOULD ALERT) + const atl1 = await client.audio.translations.create({ + file: "audio.mp3", + model: "whisper-1", + prompt: "Translate about " + persona, // $ Alert[js/prompt-injection] + }); + + // === Object assigned to variable first === + + // Should still be caught via data flow + const opts = { instructions: "Talk like a " + persona }; // $ Alert[js/prompt-injection] + const r5 = await client.responses.create(opts); + + // === Sanitizer: constant comparison === + + // Should NOT alert - guarded by constant comparison + if (persona === "pirate") { + const r6 = await client.responses.create({ + model: "gpt-4.1", + instructions: "Talk like a " + persona, // OK - sanitized by constant check + input: "Hello", + }); + } + + res.send("done"); +});