From 5d7f696c2c35fee535ef749f41a0fe61bd34a06d Mon Sep 17 00:00:00 2001 From: prk-Jr Date: Mon, 20 Jul 2026 16:41:44 +0530 Subject: [PATCH 1/2] Fix ext.trusted_server.request_host to use the publisher domain On the SSAT proxy path the browser calls /auction against the trusted-server edge domain (e.g. ts.example.com), which was leaking into ext.trusted_server.request_host on the outbound Prebid Server request. That field must track the publisher's own domain instead, matching site.domain/publisher.domain and what PBS's trusted_server verification module expects. --- .../src/integrations/prebid.rs | 57 ++++++++++++++++++- .../src/platform/test_support.rs | 15 +++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/crates/trusted-server-core/src/integrations/prebid.rs b/crates/trusted-server-core/src/integrations/prebid.rs index 903b4f355..3990ca8be 100644 --- a/crates/trusted-server-core/src/integrations/prebid.rs +++ b/crates/trusted-server-core/src/integrations/prebid.rs @@ -2229,8 +2229,17 @@ impl AuctionProvider for PrebidAuctionProvider { ) -> Result> { log::info!("Prebid: requesting bids for {} slots", request.slots.len()); - let request_info = - RequestInfo::from_request(context.request, context.services.client_info()); + // `ext.trusted_server.request_host` must be the publisher's own domain + // (matching `site.domain`/`publisher.domain` below), not the raw edge + // `Host` header of the incoming `/auction` request. On the SSAT proxy + // path the browser calls `/auction` against the trusted-server edge + // domain (e.g. `ts.example.com`), which is not what PBS's + // `trusted_server` verification module expects — see the "Host header + // value expected by the receiving service" doc on `SigningParams`. + let request_info = RequestInfo { + host: request.publisher.domain.clone(), + scheme: "https".to_owned(), + }; // Create signer and compute signature if request signing is enabled let signer_with_signature = @@ -2672,6 +2681,50 @@ mod tests { ); } + #[test] + fn request_bids_sets_trusted_server_request_host_to_publisher_domain_not_edge_host() { + let stub = Arc::new(StubHttpClient::new()); + stub.push_response(200, br#"{"seatbid":[]}"#.to_vec()); + let services = build_services_with_http_client( + Arc::clone(&stub) as Arc + ); + let settings = make_settings(); + let provider = PrebidAuctionProvider::new(base_config()); + let auction_request = create_test_auction_request(); + // The incoming `/auction` call arrives on the trusted-server edge + // domain (SSAT proxy pattern), which must NOT leak into + // `ext.trusted_server.request_host` — that field must track the + // publisher's own domain instead. + let http_req = http::Request::builder() + .method(http::Method::POST) + .uri("https://ts.pub.example/auction") + .header(header::HOST, "ts.pub.example") + .body(EdgeBody::empty()) + .expect("should build request"); + let context = AuctionContext { + settings: &settings, + request: &http_req, + timeout_ms: 500, + provider_responses: None, + services: &services, + }; + + futures::executor::block_on(provider.request_bids(&auction_request, &context)) + .expect("should start request"); + + let bodies = stub.recorded_request_bodies(); + assert_eq!(bodies.len(), 1, "should send one upstream PBS request"); + let sent: Json = + serde_json::from_slice(&bodies[0]).expect("should parse sent OpenRTB request body"); + let request_host = sent["ext"]["trusted_server"]["request_host"] + .as_str() + .expect("should set ext.trusted_server.request_host"); + assert_eq!( + request_host, auction_request.publisher.domain, + "request_host should be the publisher domain, not the edge Host header" + ); + } + fn create_test_auction_context<'a>( settings: &'a Settings, request: &'a http::Request, diff --git a/crates/trusted-server-core/src/platform/test_support.rs b/crates/trusted-server-core/src/platform/test_support.rs index 5299d9818..4235b4ed7 100644 --- a/crates/trusted-server-core/src/platform/test_support.rs +++ b/crates/trusted-server-core/src/platform/test_support.rs @@ -473,6 +473,21 @@ impl PlatformHttpClient for StubHttpClient { .expect("should lock request_headers") .push(headers); + // Capture the outgoing request body, mirroring `send()`, so tests + // exercising the async fan-out path (`request_bids` providers) can + // assert on it via `recorded_request_bodies()` too. + let (_, body) = request.request.into_parts(); + let body_bytes = body + .into_bytes_bounded(MAX_RECORDED_BODY_BYTES) + .await + .change_context(PlatformError::HttpClient) + .attach("failed to capture StubHttpClient request body")? + .to_vec(); + self.request_bodies + .lock() + .expect("should lock request bodies") + .push(body_bytes); + let response = self .responses .lock() From ba61bc4028189bffec9e30f4392a6b373633aed5 Mon Sep 17 00:00:00 2001 From: prk-Jr Date: Tue, 21 Jul 2026 11:30:31 +0530 Subject: [PATCH 2/2] Note that scheme is canonical https, not request-derived Addresses review note on the ext.trusted_server.request_host fix: the comment justified only the host, not why scheme is hardcoded. --- crates/trusted-server-core/src/integrations/prebid.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/trusted-server-core/src/integrations/prebid.rs b/crates/trusted-server-core/src/integrations/prebid.rs index 3990ca8be..a1b40281d 100644 --- a/crates/trusted-server-core/src/integrations/prebid.rs +++ b/crates/trusted-server-core/src/integrations/prebid.rs @@ -2236,6 +2236,11 @@ impl AuctionProvider for PrebidAuctionProvider { // domain (e.g. `ts.example.com`), which is not what PBS's // `trusted_server` verification module expects — see the "Host header // value expected by the receiving service" doc on `SigningParams`. + // `scheme` is canonical `"https"` rather than detected from the + // incoming request: the publisher origin is always https in + // production, and PBS verification is expected to match on that, + // not on the edge's local scheme (e.g. http under `fastly compute + // serve`). let request_info = RequestInfo { host: request.publisher.domain.clone(), scheme: "https".to_owned(),