repair invalid image poisoning across resume

This commit is contained in:
Roy Han
2026-05-20 11:15:23 -07:00
parent 5c43a64e2b
commit ef5acb20ad
7 changed files with 258 additions and 173 deletions
+19 -4
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,8 +68,19 @@ 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 body_text.contains(INVALID_IMAGE_ERROR_MESSAGE)
|| body_text.contains(EMPTY_BASE64_IMAGE_ERROR_MESSAGE)
|| parsed
.as_ref()
.and_then(|parsed| parsed.get("error"))
.is_some_and(|error| {
error.get("code").and_then(Value::as_str)
== Some(INVALID_VALUE_ERROR_CODE)
&& error
.get("param")
.and_then(Value::as_str)
.is_some_and(|param| param.ends_with(".image_url"))
})
{
CodexErr::InvalidImageRequest()
} else {
@@ -141,6 +152,10 @@ 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_VALUE_ERROR_CODE: &str = "invalid_value";
const INVALID_IMAGE_ERROR_MESSAGE: &str =
"The image data you provided does not represent a valid image";
const EMPTY_BASE64_IMAGE_ERROR_MESSAGE: &str = "Expected a base64-encoded data URL with an image MIME type (e.g. 'data:image/png;base64,aW1nIGJ5dGVzIGhlcmU='), but got empty base64-encoded bytes.";
#[cfg(test)]
#[path = "api_bridge_tests.rs"]
@@ -102,6 +102,50 @@ fn map_api_error_uses_cyber_policy_fallback_for_missing_message() {
);
}
#[test]
fn map_api_error_maps_invalid_image_param_from_400_body() {
let body = serde_json::json!({
"error": {
"message": "Invalid inline image.",
"type": "invalid_request_error",
"param": "input[13].content[2].image_url",
"code": "invalid_value"
}
})
.to_string();
let err = 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),
}));
assert!(matches!(err, CodexErr::InvalidImageRequest()));
}
#[test]
fn map_api_error_maps_empty_base64_image_message_from_400_body() {
let body = serde_json::json!({
"error": {
"message": format!(
"Invalid 'input[0].content[2].image_url': {EMPTY_BASE64_IMAGE_ERROR_MESSAGE}"
),
"type": "invalid_request_error",
"param": null,
"code": "invalid_value"
}
})
.to_string();
let err = 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),
}));
assert!(matches!(err, CodexErr::InvalidImageRequest()));
}
#[test]
fn map_api_error_keeps_unknown_400_errors_generic() {
let body = serde_json::json!({