mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-22 06:40:21 +08:00
Merge pull request #3794 from fengshao1227/fix/image-gen-namespace-permission-bypass
fix(image): 识别 Codex namespace image_gen 工具声明以拦截生图请求
This commit is contained in:
@@ -39,6 +39,9 @@ func IsImageGenerationIntent(endpoint string, requestedModel string, body []byte
|
||||
if openAIJSONToolsContainImageGeneration(gjson.GetBytes(body, "tools")) {
|
||||
return true
|
||||
}
|
||||
if openAIJSONInputContainsImageGenTool(gjson.GetBytes(body, "input")) {
|
||||
return true
|
||||
}
|
||||
return openAIJSONToolChoiceSelectsImageGeneration(gjson.GetBytes(body, "tool_choice"))
|
||||
}
|
||||
|
||||
@@ -94,11 +97,52 @@ func openAIJSONToolsContainImageGeneration(tools gjson.Result) bool {
|
||||
found = true
|
||||
return false
|
||||
}
|
||||
if isImageGenNamespaceTool(item) {
|
||||
found = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
// isImageGenNamespaceTool detects the Codex namespace-style image generation
|
||||
// tool declaration: { "type": "namespace", "name": "image_gen", ... }.
|
||||
// Codex /image uses this instead of the flat { "type": "image_generation" }.
|
||||
func isImageGenNamespaceTool(tool gjson.Result) bool {
|
||||
return openAIJSONString(tool.Get("type")) == "namespace" &&
|
||||
openAIJSONString(tool.Get("name")) == "image_gen"
|
||||
}
|
||||
|
||||
// openAIJSONInputContainsImageGenTool scans Responses input items for
|
||||
// additional_tools entries that declare the image_gen namespace. This covers
|
||||
// the "Responses Lite" format where tools are embedded inside input items
|
||||
// rather than top-level tools.
|
||||
func openAIJSONInputContainsImageGenTool(input gjson.Result) bool {
|
||||
if !input.IsArray() {
|
||||
return false
|
||||
}
|
||||
found := false
|
||||
input.ForEach(func(_, item gjson.Result) bool {
|
||||
if openAIJSONString(item.Get("type")) != "additional_tools" {
|
||||
return true
|
||||
}
|
||||
tools := item.Get("tools")
|
||||
if !tools.IsArray() {
|
||||
return true
|
||||
}
|
||||
tools.ForEach(func(_, tool gjson.Result) bool {
|
||||
if isImageGenNamespaceTool(tool) {
|
||||
found = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return !found
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
func openAIRequestBodyHasImageGenerationTool(body []byte) bool {
|
||||
if len(body) == 0 || !gjson.ValidBytes(body) {
|
||||
return false
|
||||
|
||||
@@ -55,6 +55,27 @@ func TestIsImageGenerationIntent(t *testing.T) {
|
||||
body: []byte(`{"model":"gpt-5.4","input":"write code"}`),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "namespace image_gen tool in top-level tools",
|
||||
endpoint: "/v1/responses",
|
||||
model: "gpt-5.5",
|
||||
body: []byte(`{"model":"gpt-5.5","tools":[{"type":"namespace","name":"image_gen","tools":[{"type":"function","name":"imagegen"}]}]}`),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "namespace image_gen in input additional_tools (Responses Lite)",
|
||||
endpoint: "/v1/responses",
|
||||
model: "gpt-5.5",
|
||||
body: []byte(`{"model":"gpt-5.5","input":[{"type":"additional_tools","role":"developer","tools":[{"type":"namespace","name":"image_gen","tools":[{"type":"function","name":"imagegen"}]}]}]}`),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "non-image namespace tool is not flagged",
|
||||
endpoint: "/v1/responses",
|
||||
model: "gpt-5.5",
|
||||
body: []byte(`{"model":"gpt-5.5","tools":[{"type":"namespace","name":"code_tools","tools":[{"type":"function","name":"run"}]}]}`),
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -64,6 +85,58 @@ func TestIsImageGenerationIntent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsImageGenerationIntentMap_NamespaceImageGen(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
reqBody map[string]any
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "top-level namespace image_gen",
|
||||
reqBody: map[string]any{
|
||||
"model": "gpt-5.5",
|
||||
"tools": []any{
|
||||
map[string]any{"type": "namespace", "name": "image_gen", "tools": []any{
|
||||
map[string]any{"type": "function", "name": "imagegen"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "additional_tools in input",
|
||||
reqBody: map[string]any{
|
||||
"model": "gpt-5.5",
|
||||
"input": []any{
|
||||
map[string]any{
|
||||
"type": "additional_tools",
|
||||
"tools": []any{
|
||||
map[string]any{"type": "namespace", "name": "image_gen"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "non-image namespace not flagged",
|
||||
reqBody: map[string]any{
|
||||
"model": "gpt-5.5",
|
||||
"tools": []any{
|
||||
map[string]any{"type": "namespace", "name": "code_tools"},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, IsImageGenerationIntentMap("/v1/responses", "gpt-5.5", tt.reqBody))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveOpenAIResponsesImageBillingConfigUsesCurrentBodyModel(t *testing.T) {
|
||||
imageModel, imageSize, err := resolveOpenAIResponsesImageBillingConfigFromBody(
|
||||
[]byte(`{"model":"mapped-image-model","tools":[{"type":"image_generation","size":"1024x1024"}]}`),
|
||||
|
||||
@@ -593,8 +593,14 @@ func isCodexSparkModel(model string) bool {
|
||||
}
|
||||
|
||||
func hasOpenAIImageGenerationTool(reqBody map[string]any) bool {
|
||||
rawTools, ok := reqBody["tools"]
|
||||
if !ok || rawTools == nil {
|
||||
if toolsContainImageGeneration(reqBody["tools"]) {
|
||||
return true
|
||||
}
|
||||
return inputContainsImageGenNamespace(reqBody["input"])
|
||||
}
|
||||
|
||||
func toolsContainImageGeneration(rawTools any) bool {
|
||||
if rawTools == nil {
|
||||
return false
|
||||
}
|
||||
tools, ok := rawTools.([]any)
|
||||
@@ -609,6 +615,34 @@ func hasOpenAIImageGenerationTool(reqBody map[string]any) bool {
|
||||
if strings.TrimSpace(firstNonEmptyString(toolMap["type"])) == "image_generation" {
|
||||
return true
|
||||
}
|
||||
if isImageGenNamespaceToolMap(toolMap) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isImageGenNamespaceToolMap(tool map[string]any) bool {
|
||||
return strings.TrimSpace(firstNonEmptyString(tool["type"])) == "namespace" &&
|
||||
strings.TrimSpace(firstNonEmptyString(tool["name"])) == "image_gen"
|
||||
}
|
||||
|
||||
func inputContainsImageGenNamespace(rawInput any) bool {
|
||||
input, ok := rawInput.([]any)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
for _, rawItem := range input {
|
||||
item, ok := rawItem.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(firstNonEmptyString(item["type"])) != "additional_tools" {
|
||||
continue
|
||||
}
|
||||
if toolsContainImageGeneration(item["tools"]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user