diff --git a/crates/trusted-server-core/src/integrations/prebid.rs b/crates/trusted-server-core/src/integrations/prebid.rs index 903b4f355..a1b40281d 100644 --- a/crates/trusted-server-core/src/integrations/prebid.rs +++ b/crates/trusted-server-core/src/integrations/prebid.rs @@ -2229,8 +2229,22 @@ 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`. + // `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(), + }; // Create signer and compute signature if request signing is enabled let signer_with_signature = @@ -2672,6 +2686,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()