Files
codex/codex-rs/exec-server/src/noise_relay/ordered_ciphertext.rs
T
428cd44154 exec-server: add Noise relay transport (#26242)
## 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>
2026-06-15 16:39:41 -07:00

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;