#[allow(clippy::wildcard_imports)]
use super::*;
use lean_ctx_protocol::Sha256Digest;
use lean_ctx_protocol::context_gateway::ContextDecisionReceiptV1;
use sha2::Digest as _;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::task::JoinHandle;
async fn read_request_body(stream: &mut TcpStream) -> Vec<u8> {
let mut request = Vec::new();
let mut buffer = [0_u8; 4096];
let header_end = loop {
let read = tokio::time::timeout(Duration::from_secs(10), stream.read(&mut buffer))
.await
.expect("bounded request read")
.expect("read request");
assert!(read > 0, "upstream closed before the request headers");
request.extend_from_slice(&buffer[..read]);
if let Some(end) = request.windows(4).position(|window| window == b"\r\n\r\n") {
break end + 4;
}
};
let head = String::from_utf8_lossy(&request[..header_end]).to_ascii_lowercase();
let length = head
.lines()
.find_map(|line| line.strip_prefix("content-length:"))
.map_or(0, |value| value.trim().parse::<usize>().expect("length"));
while request.len() < header_end + length {
let read = tokio::time::timeout(Duration::from_secs(10), stream.read(&mut buffer))
.await
.expect("bounded body read")
.expect("read body");
assert!(read > 0, "upstream closed before the request body");
request.extend_from_slice(&buffer[..read]);
}
request[header_end..header_end + length].to_vec()
}
async fn recording_upstream(
statuses: Vec<u16>,
after_first: impl FnOnce() + Send + 'static,
) -> (String, Recorder) {
let listener = TcpListener::bind("127.0.0.1:0")
.await
.expect("bind loopback upstream");
let address = listener.local_addr().expect("loopback address");
let stop = std::sync::Arc::new(tokio::sync::Notify::new());
let stopped = stop.clone();
let server = tokio::spawn(async move {
let mut bodies = Vec::new();
let mut after_first = Some(after_first);
for status in statuses {
let accepted = tokio::select! {
accepted = listener.accept() => accepted,
() = stopped.notified() => break,
};
let Ok((mut stream, _)) = accepted else {
break;
};
bodies.push(read_request_body(&mut stream).await);
if let Some(hook) = after_first.take() {
hook();
}
let wire = format!(
"HTTP/1.1 {status} Status\r\ncontent-type: application/json\r\n\
content-length: 2\r\nconnection: close\r\n\r\n{{}}"
);
let _ = stream.write_all(wire.as_bytes()).await;
let _ = stream.shutdown().await;
}
bodies
});
(format!("http://{address}"), Recorder { server, stop })
}
struct Recorder {
server: JoinHandle<Vec<Vec<u8>>>,
stop: std::sync::Arc<tokio::sync::Notify>,
}
impl Recorder {
async fn bodies(self) -> Vec<Vec<u8>> {
self.stop.notify_one();
self.server.await.expect("upstream")
}
}
fn sha256(bytes: &[u8]) -> Sha256Digest {
let hex = lean_ctx::core::agent_identity::hex_encode(&sha2::Sha256::digest(bytes));
Sha256Digest::new(format!("sha256:{hex}")).expect("canonical SHA-256 digest")
}
fn proxy_receipts() -> Vec<ContextDecisionReceiptV1> {
lean_ctx::core::context_admission::receipt_store::latest(
lean_ctx::core::context_admission::egress::PROXY_RECEIPT_KEY,
16,
)
.into_iter()
.map(|(_, loaded)| loaded.expect("receipt verifies").receipt)
.collect()
}
fn has_reason(receipt: &ContextDecisionReceiptV1, code: &str) -> bool {
receipt.decisions.iter().any(|decision| {
decision
.reason_codes
.iter()
.any(|reason| reason.as_str() == code)
})
}
fn chat_request(body: Vec<u8>) -> Request<Body> {
Request::builder()
.method("POST")
.uri("/v1/chat/completions")
.header(CONTENT_TYPE, "application/json")
.body(Body::from(body))
.expect("request")
}
async fn forward_chat(upstream: &str, body: Vec<u8>) -> Result<Response, StatusCode> {
tokio::time::timeout(
Duration::from_secs(30),
forward_request(
State(proxy_test_state(upstream)),
chat_request(body),
upstream,
"/v1/chat/completions",
add_test_marker,
"OpenAI",
&[],
),
)
.await
.expect("the request is refused or answered, never left waiting")
}
async fn forward_masked(
compressor: Option<TestCompressor>,
) -> (Vec<u8>, Vec<ContextDecisionReceiptV1>) {
let key = example_access_key();
let body = serde_json::json!({
"model": "gpt-5",
"messages": [{"role": "user", "content": format!("deploy with {key} today")}]
});
let (upstream, server) = recording_upstream(vec![200], || {}).await;
let mut driver = proxy_test_driver(proxy_test_state(&upstream), &body, &upstream);
if let Some(compressor) = compressor {
driver.compress_body = Some(compressor);
}
let context = proxy_test_context();
driver.apply_security_boundaries(&context).await.unwrap();
driver.gather_context_strategy(&context).await.unwrap();
let _ = driver.dispatch_primitive(&context).await.unwrap();
let mut bodies = server.bodies().await;
assert_eq!(bodies.len(), 1, "exactly one upstream request");
let received = bodies.remove(0);
assert!(
!String::from_utf8_lossy(&received).contains(key),
"the protected value reached the upstream"
);
(received, proxy_receipts())
}
#[tokio::test]
#[serial_test::serial(cache_telemetry)]
async fn one_receipt_binds_the_final_bytes_for_untouched_and_transformed_bodies() {
let cases: [(&str, Option<TestCompressor>, u32); 3] = [
("untouched", None, 1),
(
"transformed once",
Some(add_test_marker as TestCompressor),
1,
),
(
"transformed twice",
Some(reintroducing_compressor as TestCompressor),
2,
),
];
for (case, compressor, redactions) in cases {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let (received, receipts) = forward_masked(compressor).await;
assert_eq!(receipts.len(), 1, "{case}: one request, one receipt");
let receipt = &receipts[0];
assert!(
receipt.security.redactions >= redactions,
"{case}: the receipt shows {} redaction(s), expected at least {redactions}",
receipt.security.redactions
);
assert_eq!(
receipt.final_context,
Some(sha256(&received)),
"{case}: the receipt binds exactly the bytes the upstream received"
);
}
}
#[tokio::test]
#[serial_test::serial(cache_telemetry)]
async fn the_receipt_names_the_caller_the_proxy_told_apart_by_a_keyed_reference() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let config = lean_ctx::core::paths::config_dir_read_only().expect("config dir");
std::fs::create_dir_all(&config).expect("config dir");
std::fs::write(
config.join("config.toml"),
"[context_gateway]\nmode = \"governed\"\n",
)
.expect("governed config");
let (upstream, server) = recording_upstream(vec![200, 200, 200], || {}).await;
for (turn, credential) in ["Bearer caller-a", "Bearer caller-a", "Bearer caller-b"]
.into_iter()
.enumerate()
{
let body = serde_json::json!({
"model": "gpt-5",
"messages": [{"role": "user", "content": format!("which caller sent turn {turn}")}]
});
let mut request = chat_request(serde_json::to_vec(&body).expect("body"));
request.headers_mut().insert(
"authorization",
axum::http::HeaderValue::from_static(credential),
);
let response = forward_request(
State(proxy_test_state(&upstream)),
request,
&upstream,
"/v1/chat/completions",
add_test_marker,
"OpenAI",
&[],
)
.await
.expect("forwarded");
assert!(response.status().is_success());
}
assert_eq!(server.bodies().await.len(), 3);
let refs: Vec<String> = lean_ctx::core::context_admission::receipt_store::latest(
lean_ctx::core::context_admission::egress::PROXY_RECEIPT_KEY,
16,
)
.into_iter()
.map(|(_, loaded)| {
let digest = loaded.expect("receipt verifies").digest;
match lean_ctx::core::context_admission::receipt_store::trace::load(&digest) {
lean_ctx::core::context_admission::receipt_store::trace::StoredTrace::Bound {
sidecar,
..
} => sidecar.principal_ref.expect("the caller is named"),
other => panic!("the governed receipt has its sidecar: {other:?}"),
}
})
.collect();
assert_eq!(refs.len(), 3, "{refs:?}");
let distinct: std::collections::BTreeSet<&String> = refs.iter().collect();
assert_eq!(
distinct.len(),
2,
"same credential, same reference: {refs:?}"
);
let unkeyed = crate::forward::principal_of(
&[(
axum::http::header::AUTHORIZATION,
axum::http::HeaderValue::from_static("Bearer caller-a"),
)]
.into_iter()
.collect(),
None,
)
.expect("a principal");
for reference in &refs {
assert!(!reference.contains("caller-") && !reference.contains(&unkeyed.receipt));
}
}
const OPAQUE_BODY: &[u8] = &[0xff, 0xfe, 0x00, 0x01, 0x80, 0x7f];
#[tokio::test]
#[serial_test::serial(cache_telemetry)]
async fn opaque_body_is_forwarded_unchanged_and_recorded_as_opaque_in_developer_mode() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let (upstream, server) = recording_upstream(vec![200], || {}).await;
let response = forward_chat(&upstream, OPAQUE_BODY.to_vec())
.await
.expect("an opaque body is forwarded in developer mode");
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(server.bodies().await, vec![OPAQUE_BODY.to_vec()]);
let receipts = proxy_receipts();
assert_eq!(receipts.len(), 1);
assert!(has_reason(&receipts[0], "egress.opaque_payload"));
assert_eq!(receipts[0].final_context, Some(sha256(OPAQUE_BODY)));
}
#[tokio::test]
#[serial_test::serial(cache_telemetry)]
async fn opaque_body_is_refused_in_governed_mode_without_a_connection() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
write_gateway_config("[context_gateway]\nmode = \"governed\"\n");
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let upstream = format!("http://{}", listener.local_addr().unwrap());
let response = forward_chat(&upstream, OPAQUE_BODY.to_vec())
.await
.expect("a refusal is a typed response");
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let codes = response.headers()[crate::admitted::REASON_CODES_HEADER]
.to_str()
.expect("reason codes")
.to_owned();
assert!(codes.contains("egress.opaque_payload"), "{codes}");
assert!(
tokio::time::timeout(Duration::from_millis(50), listener.accept())
.await
.is_err(),
"a refused opaque body must not open an upstream connection"
);
}
#[tokio::test]
#[serial_test::serial(cache_telemetry)]
async fn policy_change_before_bind_refuses_an_admitted_opaque_body() {
use crate::admitted::failpoint::{self, Point};
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
write_gateway_config("[context_gateway]\nmode = \"developer\"\n");
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let upstream = format!("http://{}", listener.local_addr().unwrap());
let _armed = failpoint::arm(|point| {
if point == Point::CallerVerified {
write_gateway_config("[context_gateway]\nmode = \"governed\"\n");
}
});
let result = forward_chat(&upstream, OPAQUE_BODY.to_vec()).await;
assert_typed_refusal(result, "policy.changed").await;
assert!(
tokio::time::timeout(Duration::from_millis(50), listener.accept())
.await
.is_err(),
"an opaque body admitted under the old policy must not leave under the new one"
);
}
#[tokio::test]
#[serial_test::serial(cache_telemetry)]
async fn policy_change_during_backoff_stops_the_retry_of_an_opaque_body() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
write_gateway_config("[context_gateway]\nmode = \"developer\"\n");
let (upstream, server) = recording_upstream(vec![503, 200], || {
write_gateway_config("[context_gateway]\nmode = \"governed\"\n");
})
.await;
let result = forward_chat(&upstream, OPAQUE_BODY.to_vec()).await;
assert_typed_refusal(result, "policy.changed").await;
assert_eq!(
server.bodies().await.len(),
1,
"the retry must not be dispatched after the policy changed"
);
}
#[tokio::test]
#[serial_test::serial(cache_telemetry)]
async fn partially_inspected_content_is_withheld_in_governed_and_delivered_in_developer_mode() {
let text = "a sentence long enough to exceed the tiny inspection budget of this test";
let body = serde_json::to_vec(&serde_json::json!({
"model": "m",
"messages": [{"role": "user", "content": text}]
}))
.unwrap();
let levers = ["max_inspected_bytes = 8", "detector_timeout_ms = 0"];
let modes = [("governed", false), ("developer", true)];
for (lever, (mode, delivered)) in levers
.iter()
.flat_map(|lever| modes.iter().map(move |mode| (*lever, *mode)))
{
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
write_gateway_config(&format!("[context_gateway]\nmode = \"{mode}\"\n{lever}\n"));
let (upstream, server) = recording_upstream(vec![200], || {}).await;
let request = Request::builder()
.method("POST")
.uri("/v1/chat/completions")
.header(CONTENT_TYPE, "application/json")
.body(Body::from(body.clone()))
.unwrap();
let identity: TestCompressor = |value, original_size| {
let out = serde_json::to_vec(&value).unwrap();
let size = out.len();
(out, original_size, size)
};
let result = tokio::time::timeout(
Duration::from_secs(30),
forward_request(
State(proxy_test_state(&upstream)),
request,
&upstream,
"/v1/chat/completions",
identity,
"OpenAI",
&[],
),
)
.await
.expect("bounded forward");
assert!(
result.is_ok(),
"{mode}, {lever}: {:?}",
result.as_ref().err()
);
let bodies = server.bodies().await;
let arrived = bodies
.iter()
.any(|received| String::from_utf8_lossy(received).contains(text));
assert_eq!(arrived, delivered, "{mode}, {lever}");
if delivered {
assert_eq!(
bodies,
vec![body.clone()],
"{mode}, {lever}: byte-identical"
);
}
}
}
#[tokio::test]
async fn client_abort_mid_stream_closes_the_upstream_connection() {
use futures::StreamExt;
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let upstream = format!("http://{}", listener.local_addr().unwrap());
let server = tokio::spawn(async move {
let (mut stream, _) = listener.accept().await.unwrap();
let _ = read_request_body(&mut stream).await;
stream
.write_all(
b"HTTP/1.1 200 OK\r\ncontent-type: text/event-stream\r\n\r\ndata: {\"n\":1}\n\n",
)
.await
.unwrap();
let mut buffer = [0_u8; 64];
matches!(
tokio::time::timeout(Duration::from_secs(3), stream.read(&mut buffer)).await,
Ok(Ok(0) | Err(_))
)
});
let body = br#"{"model":"m","stream":true,"messages":[{"role":"user","content":"hello"}]}"#;
let response = forward_chat(&upstream, body.to_vec())
.await
.expect("streaming response");
assert_eq!(response.status(), StatusCode::OK);
let mut frames = response.into_body().into_data_stream();
let first = tokio::time::timeout(Duration::from_secs(3), frames.next())
.await
.expect("the first frame is relayed while the stream is open");
assert!(first.is_some());
drop(frames);
assert!(
server.await.unwrap(),
"the upstream connection must close when the client is gone"
);
}
fn enable_prefix_replay() {
let mut config = lean_ctx::core::config::Config::default();
config.proxy.proxy_mode = Some("cache".into());
config.save().expect("cache-mode config");
assert_eq!(
lean_ctx::core::config::Config::load()
.proxy
.resolved_proxy_mode(),
lean_ctx::core::config::ProxyMode::Cache
);
}
#[derive(Clone, Copy)]
enum ReplayRoute {
Anthropic,
OpenAiChat,
}
async fn turn(route: ReplayRoute, body: &serde_json::Value) -> Vec<u8> {
let (upstream, server) = recording_upstream(vec![200], || {}).await;
let (uri, state) = (
match route {
ReplayRoute::Anthropic => "/v1/messages",
ReplayRoute::OpenAiChat => "/v1/chat/completions",
},
State(proxy_test_state(&upstream)),
);
let request = Request::builder()
.method("POST")
.uri(uri)
.header(CONTENT_TYPE, "application/json")
.body(Body::from(serde_json::to_vec(body).unwrap()))
.unwrap();
let handled = async {
match route {
ReplayRoute::Anthropic => crate::anthropic::handler(state, request).await,
ReplayRoute::OpenAiChat => crate::openai::handler(state, request).await,
}
};
let response = tokio::time::timeout(Duration::from_secs(30), handled)
.await
.expect("bounded route")
.expect("forwarded");
assert_eq!(response.status(), StatusCode::OK);
server.bodies().await.remove(0)
}
fn continued(first: &serde_json::Value) -> serde_json::Value {
serde_json::json!([
first,
{"role": "assistant", "content": "noted"},
{"role": "user", "content": "continue"}
])
}
#[tokio::test]
#[serial_test::serial(cache_telemetry)]
async fn prefix_replay_never_sends_an_earlier_turns_frame() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
enable_prefix_replay();
let changes: [(&str, serde_json::Value); 4] = [
("model", serde_json::json!({"model": "claude-other"})),
("max_tokens", serde_json::json!({"max_tokens": 64})),
(
"tools",
serde_json::json!({"tools": [{"name": "lookup", "input_schema": {"type": "object"}}]}),
),
("stream", serde_json::json!({"stream": true})),
];
for (changed, overlay) in changes {
let first = serde_json::json!({
"role": "user",
"content": format!("prefix replay frame regression: {changed}")
});
let turn_one = serde_json::json!({
"model": "claude-test", "max_tokens": 16, "messages": [first]
});
let _ = turn(ReplayRoute::Anthropic, &turn_one).await;
let mut turn_two = serde_json::json!({
"model": "claude-test", "max_tokens": 16, "messages": continued(&first)
});
for (key, value) in overlay.as_object().expect("frame change") {
turn_two[key] = value.clone();
}
let received: serde_json::Value =
serde_json::from_slice(&turn(ReplayRoute::Anthropic, &turn_two).await).unwrap();
assert_eq!(
received, turn_two,
"{changed}: the upstream must receive the current request, not an earlier frame"
);
}
}
#[tokio::test]
#[serial_test::serial(cache_telemetry)]
async fn prefix_replay_does_not_cross_routes() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
enable_prefix_replay();
let first = serde_json::json!({
"role": "user", "content": "prefix replay cross-route regression"
});
let _ = turn(
ReplayRoute::OpenAiChat,
&serde_json::json!({"model": "gpt-test", "messages": [first]}),
)
.await;
let anthropic = serde_json::json!({
"model": "claude-test", "max_tokens": 16, "messages": continued(&first)
});
let received: serde_json::Value =
serde_json::from_slice(&turn(ReplayRoute::Anthropic, &anthropic).await).unwrap();
assert_eq!(received, anthropic);
}
#[tokio::test]
#[serial_test::serial(cache_telemetry)]
async fn prefix_replay_keeps_the_prefix_byte_stable_for_an_unchanged_frame() {
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
enable_prefix_replay();
let first = serde_json::json!({
"role": "user", "content": "prefix replay stable-frame control"
});
let turn_one = serde_json::json!({
"model": "claude-test", "max_tokens": 16, "messages": [first]
});
let forwarded_one = turn(ReplayRoute::Anthropic, &turn_one).await;
let messages = continued(&first);
let turn_two = serde_json::json!({
"model": "claude-test", "max_tokens": 16, "messages": messages
});
let forwarded_two = turn(ReplayRoute::Anthropic, &turn_two).await;
let delta = &messages.as_array().expect("messages")[1..];
assert_eq!(
Some(forwarded_two),
crate::prefix_replay::overlay_prefix(&forwarded_one, delta),
"the second turn is the first turn's forwarded bytes plus the new messages"
);
}
#[path = "tests_sealed.rs"]
mod sealed;