Skip to content

Commit 535adc7

Browse files
committed
add barrier when data flows into user messages for system prompt detection, remove embeddings from user prompt injection query
1 parent 9c13626 commit 535adc7

11 files changed

Lines changed: 218 additions & 67 deletions

File tree

javascript/ql/lib/ext/google-genai.model.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,4 @@ extensions:
1919
- ["google-genai.Client", "Member[models].Member[generateVideos].Argument[0].Member[prompt]", "user-prompt-injection"]
2020
- ["google-genai.Client", "Member[chats].Member[create].ReturnValue.Member[sendMessage,sendMessageStream].Argument[0].Member[message]", "user-prompt-injection"]
2121
- ["google-genai.Client", "Member[chats].Member[create].ReturnValue.Member[sendMessage,sendMessageStream].Argument[0].Member[content]", "user-prompt-injection"]
22-
- ["google-genai.Client", "Member[models].Member[embedContent].Argument[0].Member[content]", "user-prompt-injection"]
2322
- ["google-genai.Client", "Member[interactions].Member[create].Argument[0].Member[input]", "user-prompt-injection"]

javascript/ql/src/experimental/semmle/javascript/frameworks/Anthropic.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module Anthropic {
3333
// messages: [{ role: "assistant", content: "..." }]
3434
exists(API::Node msg |
3535
msg = messagesCreateParams().getMember("messages").getArrayElement() and
36-
msg.getMember("role").asSink().mayHaveStringValue("assistant")
36+
msg.getMember("role").asSink().mayHaveStringValue(["system", "assistant"])
3737
|
3838
result = msg.getMember("content")
3939
)
@@ -47,7 +47,7 @@ module Anthropic {
4747
// messages: [{ role: "user", content: "..." }]
4848
exists(API::Node msg |
4949
msg = messagesCreateParams().getMember("messages").getArrayElement() and
50-
not msg.getMember("role").asSink().mayHaveStringValue("assistant")
50+
not msg.getMember("role").asSink().mayHaveStringValue(["system", "assistant"])
5151
|
5252
result = msg.getMember("content")
5353
)

javascript/ql/src/experimental/semmle/javascript/frameworks/GoogleGenAI.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module GoogleGenAI {
3333
.getParameter(0)
3434
.getMember("contents")
3535
.getArrayElement() and
36-
msg.getMember("role").asSink().mayHaveStringValue("model")
36+
msg.getMember("role").asSink().mayHaveStringValue(["system", "model"])
3737
|
3838
result = msg.getMember("parts").getArrayElement().getMember("text")
3939
)
@@ -53,7 +53,7 @@ module GoogleGenAI {
5353
.getParameter(0)
5454
.getMember("contents")
5555
.getArrayElement() and
56-
not msg.getMember("role").asSink().mayHaveStringValue("model")
56+
not msg.getMember("role").asSink().mayHaveStringValue(["system", "model"])
5757
|
5858
result = msg.getMember("parts").getArrayElement().getMember("text")
5959
)

javascript/ql/src/experimental/semmle/javascript/frameworks/OpenAI.qll

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,6 @@ module OpenAI {
171171
.getParameter(0)
172172
.getMember("prompt")
173173
or
174-
// embeddings.create({ input: ... })
175-
result =
176-
clientsNoGuardrails()
177-
.getMember("embeddings")
178-
.getMember("create")
179-
.getParameter(0)
180-
.getMember("input")
181-
or
182174
// beta.threads.messages.create(threadId, { role: "user", content: ... })
183175
exists(API::Node msg |
184176
msg =

javascript/ql/src/experimental/semmle/javascript/security/PromptInjection/SystemPromptInjectionCustomizations.qll

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,24 @@ module SystemPromptInjection {
7474
}
7575
}
7676

77+
/**
78+
* Content placed in a message with `role: "user"` is not a system prompt
79+
* injection vector; it is intended user-role content.
80+
*
81+
* This prevents false positives when user input and system prompts are
82+
* combined in the same message array (e.g. `[{role:"system", content: ...},
83+
* {role:"user", content: tainted}]`) and taint would otherwise propagate
84+
* through array operations to the system message.
85+
*/
86+
private class UserRoleMessageContentBarrier extends Sanitizer {
87+
UserRoleMessageContentBarrier() {
88+
exists(DataFlow::SourceNode obj |
89+
obj.getAPropertySource("role").mayHaveStringValue("user") and
90+
this = obj.getAPropertyWrite("content").getRhs()
91+
)
92+
}
93+
}
94+
7795
/**
7896
* A comparison with a constant, considered as a sanitizer-guard.
7997
*/

javascript/ql/test/experimental/Security/CWE-1427/SystemPromptInjection/SystemPromptInjection.expected

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ edges
3333
| anthropic_test.js:8:9:8:15 | persona | anthropic_test.js:99:35:99:41 | persona | provenance | |
3434
| anthropic_test.js:8:9:8:15 | persona | anthropic_test.js:110:30:110:36 | persona | provenance | |
3535
| anthropic_test.js:8:9:8:15 | persona | anthropic_test.js:117:30:117:36 | persona | provenance | |
36+
| anthropic_test.js:8:9:8:15 | persona | anthropic_test.js:141:49:141:55 | persona | provenance | |
3637
| anthropic_test.js:8:19:8:35 | req.query.persona | anthropic_test.js:8:9:8:15 | persona | provenance | |
3738
| anthropic_test.js:17:30:17:36 | persona | anthropic_test.js:17:13:17:36 | "Talk l ... persona | provenance | |
3839
| anthropic_test.js:30:32:30:38 | persona | anthropic_test.js:30:15:30:38 | "Talk l ... persona | provenance | |
@@ -42,6 +43,15 @@ edges
4243
| anthropic_test.js:99:35:99:41 | persona | anthropic_test.js:99:18:99:41 | "Talk l ... persona | provenance | |
4344
| anthropic_test.js:110:30:110:36 | persona | anthropic_test.js:110:13:110:36 | "Talk l ... persona | provenance | |
4445
| anthropic_test.js:117:30:117:36 | persona | anthropic_test.js:117:13:117:36 | "Talk l ... persona | provenance | |
46+
| anthropic_test.js:140:9:140:17 | messages2 [0, content] | anthropic_test.js:144:22:144:30 | messages2 [0, content] | provenance | |
47+
| anthropic_test.js:140:21:143:3 | [\\n { ... },\\n ] [0, content] | anthropic_test.js:140:9:140:17 | messages2 [0, content] | provenance | |
48+
| anthropic_test.js:141:5:141:57 | { role: ... rsona } [content] | anthropic_test.js:140:21:143:3 | [\\n { ... },\\n ] [0, content] | provenance | |
49+
| anthropic_test.js:141:32:141:55 | "Talk l ... persona | anthropic_test.js:141:5:141:57 | { role: ... rsona } [content] | provenance | |
50+
| anthropic_test.js:141:49:141:55 | persona | anthropic_test.js:141:32:141:55 | "Talk l ... persona | provenance | |
51+
| anthropic_test.js:144:9:144:18 | systemMsg2 [content] | anthropic_test.js:148:13:148:22 | systemMsg2 [content] | provenance | |
52+
| anthropic_test.js:144:22:144:30 | messages2 [0, content] | anthropic_test.js:144:22:144:63 | message ... ystem") [content] | provenance | |
53+
| anthropic_test.js:144:22:144:63 | message ... ystem") [content] | anthropic_test.js:144:9:144:18 | systemMsg2 [content] | provenance | |
54+
| anthropic_test.js:148:13:148:22 | systemMsg2 [content] | anthropic_test.js:148:13:148:30 | systemMsg2.content | provenance | |
4555
| gemini_test.js:8:9:8:15 | persona | gemini_test.js:18:43:18:49 | persona | provenance | |
4656
| gemini_test.js:8:9:8:15 | persona | gemini_test.js:30:42:30:48 | persona | provenance | |
4757
| gemini_test.js:8:9:8:15 | persona | gemini_test.js:59:43:59:49 | persona | provenance | |
@@ -62,11 +72,11 @@ edges
6272
| openai_test.js:11:9:11:15 | persona | openai_test.js:83:35:83:41 | persona | provenance | |
6373
| openai_test.js:11:9:11:15 | persona | openai_test.js:97:36:97:42 | persona | provenance | |
6474
| openai_test.js:11:9:11:15 | persona | openai_test.js:110:35:110:41 | persona | provenance | |
65-
| openai_test.js:11:9:11:15 | persona | openai_test.js:149:36:149:42 | persona | provenance | |
66-
| openai_test.js:11:9:11:15 | persona | openai_test.js:160:36:160:42 | persona | provenance | |
67-
| openai_test.js:11:9:11:15 | persona | openai_test.js:166:52:166:58 | persona | provenance | |
68-
| openai_test.js:11:9:11:15 | persona | openai_test.js:172:31:172:37 | persona | provenance | |
69-
| openai_test.js:11:9:11:15 | persona | openai_test.js:200:49:200:55 | persona | provenance | |
75+
| openai_test.js:11:9:11:15 | persona | openai_test.js:141:36:141:42 | persona | provenance | |
76+
| openai_test.js:11:9:11:15 | persona | openai_test.js:152:36:152:42 | persona | provenance | |
77+
| openai_test.js:11:9:11:15 | persona | openai_test.js:158:52:158:58 | persona | provenance | |
78+
| openai_test.js:11:9:11:15 | persona | openai_test.js:164:31:164:37 | persona | provenance | |
79+
| openai_test.js:11:9:11:15 | persona | openai_test.js:192:49:192:55 | persona | provenance | |
7080
| openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:11:9:11:15 | persona | provenance | |
7181
| openai_test.js:19:36:19:42 | persona | openai_test.js:19:19:19:42 | "Talk l ... persona | provenance | |
7282
| openai_test.js:29:35:29:41 | persona | openai_test.js:29:18:29:41 | "Talk l ... persona | provenance | |
@@ -75,11 +85,11 @@ edges
7585
| openai_test.js:83:35:83:41 | persona | openai_test.js:83:18:83:41 | "Talk l ... persona | provenance | |
7686
| openai_test.js:97:36:97:42 | persona | openai_test.js:97:19:97:42 | "Talk l ... persona | provenance | |
7787
| openai_test.js:110:35:110:41 | persona | openai_test.js:110:18:110:41 | "Talk l ... persona | provenance | |
78-
| openai_test.js:149:36:149:42 | persona | openai_test.js:149:19:149:42 | "Talk l ... persona | provenance | |
79-
| openai_test.js:160:36:160:42 | persona | openai_test.js:160:19:160:42 | "Talk l ... persona | provenance | |
80-
| openai_test.js:166:52:166:58 | persona | openai_test.js:166:30:166:58 | "Also t ... persona | provenance | |
81-
| openai_test.js:172:31:172:37 | persona | openai_test.js:172:14:172:37 | "Talk l ... persona | provenance | |
82-
| openai_test.js:200:49:200:55 | persona | openai_test.js:200:32:200:55 | "Talk l ... persona | provenance | |
88+
| openai_test.js:141:36:141:42 | persona | openai_test.js:141:19:141:42 | "Talk l ... persona | provenance | |
89+
| openai_test.js:152:36:152:42 | persona | openai_test.js:152:19:152:42 | "Talk l ... persona | provenance | |
90+
| openai_test.js:158:52:158:58 | persona | openai_test.js:158:30:158:58 | "Also t ... persona | provenance | |
91+
| openai_test.js:164:31:164:37 | persona | openai_test.js:164:14:164:37 | "Talk l ... persona | provenance | |
92+
| openai_test.js:192:49:192:55 | persona | openai_test.js:192:32:192:55 | "Talk l ... persona | provenance | |
8393
nodes
8494
| agents_test.js:8:9:8:15 | persona | semmle.label | persona |
8595
| agents_test.js:8:19:8:35 | req.query.persona | semmle.label | req.query.persona |
@@ -120,6 +130,16 @@ nodes
120130
| anthropic_test.js:110:30:110:36 | persona | semmle.label | persona |
121131
| anthropic_test.js:117:13:117:36 | "Talk l ... persona | semmle.label | "Talk l ... persona |
122132
| anthropic_test.js:117:30:117:36 | persona | semmle.label | persona |
133+
| anthropic_test.js:140:9:140:17 | messages2 [0, content] | semmle.label | messages2 [0, content] |
134+
| anthropic_test.js:140:21:143:3 | [\\n { ... },\\n ] [0, content] | semmle.label | [\\n { ... },\\n ] [0, content] |
135+
| anthropic_test.js:141:5:141:57 | { role: ... rsona } [content] | semmle.label | { role: ... rsona } [content] |
136+
| anthropic_test.js:141:32:141:55 | "Talk l ... persona | semmle.label | "Talk l ... persona |
137+
| anthropic_test.js:141:49:141:55 | persona | semmle.label | persona |
138+
| anthropic_test.js:144:9:144:18 | systemMsg2 [content] | semmle.label | systemMsg2 [content] |
139+
| anthropic_test.js:144:22:144:30 | messages2 [0, content] | semmle.label | messages2 [0, content] |
140+
| anthropic_test.js:144:22:144:63 | message ... ystem") [content] | semmle.label | message ... ystem") [content] |
141+
| anthropic_test.js:148:13:148:22 | systemMsg2 [content] | semmle.label | systemMsg2 [content] |
142+
| anthropic_test.js:148:13:148:30 | systemMsg2.content | semmle.label | systemMsg2.content |
123143
| gemini_test.js:8:9:8:15 | persona | semmle.label | persona |
124144
| gemini_test.js:8:19:8:35 | req.query.persona | semmle.label | req.query.persona |
125145
| gemini_test.js:18:26:18:49 | "Talk l ... persona | semmle.label | "Talk l ... persona |
@@ -150,16 +170,16 @@ nodes
150170
| openai_test.js:97:36:97:42 | persona | semmle.label | persona |
151171
| openai_test.js:110:18:110:41 | "Talk l ... persona | semmle.label | "Talk l ... persona |
152172
| openai_test.js:110:35:110:41 | persona | semmle.label | persona |
153-
| openai_test.js:149:19:149:42 | "Talk l ... persona | semmle.label | "Talk l ... persona |
154-
| openai_test.js:149:36:149:42 | persona | semmle.label | persona |
155-
| openai_test.js:160:19:160:42 | "Talk l ... persona | semmle.label | "Talk l ... persona |
156-
| openai_test.js:160:36:160:42 | persona | semmle.label | persona |
157-
| openai_test.js:166:30:166:58 | "Also t ... persona | semmle.label | "Also t ... persona |
158-
| openai_test.js:166:52:166:58 | persona | semmle.label | persona |
159-
| openai_test.js:172:14:172:37 | "Talk l ... persona | semmle.label | "Talk l ... persona |
160-
| openai_test.js:172:31:172:37 | persona | semmle.label | persona |
161-
| openai_test.js:200:32:200:55 | "Talk l ... persona | semmle.label | "Talk l ... persona |
162-
| openai_test.js:200:49:200:55 | persona | semmle.label | persona |
173+
| openai_test.js:141:19:141:42 | "Talk l ... persona | semmle.label | "Talk l ... persona |
174+
| openai_test.js:141:36:141:42 | persona | semmle.label | persona |
175+
| openai_test.js:152:19:152:42 | "Talk l ... persona | semmle.label | "Talk l ... persona |
176+
| openai_test.js:152:36:152:42 | persona | semmle.label | persona |
177+
| openai_test.js:158:30:158:58 | "Also t ... persona | semmle.label | "Also t ... persona |
178+
| openai_test.js:158:52:158:58 | persona | semmle.label | persona |
179+
| openai_test.js:164:14:164:37 | "Talk l ... persona | semmle.label | "Talk l ... persona |
180+
| openai_test.js:164:31:164:37 | persona | semmle.label | persona |
181+
| openai_test.js:192:32:192:55 | "Talk l ... persona | semmle.label | "Talk l ... persona |
182+
| openai_test.js:192:49:192:55 | persona | semmle.label | persona |
163183
subpaths
164184
#select
165185
| 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 |
@@ -179,6 +199,7 @@ subpaths
179199
| 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 |
180200
| 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 |
181201
| 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 |
202+
| anthropic_test.js:148:13:148:30 | systemMsg2.content | anthropic_test.js:8:19:8:35 | req.query.persona | anthropic_test.js:148:13:148:30 | systemMsg2.content | This prompt construction depends on a $@. | anthropic_test.js:8:19:8:35 | req.query.persona | user-provided value |
182203
| 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 |
183204
| 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 |
184205
| 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 |
@@ -192,8 +213,8 @@ subpaths
192213
| 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 |
193214
| 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 |
194215
| 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 |
195-
| 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 |
196-
| 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 |
197-
| 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 |
198-
| 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 |
199-
| 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 |
216+
| openai_test.js:141:19:141:42 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:141:19:141:42 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value |
217+
| openai_test.js:152:19:152:42 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:152:19:152:42 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value |
218+
| openai_test.js:158:30:158:58 | "Also t ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:158:30:158:58 | "Also t ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value |
219+
| openai_test.js:164:14:164:37 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:164:14:164:37 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value |
220+
| openai_test.js:192:32:192:55 | "Talk l ... persona | openai_test.js:11:19:11:35 | req.query.persona | openai_test.js:192:32:192:55 | "Talk l ... persona | This prompt construction depends on a $@. | openai_test.js:11:19:11:35 | req.query.persona | user-provided value |

javascript/ql/test/experimental/Security/CWE-1427/SystemPromptInjection/anthropic_test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,38 @@ app.get("/test", async (req, res) => {
117117
system: "Talk like a " + persona, // $ Alert[js/prompt-injection]
118118
});
119119

120+
// === Barrier: user-role content in shared message array ===
121+
122+
// SHOULD NOT ALERT — user input placed in { role: "user" } should not
123+
// taint system messages extracted from the same array.
124+
const messages = [
125+
{ role: "system", content: "You are a helpful assistant" },
126+
{ role: "user", content: query }, // OK - user role barrier
127+
];
128+
const systemMsg = messages.find((m) => m.role === "system");
129+
const m6 = await client.messages.create({
130+
model: "claude-sonnet-4-20250514",
131+
max_tokens: 1024,
132+
system: systemMsg.content,
133+
messages: [{ role: "user", content: query }],
134+
});
135+
136+
// === Barrier does NOT suppress: tainted value in system role ===
137+
138+
// SHOULD ALERT — tainted data goes into system role; barrier on user role
139+
// must not suppress the system-role taint path.
140+
const messages2 = [
141+
{ role: "system", content: "Talk like a " + persona }, // $ Alert[js/prompt-injection]
142+
{ role: "user", content: query },
143+
];
144+
const systemMsg2 = messages2.find((m) => m.role === "system");
145+
const m7 = await client.messages.create({
146+
model: "claude-sonnet-4-20250514",
147+
max_tokens: 1024,
148+
system: systemMsg2.content,
149+
messages: [{ role: "user", content: query }],
150+
});
151+
120152
// === Sanitizer: constant comparison ===
121153

122154
// SHOULD NOT ALERT

0 commit comments

Comments
 (0)