Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
f00351c1
Unverified
Commit
f00351c1
authored
Apr 01, 2026
by
YanzheL
Browse files
fix(openai): sanitize empty base64 input images
parent
936fce68
Changes
2
Show whitespace changes
Inline
Side-by-side
backend/internal/service/openai_gateway_service.go
View file @
f00351c1
...
...
@@ -1931,6 +1931,11 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
}
}
if
sanitizeEmptyBase64InputImagesInOpenAIRequestBodyMap
(
reqBody
)
{
bodyModified
=
true
disablePatch
()
}
// Re-serialize body only if modified
if
bodyModified
{
serializedByPatch
:=
false
...
...
@@ -2358,6 +2363,14 @@ func (s *OpenAIGatewayService) forwardOpenAIPassthrough(
reqStream
=
gjson
.
GetBytes
(
body
,
"stream"
)
.
Bool
()
}
sanitizedBody
,
sanitized
,
err
:=
sanitizeEmptyBase64InputImagesInOpenAIBody
(
body
)
if
err
!=
nil
{
return
nil
,
err
}
if
sanitized
{
body
=
sanitizedBody
}
logger
.
LegacyPrintf
(
"service.openai_gateway"
,
"[OpenAI 自动透传] 命中自动透传分支: account=%d name=%s type=%s model=%s stream=%v"
,
account
.
ID
,
...
...
@@ -4691,6 +4704,123 @@ func normalizeOpenAIServiceTier(raw string) *string {
}
}
func
sanitizeEmptyBase64InputImagesInOpenAIBody
(
body
[]
byte
)
([]
byte
,
bool
,
error
)
{
if
len
(
body
)
==
0
||
!
bytes
.
Contains
(
body
,
[]
byte
(
`"image_url"`
))
||
!
bytes
.
Contains
(
body
,
[]
byte
(
`base64,`
))
{
return
body
,
false
,
nil
}
var
reqBody
map
[
string
]
any
if
err
:=
json
.
Unmarshal
(
body
,
&
reqBody
);
err
!=
nil
{
return
body
,
false
,
fmt
.
Errorf
(
"sanitize request body: %w"
,
err
)
}
if
!
sanitizeEmptyBase64InputImagesInOpenAIRequestBodyMap
(
reqBody
)
{
return
body
,
false
,
nil
}
normalized
,
err
:=
json
.
Marshal
(
reqBody
)
if
err
!=
nil
{
return
body
,
false
,
fmt
.
Errorf
(
"serialize sanitized request body: %w"
,
err
)
}
return
normalized
,
true
,
nil
}
func
sanitizeEmptyBase64InputImagesInOpenAIRequestBodyMap
(
reqBody
map
[
string
]
any
)
bool
{
if
reqBody
==
nil
{
return
false
}
input
,
ok
:=
reqBody
[
"input"
]
if
!
ok
{
return
false
}
normalizedInput
,
changed
:=
sanitizeEmptyBase64InputImagesInOpenAIInput
(
input
)
if
!
changed
{
return
false
}
reqBody
[
"input"
]
=
normalizedInput
return
true
}
func
sanitizeEmptyBase64InputImagesInOpenAIInput
(
input
any
)
(
any
,
bool
)
{
items
,
ok
:=
input
.
([]
any
)
if
!
ok
{
return
input
,
false
}
normalizedItems
:=
make
([]
any
,
0
,
len
(
items
))
changed
:=
false
for
_
,
item
:=
range
items
{
itemMap
,
ok
:=
item
.
(
map
[
string
]
any
)
if
!
ok
{
normalizedItems
=
append
(
normalizedItems
,
item
)
continue
}
if
shouldDropEmptyBase64InputImagePart
(
itemMap
)
{
changed
=
true
continue
}
content
,
ok
:=
itemMap
[
"content"
]
if
!
ok
{
normalizedItems
=
append
(
normalizedItems
,
itemMap
)
continue
}
parts
,
ok
:=
content
.
([]
any
)
if
!
ok
{
normalizedItems
=
append
(
normalizedItems
,
itemMap
)
continue
}
normalizedParts
:=
make
([]
any
,
0
,
len
(
parts
))
itemChanged
:=
false
for
_
,
part
:=
range
parts
{
if
shouldDropEmptyBase64InputImagePart
(
part
)
{
changed
=
true
itemChanged
=
true
continue
}
normalizedParts
=
append
(
normalizedParts
,
part
)
}
if
itemChanged
{
if
len
(
normalizedParts
)
==
0
{
continue
}
itemMap
[
"content"
]
=
normalizedParts
}
normalizedItems
=
append
(
normalizedItems
,
itemMap
)
}
if
!
changed
{
return
input
,
false
}
return
normalizedItems
,
true
}
func
shouldDropEmptyBase64InputImagePart
(
part
any
)
bool
{
partMap
,
ok
:=
part
.
(
map
[
string
]
any
)
if
!
ok
{
return
false
}
typeValue
,
_
:=
partMap
[
"type"
]
.
(
string
)
if
strings
.
TrimSpace
(
typeValue
)
!=
"input_image"
{
return
false
}
imageURL
,
_
:=
partMap
[
"image_url"
]
.
(
string
)
return
isEmptyBase64DataURI
(
imageURL
)
}
func
isEmptyBase64DataURI
(
raw
string
)
bool
{
if
!
strings
.
HasPrefix
(
raw
,
"data:"
)
{
return
false
}
rest
:=
strings
.
TrimPrefix
(
raw
,
"data:"
)
semicolonIdx
:=
strings
.
Index
(
rest
,
";"
)
if
semicolonIdx
<
0
{
return
false
}
rest
=
rest
[
semicolonIdx
+
1
:
]
if
!
strings
.
HasPrefix
(
rest
,
"base64,"
)
{
return
false
}
return
strings
.
TrimSpace
(
strings
.
TrimPrefix
(
rest
,
"base64,"
))
==
""
}
func
getOpenAIRequestBodyMap
(
c
*
gin
.
Context
,
body
[]
byte
)
(
map
[
string
]
any
,
error
)
{
if
c
!=
nil
{
if
cached
,
ok
:=
c
.
Get
(
OpenAIParsedRequestBodyKey
);
ok
{
...
...
backend/internal/service/openai_gateway_service_hotpath_test.go
View file @
f00351c1
package
service
import
(
"encoding/json"
"net/http/httptest"
"testing"
...
...
@@ -139,3 +140,61 @@ func TestGetOpenAIRequestBodyMap_WriteBackContextCache(t *testing.T) {
require
.
True
(
t
,
ok
)
require
.
Equal
(
t
,
got
,
cachedMap
)
}
func
TestSanitizeEmptyBase64InputImagesInOpenAIRequestBodyMap
(
t
*
testing
.
T
)
{
var
reqBody
map
[
string
]
any
require
.
NoError
(
t
,
json
.
Unmarshal
([]
byte
(
`{
"model":"gpt-5.4",
"input":[
{"role":"user","content":[
{"type":"input_text","text":"Describe this"},
{"type":"input_image","image_url":"data:image/png;base64, "},
{"type":"input_image","image_url":"data:image/png;base64,abc123"}
]},
{"role":"user","content":[
{"type":"input_image","image_url":"data:image/png;base64,"}
]},
{"type":"input_image","image_url":"data:image/png;base64,"},
{"type":"input_image","image_url":"data:image/png;base64,top-level-valid"}
]
}`
),
&
reqBody
))
require
.
True
(
t
,
sanitizeEmptyBase64InputImagesInOpenAIRequestBodyMap
(
reqBody
))
normalized
,
err
:=
json
.
Marshal
(
reqBody
)
require
.
NoError
(
t
,
err
)
require
.
JSONEq
(
t
,
`{
"model":"gpt-5.4",
"input":[
{"role":"user","content":[
{"type":"input_text","text":"Describe this"},
{"type":"input_image","image_url":"data:image/png;base64,abc123"}
]},
{"type":"input_image","image_url":"data:image/png;base64,top-level-valid"}
]
}`
,
string
(
normalized
))
}
func
TestSanitizeEmptyBase64InputImagesInOpenAIBody
(
t
*
testing
.
T
)
{
body
,
changed
,
err
:=
sanitizeEmptyBase64InputImagesInOpenAIBody
([]
byte
(
`{
"model":"gpt-5.4",
"stream":true,
"input":[
{"role":"user","content":[
{"type":"input_text","text":"Describe this"},
{"type":"input_image","image_url":"data:image/png;base64,"}
]}
]
}`
))
require
.
NoError
(
t
,
err
)
require
.
True
(
t
,
changed
)
require
.
JSONEq
(
t
,
`{
"model":"gpt-5.4",
"stream":true,
"input":[
{"role":"user","content":[
{"type":"input_text","text":"Describe this"}
]}
]
}`
,
string
(
body
))
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment