mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
## Why Rendezvous forwards traffic between the orchestrator and exec-server. The endpoints need to authenticate each other and encrypt that traffic without trusting Rendezvous with plaintext or endpoint keys. ## Changes - Adds a hybrid Noise IK channel through Clatter using X25519, ML-KEM-768, AES-256-GCM, and SHA-256. - Binds each handshake to `environment_id`, `executor_registration_id`, and `stream_id`. - Pins the registry-provided executor key and carries the harness authorization inside the encrypted handshake. - Orders relay frames before consuming Noise nonces and fragments large JSON-RPC messages into bounded records. - Bounds handshake payloads, frames, streams, and message reassembly. Runtime activation is in [openai/codex#26245](https://github.com/openai/codex/pull/26245). ## Stack 1. **[openai/codex#26242](https://github.com/openai/codex/pull/26242)**: Noise channel and relay transport 2. [openai/codex#26245](https://github.com/openai/codex/pull/26245): remote registration and runtime activation ## Verification - `just test -p codex-exec-server` - Oversized initiator payload regression coverage - `just fix -p codex-exec-server` - `just bazel-lock-check` - `cargo shear` --------- Co-authored-by: Codex <noreply@openai.com>
71 lines
2.4 KiB
Rust
71 lines
2.4 KiB
Rust
use std::collections::BTreeMap;
|
|
|
|
use crate::ExecServerError;
|
|
|
|
const MAX_REORDER_DISTANCE: u32 = 64;
|
|
const MAX_PENDING_BYTES: usize = 1024 * 1024;
|
|
|
|
/// Reorders relay records before they reach Noise's implicit receive nonce.
|
|
/// The window is bounded, and each sequence number is released at most once.
|
|
#[derive(Default)]
|
|
pub(crate) struct OrderedCiphertextFrames {
|
|
next_seq: u32,
|
|
pending: BTreeMap<u32, Vec<u8>>,
|
|
pending_bytes: usize,
|
|
}
|
|
|
|
impl OrderedCiphertextFrames {
|
|
/// Accept one relay record and return the newly contiguous ciphertext run.
|
|
///
|
|
/// Returns nothing for duplicates or while a gap remains. Closing a gap also
|
|
/// releases any buffered records that now follow it contiguously.
|
|
pub(crate) fn push(
|
|
&mut self,
|
|
seq: u32,
|
|
payload: Vec<u8>,
|
|
) -> Result<Vec<Vec<u8>>, ExecServerError> {
|
|
// Keep the first ciphertext for a sequence. Later copies are duplicates.
|
|
if seq < self.next_seq || self.pending.contains_key(&seq) {
|
|
return Ok(Vec::new());
|
|
}
|
|
if seq > self.next_seq {
|
|
// Bound both the sequence gap and buffered bytes.
|
|
if seq - self.next_seq > MAX_REORDER_DISTANCE {
|
|
return Err(ExecServerError::Protocol(
|
|
"Noise relay ciphertext exceeds reorder window".to_string(),
|
|
));
|
|
}
|
|
let pending_bytes = self.pending_bytes + payload.len();
|
|
if pending_bytes > MAX_PENDING_BYTES {
|
|
return Err(ExecServerError::Protocol(
|
|
"Noise relay pending ciphertext buffer is full".to_string(),
|
|
));
|
|
}
|
|
self.pending.insert(seq, payload);
|
|
self.pending_bytes = pending_bytes;
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
// Release the expected record and anything now contiguous behind it.
|
|
let mut ready = vec![payload];
|
|
self.advance()?;
|
|
while let Some(payload) = self.pending.remove(&self.next_seq) {
|
|
self.pending_bytes -= payload.len();
|
|
ready.push(payload);
|
|
self.advance()?;
|
|
}
|
|
Ok(ready)
|
|
}
|
|
|
|
fn advance(&mut self) -> Result<(), ExecServerError> {
|
|
self.next_seq = self.next_seq.checked_add(1).ok_or_else(|| {
|
|
ExecServerError::Protocol("Noise relay sequence number exhausted".to_string())
|
|
})?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "ordered_ciphertext_tests.rs"]
|
|
mod tests;
|