diff --git a/codex-rs/exec-server-protocol/src/rpc.rs b/codex-rs/exec-server-protocol/src/rpc.rs index 524ee374b5..12bab5cddc 100644 --- a/codex-rs/exec-server-protocol/src/rpc.rs +++ b/codex-rs/exec-server-protocol/src/rpc.rs @@ -184,6 +184,11 @@ impl<'de> Visitor<'de> for BoundedValueVisitor<'_> { { let mut values = Map::new(); while let Some(key) = object.next_key::()? { + if values.contains_key(&key) { + return Err(de::Error::custom(format!( + "duplicate JSON object key `{key}`" + ))); + } let value = object.next_value_seed(BoundedValueSeed { remaining: &mut *self.remaining, })?; diff --git a/codex-rs/exec-server-protocol/src/rpc_tests.rs b/codex-rs/exec-server-protocol/src/rpc_tests.rs index a00d0fbf8f..d60f18ef6a 100644 --- a/codex-rs/exec-server-protocol/src/rpc_tests.rs +++ b/codex-rs/exec-server-protocol/src/rpc_tests.rs @@ -60,6 +60,19 @@ fn accepts_large_scalar_payload() -> serde_json::Result<()> { Ok(()) } +#[test] +fn rejects_duplicate_object_keys() { + let error = serde_json::from_str::(r#"{"method":"safe","method":"dangerous"}"#) + .expect_err("duplicate JSON object keys should be rejected"); + + assert!( + error + .to_string() + .contains("duplicate JSON object key `method`"), + "unexpected error: {error}" + ); +} + #[test] fn rejects_compact_array_heap_amplification() { const REPRO_VALUE_COUNT: usize = 2_097_137; diff --git a/codex-rs/exec-server/src/server/file_system_handler.rs b/codex-rs/exec-server/src/server/file_system_handler.rs index cbf263dad9..21da65053a 100644 --- a/codex-rs/exec-server/src/server/file_system_handler.rs +++ b/codex-rs/exec-server/src/server/file_system_handler.rs @@ -11,6 +11,7 @@ use crate::ExecutorFileSystem; use crate::RemoveOptions; use crate::file_read::FileReadHandleManager; use crate::local_file_system::LocalFileSystem; +use crate::protocol::FS_READ_DIRECTORY_METHOD; use crate::protocol::FS_WRITE_FILE_METHOD; use crate::protocol::FsCanonicalizeParams; use crate::protocol::FsCanonicalizeResponse; @@ -42,6 +43,9 @@ use crate::rpc::invalid_request; use crate::rpc::not_found; const MAX_FILE_READ_HANDLE_ID_BYTES: usize = 32; +// Each read-directory entry needs four JSON values. Keep same-version +// producers comfortably below the shared 256K-value decoder budget. +const MAX_READ_DIRECTORY_ENTRIES: usize = 50_000; #[derive(Clone)] pub(crate) struct FileSystemHandler { @@ -189,7 +193,14 @@ impl FileSystemHandler { .file_system .read_directory(¶ms.path, params.sandbox.as_ref()) .await - .map_err(map_fs_error)? + .map_err(map_fs_error)?; + let entry_count = entries.len(); + if entry_count > MAX_READ_DIRECTORY_ENTRIES { + return Err(internal_error(format!( + "{FS_READ_DIRECTORY_METHOD} returned {entry_count} entries; limit is {MAX_READ_DIRECTORY_ENTRIES}" + ))); + } + let entries = entries .into_iter() .map(|entry| FsReadDirectoryEntry { file_name: entry.file_name,