sanitize invalid image history

This commit is contained in:
Roy Han
2026-05-20 22:39:19 -07:00
parent 0b4f86095c
commit ffbb4e53fb
8 changed files with 548 additions and 93 deletions
+32 -5
View File
@@ -56,8 +56,8 @@ pub fn map_api_error(err: ApiError) -> CodexErr {
}
if status == http::StatusCode::BAD_REQUEST {
if let Ok(parsed) = serde_json::from_str::<Value>(&body_text)
&& let Some(error) = parsed.get("error")
let parsed = serde_json::from_str::<Value>(&body_text).ok();
if let Some(error) = parsed.as_ref().and_then(|parsed| parsed.get("error"))
&& error.get("code").and_then(Value::as_str)
== Some(CYBER_POLICY_ERROR_CODE)
{
@@ -68,9 +68,7 @@ pub fn map_api_error(err: ApiError) -> CodexErr {
.map(str::to_string)
.unwrap_or_else(|| CYBER_POLICY_FALLBACK_MESSAGE.to_string());
CodexErr::CyberPolicy { message }
} else if body_text
.contains("The image data you provided does not represent a valid image")
{
} else if is_invalid_image_response_error(&body_text, parsed.as_ref()) {
CodexErr::InvalidImageRequest()
} else {
CodexErr::InvalidRequest(body_text)
@@ -141,6 +139,35 @@ const X_ERROR_JSON_HEADER: &str = "x-error-json";
const CYBER_POLICY_ERROR_CODE: &str = "cyber_policy";
const CYBER_POLICY_FALLBACK_MESSAGE: &str =
"This request has been flagged for possible cybersecurity risk.";
const INVALID_IMAGE_ERROR_MESSAGE: &str =
"The image data you provided does not represent a valid image";
const EMPTY_BASE64_IMAGE_ERROR_MESSAGE: &str = concat!(
"Expected a base64-encoded data URL with an image MIME type, ",
"but got empty base64-encoded bytes."
);
fn is_invalid_image_response_error(body_text: &str, parsed: Option<&Value>) -> bool {
if body_text.contains(INVALID_IMAGE_ERROR_MESSAGE)
|| body_text.contains(EMPTY_BASE64_IMAGE_ERROR_MESSAGE)
{
return true;
}
let Some(error) = parsed.and_then(|parsed| parsed.get("error")) else {
return false;
};
if error.get("code").and_then(Value::as_str) != Some("invalid_value") {
return false;
}
let param = error.get("param").and_then(Value::as_str);
let message = error
.get("message")
.and_then(Value::as_str)
.unwrap_or_default();
param.is_some_and(|param| param == "image_url" || param.ends_with(".image_url"))
|| (param == Some("url") && message.contains("Error while downloading "))
}
#[cfg(test)]
#[path = "api_bridge_tests.rs"]
@@ -2,6 +2,15 @@ use super::*;
use base64::Engine;
use pretty_assertions::assert_eq;
fn map_bad_request(body: String) -> CodexErr {
map_api_error(ApiError::Transport(TransportError::Http {
status: http::StatusCode::BAD_REQUEST,
url: Some("http://example.com/v1/responses".to_string()),
headers: None,
body: Some(body),
}))
}
#[test]
fn map_api_error_maps_server_overloaded() {
let err = map_api_error(ApiError::ServerOverloaded);
@@ -124,6 +133,43 @@ fn map_api_error_keeps_unknown_400_errors_generic() {
assert_eq!(message, body);
}
#[test]
fn map_api_error_maps_image_400s_to_invalid_image_request() {
let cases = [
serde_json::json!({
"error": {
"message": "The image data you provided does not represent a valid image. Please check your input and try again.",
"type": "invalid_request_error",
"param": "input[0].content[1].image_url",
"code": "invalid_value"
}
}),
serde_json::json!({
"type": "error",
"status": 400,
"error": {
"type": "invalid_request_error",
"code": "invalid_value",
"message": "Error while downloading http://127.0.0.1:8787/image.png. Upstream status code: 407.",
"param": "url"
}
}),
serde_json::json!({
"error": {
"message": "Expected a base64-encoded data URL with an image MIME type, but got empty base64-encoded bytes.",
"code": "invalid_value"
}
}),
];
for body in cases {
assert!(matches!(
map_bad_request(body.to_string()),
CodexErr::InvalidImageRequest()
));
}
}
#[test]
fn map_api_error_maps_usage_limit_limit_name_header() {
let mut headers = HeaderMap::new();