Skip to content

Bound executor-controlled HTTP response buffering#31781

Open
jif-oai wants to merge 1 commit into
mainfrom
jif/hostile-executor-byte-bounds
Open

Bound executor-controlled HTTP response buffering#31781
jif-oai wants to merge 1 commit into
mainfrom
jif/hostile-executor-byte-bounds

Conversation

@jif-oai

@jif-oai jif-oai commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Why

The remote exec-server is not a trusted process. Streamed HTTP responses were bounded by frame count, but each frame could carry almost the full JSON-RPC message limit. A peer could therefore make app-server retain a very large amount of response data before the existing 256-frame backpressure fired.

What changed

  • Define a 1 MiB wire limit for each http/request/bodyDelta payload.
  • Split larger HTTP chunks on the executor before sending them.
  • Share a 16 MiB queued-body budget across every active HTTP response stream on one executor connection.
  • Close an executor transport that violates the per-frame wire limit.
  • Fail only the affected body stream when its connection exhausts the shared queued-byte budget.
  • Cover both malicious cases with the existing scripted exec-server integration harness.

Scope

This bounds executor-controlled bytes retained by the HTTP response stream queues. It does not yet add a byte budget to the generic inbound JSON-RPC transport queue; that remains a separate hardening follow-up.

@jif-oai jif-oai requested a review from a team as a code owner July 9, 2026 12:04

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 939bff96b6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

})
.await?;
}
tokio::time::sleep(Duration::from_millis(100)).await;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Make the byte-budget test wait deterministically

This test relies on the notification handler draining and applying all 17 one-MiB body-delta notifications during this fixed 100 ms pause before the http/request response is sent. On a slow CI run, the response can be delivered first, the test starts calling recv(), permits are released as deltas arrive, and the shared byte budget may never be exhausted, causing the test to time out instead of observing the intended error. Use explicit synchronization that proves the queue has filled before returning the response rather than depending on timing.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to agree with codex re: sleeps for ordering in tests, I've seen them cause so many test flakes caused by noisy neighbors in CI. Is there a cleaner way to synchronize the test?

Comment thread codex-rs/exec-server/src/client/http_response_body_stream.rs
/// What this tests: frame-count backpressure cannot hide an unbounded amount
/// of executor-controlled body bytes across the orchestrator's stream queues.
#[tokio::test]
async fn http_response_body_stream_enforces_queued_byte_budget() -> Result<()> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also have a test that checks that two or more streams all contend for the same 16mb budget? IIUC this test would still pass if the permits were per-connection.

Comment thread codex-rs/exec-server/src/client/http_response_body_stream.rs
Comment thread codex-rs/exec-server/src/client/http_response_body_stream.rs
done: true,
error: Some(message.clone()),
},
/*byte_permit*/ None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe leave a comment explaining? IIUC this is a failure path so we want to bypass budgets to deliver the notification but would help me and future readers to have the intent

})
.await?;
}
tokio::time::sleep(Duration::from_millis(100)).await;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to agree with codex re: sleeps for ordering in tests, I've seen them cause so many test flakes caused by noisy neighbors in CI. Is there a cleaner way to synchronize the test?

} else {
u32::try_from(queued_bytes).ok().and_then(|queued_bytes| {
Arc::clone(&self.http_body_stream_byte_budget)
.try_acquire_many_owned(queued_bytes)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC exec-server basically has to trust that app-server will more or less work, is there a strong reason to fail these streams at capacity rather than doing self.http_body_stream_byte_budget.acquire(queued_bytes).await?

Comment thread codex-rs/exec-server-protocol/src/protocol.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants