mirror of
https://github.com/openai/codex.git
synced 2026-04-24 06:35:50 +00:00
saved_path mod + filesystem integrity risk patch
This commit is contained in:
@@ -153,6 +153,7 @@ pub fn parse_turn_item(item: &ResponseItem) -> Option<TurnItem> {
|
||||
status: status.clone(),
|
||||
revised_prompt: revised_prompt.clone(),
|
||||
result: result.clone(),
|
||||
saved_path: None,
|
||||
},
|
||||
)),
|
||||
_ => None,
|
||||
|
||||
@@ -64,11 +64,19 @@ async fn save_image_generation_result_to_cwd(
|
||||
.map_err(|err| {
|
||||
CodexErr::InvalidRequest(format!("invalid image generation payload: {err}"))
|
||||
})?;
|
||||
let file_stem = if call_id.is_empty() {
|
||||
"generated_image"
|
||||
} else {
|
||||
call_id
|
||||
};
|
||||
let mut file_stem: String = call_id
|
||||
.chars()
|
||||
.map(|ch| {
|
||||
if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {
|
||||
ch
|
||||
} else {
|
||||
'_'
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if file_stem.is_empty() {
|
||||
file_stem = "generated_image".to_string();
|
||||
}
|
||||
let path = cwd.join(format!("{file_stem}.png"));
|
||||
tokio::fs::write(&path, bytes).await?;
|
||||
Ok(path)
|
||||
@@ -181,16 +189,20 @@ pub(crate) async fn handle_output_item_done(
|
||||
}
|
||||
// No tool call: convert messages/reasoning into turn items and mark them as complete.
|
||||
Ok(None) => {
|
||||
if let Some(mut turn_item) = handle_non_tool_response_item(&item, plan_mode) {
|
||||
if let TurnItem::ImageGeneration(image_item) = &mut turn_item {
|
||||
let path = save_image_generation_result_to_cwd(
|
||||
&ctx.turn_context.cwd,
|
||||
&image_item.id,
|
||||
&image_item.result,
|
||||
)
|
||||
.await?;
|
||||
image_item.result = path.to_string_lossy().into_owned();
|
||||
}
|
||||
if let Some(turn_item) = handle_non_tool_response_item(&item, plan_mode) {
|
||||
let turn_item = match turn_item {
|
||||
TurnItem::ImageGeneration(mut image_item) => {
|
||||
let path = save_image_generation_result_to_cwd(
|
||||
&ctx.turn_context.cwd,
|
||||
&image_item.id,
|
||||
&image_item.result,
|
||||
)
|
||||
.await?;
|
||||
image_item.saved_path = Some(path.to_string_lossy().into_owned());
|
||||
TurnItem::ImageGeneration(image_item)
|
||||
}
|
||||
other => other,
|
||||
};
|
||||
|
||||
if previously_active_item.is_none() {
|
||||
let mut started_item = turn_item.clone();
|
||||
@@ -198,6 +210,7 @@ pub(crate) async fn handle_output_item_done(
|
||||
item.status = "in_progress".to_string();
|
||||
item.revised_prompt = None;
|
||||
item.result.clear();
|
||||
item.saved_path = None;
|
||||
}
|
||||
ctx.sess
|
||||
.emit_turn_item_started(&ctx.turn_context, &started_item)
|
||||
@@ -469,6 +482,22 @@ mod tests {
|
||||
assert_eq!(std::fs::read(saved_path).expect("saved file"), b"foo");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn save_image_generation_result_sanitizes_call_id_for_output_path() {
|
||||
let dir = tempdir().expect("tempdir");
|
||||
|
||||
let saved_path = save_image_generation_result_to_cwd(dir.path(), "../ig/..", "Zm9v")
|
||||
.await
|
||||
.expect("image should be saved");
|
||||
|
||||
assert_eq!(saved_path.parent(), Some(dir.path()));
|
||||
assert_eq!(
|
||||
saved_path.file_name().and_then(|v| v.to_str()),
|
||||
Some("___ig___.png")
|
||||
);
|
||||
assert_eq!(std::fs::read(saved_path).expect("saved file"), b"foo");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn save_image_generation_result_rejects_non_standard_base64() {
|
||||
let dir = tempdir().expect("tempdir");
|
||||
|
||||
@@ -303,10 +303,11 @@ async fn image_generation_call_event_is_emitted() -> anyhow::Result<()> {
|
||||
assert_eq!(end.call_id, "ig_123");
|
||||
assert_eq!(end.status, "completed");
|
||||
assert_eq!(end.revised_prompt, Some("A tiny blue square".to_string()));
|
||||
assert_eq!(end.result, "Zm9v");
|
||||
let expected_saved_path = cwd.path().join("ig_123.png");
|
||||
assert_eq!(
|
||||
end.result,
|
||||
expected_saved_path.to_string_lossy().into_owned()
|
||||
end.saved_path,
|
||||
Some(expected_saved_path.to_string_lossy().into_owned())
|
||||
);
|
||||
assert_eq!(std::fs::read(expected_saved_path)?, b"foo");
|
||||
|
||||
|
||||
@@ -89,6 +89,9 @@ pub struct ImageGenerationItem {
|
||||
#[ts(optional)]
|
||||
pub revised_prompt: Option<String>,
|
||||
pub result: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub saved_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema)]
|
||||
@@ -254,6 +257,7 @@ impl ImageGenerationItem {
|
||||
status: self.status.clone(),
|
||||
revised_prompt: self.revised_prompt.clone(),
|
||||
result: self.result.clone(),
|
||||
saved_path: self.saved_path.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1898,6 +1898,9 @@ pub struct ImageGenerationEndEvent {
|
||||
#[ts(optional)]
|
||||
pub revised_prompt: Option<String>,
|
||||
pub result: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub saved_path: Option<String>,
|
||||
}
|
||||
|
||||
// Conversation kept for backward compatibility.
|
||||
@@ -3270,6 +3273,7 @@ mod tests {
|
||||
status: "in_progress".into(),
|
||||
revised_prompt: None,
|
||||
result: String::new(),
|
||||
saved_path: None,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -3291,6 +3295,7 @@ mod tests {
|
||||
status: "completed".into(),
|
||||
revised_prompt: Some("A tiny blue square".into()),
|
||||
result: "Zm9v".into(),
|
||||
saved_path: Some("/tmp/ig-1.png".into()),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -3302,6 +3307,7 @@ mod tests {
|
||||
assert_eq!(event.status, "completed");
|
||||
assert_eq!(event.revised_prompt.as_deref(), Some("A tiny blue square"));
|
||||
assert_eq!(event.result, "Zm9v");
|
||||
assert_eq!(event.saved_path.as_deref(), Some("/tmp/ig-1.png"));
|
||||
}
|
||||
_ => panic!("expected ImageGenerationEnd event"),
|
||||
}
|
||||
|
||||
@@ -6053,6 +6053,7 @@ async fn image_generation_call_adds_history_cell() {
|
||||
status: "completed".into(),
|
||||
revised_prompt: Some("A tiny blue square".into()),
|
||||
result: "Zm9v".into(),
|
||||
saved_path: None,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user