Skip to content
Open
1 change: 1 addition & 0 deletions crates/trusted-server-adapter-axum/tests/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ async fn aps_profile_serves_renderer_through_adapter_fallback() {
settings.auction.providers.insert(
"aps-main".parse().expect("should parse APS provider ID"),
trusted_server_core::auction::ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "aps".to_string(),
endpoint: "https://aps.example/e/pb/bid".to_string(),
Expand Down
3 changes: 3 additions & 0 deletions crates/trusted-server-adapter-cloudflare/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,7 @@ mod tests {
settings.auction.providers.insert(
"aps-main".parse().expect("should parse APS provider ID"),
trusted_server_core::auction::ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "aps".to_string(),
endpoint: "https://aps.example/e/pb/bid".to_string(),
Expand Down Expand Up @@ -883,6 +884,7 @@ mod tests {
(
id.parse().expect("should parse provider ID"),
trusted_server_core::auction::ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "standard".to_string(),
endpoint: format!("https://{id}.example/openrtb"),
Expand Down Expand Up @@ -927,6 +929,7 @@ mod tests {
(
id.parse().expect("should parse provider ID"),
trusted_server_core::auction::ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "standard".to_string(),
endpoint: format!("https://{id}.example/openrtb"),
Expand Down
1 change: 1 addition & 0 deletions crates/trusted-server-adapter-fastly/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1717,6 +1717,7 @@ mod tests {
settings.auction.providers.insert(
"aps-main".parse().expect("should parse APS provider ID"),
trusted_server_core::auction::ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "aps".to_string(),
endpoint: "https://aps.example/e/pb/bid".to_string(),
Expand Down
2 changes: 2 additions & 0 deletions crates/trusted-server-adapter-spin/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,7 @@ mod tests {
(
id.parse().expect("should parse provider ID"),
trusted_server_core::auction::ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "standard".to_string(),
endpoint: format!("https://{id}.example/openrtb"),
Expand All @@ -909,6 +910,7 @@ mod tests {
settings.auction.providers.insert(
"aps-main".parse().expect("should parse APS provider ID"),
trusted_server_core::auction::ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "aps".to_string(),
endpoint: "https://aps.example/e/pb/bid".to_string(),
Expand Down
1 change: 1 addition & 0 deletions crates/trusted-server-core/src/auction/formats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@ mod tests {
providers: BTreeMap::from([(
ProviderId::from_str("pbs-primary").expect("should parse provider ID"),
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "prebid-server".to_string(),
endpoint: "https://pbs.example.test/openrtb".to_string(),
Expand Down
1 change: 1 addition & 0 deletions crates/trusted-server-core/src/auction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ mod plan_sharing_tests {
settings.auction.providers = std::collections::BTreeMap::from([(
"aps-main".parse().expect("should parse APS provider ID"),
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "aps".to_string(),
endpoint: "https://aps.example/e/pb/bid".to_string(),
Expand Down
1 change: 1 addition & 0 deletions crates/trusted-server-core/src/auction/openrtb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,7 @@ mod routing_metadata_tests {
providers: BTreeMap::from([(
provider_id.clone(),
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: profile.to_string(),
endpoint: if profile == "aps" {
Expand Down
1 change: 1 addition & 0 deletions crates/trusted-server-core/src/auction/openrtb/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ fn config_with_endpoint(profile: &str, profile_config: Value, endpoint: &str) ->
providers: BTreeMap::from([(
provider_id.clone(),
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: profile.to_string(),
endpoint: endpoint.to_string(),
Expand Down
3 changes: 3 additions & 0 deletions crates/trusted-server-core/src/auction/orchestrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2618,6 +2618,7 @@ mod tests {
(
ProviderId::from_str(id).expect("should parse fictional provider ID"),
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "standard".to_string(),
endpoint: "https://example.test/openrtb".to_string(),
Expand Down Expand Up @@ -2650,6 +2651,7 @@ mod tests {
(
ProviderId::from_str(id).expect("should parse fictional provider ID"),
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "prebid-server".to_string(),
endpoint: format!("https://{id}.example.test/openrtb"),
Expand Down Expand Up @@ -2686,6 +2688,7 @@ mod tests {
(
ProviderId::from_str(id).expect("should parse fictional provider ID"),
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "aps".to_string(),
endpoint: "https://aps.example/e/pb/bid".to_string(),
Expand Down
82 changes: 80 additions & 2 deletions crates/trusted-server-core/src/auction/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ pub struct ProviderConfig {
pub profile: String,
/// Fixed provider endpoint.
pub endpoint: String,
/// Permit unencrypted HTTP for this provider only. Defaults to false.
/// Use only for explicitly approved demo/test endpoints.
#[serde(default, skip_serializing_if = "is_false")]
pub allow_insecure_http: bool,
/// Optional profile-default timeout override.
#[serde(default)]
pub timeout_ms: Option<u32>,
Expand Down Expand Up @@ -314,7 +318,12 @@ impl AuctionPlan {
"provider `{id}` profile_config must be an object"
)));
}
let endpoint = canonicalize_endpoint(&id, registration.id, &raw.endpoint)?;
let endpoint = canonicalize_endpoint(
&id,
registration.id,
&raw.endpoint,
raw.allow_insecure_http,
)?;
let timeout_ms = raw
.timeout_ms
.unwrap_or(match registration.default_timeout {
Expand Down Expand Up @@ -566,13 +575,14 @@ fn canonicalize_endpoint(
provider_id: &ProviderId,
profile_id: &str,
value: &str,
allow_insecure_http: bool,
) -> Result<CanonicalProviderEndpoint, Report<TrustedServerError>> {
let mut endpoint = Url::parse(value).map_err(|error| {
configuration_error(format!(
"provider `{provider_id}` endpoint must be an absolute HTTPS URL: {error}"
))
})?;
if endpoint.scheme() != "https"
if !(endpoint.scheme() == "https" || (allow_insecure_http && endpoint.scheme() == "http"))
|| endpoint.host_str().is_none()
|| !endpoint.username().is_empty()
|| endpoint.password().is_some()
Expand All @@ -598,6 +608,10 @@ fn canonicalize_endpoint(
Ok(CanonicalProviderEndpoint(endpoint))
}

fn is_false(value: &bool) -> bool {
!*value
}

fn normalize_prebid_server_endpoint(endpoint: &mut Url) {
match endpoint.path() {
"" | "/" => endpoint.set_path("/openrtb2/auction"),
Expand Down Expand Up @@ -644,6 +658,7 @@ mod tests {

fn provider(profile: &str) -> ProviderConfig {
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: profile.to_string(),
endpoint: "https://bid.example/openrtb2/auction".to_string(),
Expand Down Expand Up @@ -1123,6 +1138,69 @@ mod tests {
assert!(AuctionPlan::compile(config(BTreeMap::from([(id("aps"), aps)]))).is_err());
}

#[test]
fn insecure_http_requires_an_explicit_per_provider_opt_in() {
let mut demo = provider("prebid-server");
demo.endpoint = "http://pbs.example.com:8000".to_string();
assert!(
AuctionPlan::compile(config(BTreeMap::from([(id("demo"), demo.clone())]))).is_err()
);
demo.allow_insecure_http = true;
let plan = AuctionPlan::compile(config(BTreeMap::from([(id("demo"), demo.clone())])))
.expect("explicit demo HTTP endpoint should compile");
assert_eq!(
plan.providers()[0].endpoint.as_str(),
"http://pbs.example.com:8000/openrtb2/auction"
);
let mut other = provider("standard");
other.endpoint = "http://other.example/bid".to_string();
assert!(
AuctionPlan::compile(config(BTreeMap::from([
(id("demo"), demo),
(id("other"), other)
])))
.is_err()
);
}

#[test]
fn insecure_http_opt_in_does_not_allow_credentials_fragments_or_other_schemes() {
for endpoint in [
"http://user:pass@pbs.example/bid",
"http://pbs.example/bid#fragment",
"ftp://pbs.example/bid",
"/relative",
] {
let mut raw = provider("prebid-server");
raw.allow_insecure_http = true;
raw.endpoint = endpoint.to_string();
assert!(
AuctionPlan::compile(config(BTreeMap::from([(id("demo"), raw)]))).is_err(),
"should reject {endpoint}"
);
}
}

#[test]
fn insecure_http_default_preserves_existing_provider_wire_shape() {
let legacy = serde_json::to_value(provider("prebid-server")).unwrap();
assert!(legacy.get("allow_insecure_http").is_none());
assert!(
!serde_json::from_value::<ProviderConfig>(legacy)
.unwrap()
.allow_insecure_http
);
let mut opted_in = provider("prebid-server");
opted_in.allow_insecure_http = true;
let encoded = serde_json::to_value(opted_in).unwrap();
assert_eq!(encoded["allow_insecure_http"], true);
assert!(
serde_json::from_value::<ProviderConfig>(encoded)
.unwrap()
.allow_insecure_http
);
Comment on lines +1186 to +1201

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⛏ nitpick — These are the only unwrap() calls in this file (the base had none), and AGENTS.md asks for expect("should ...") in tests too. Scratch-verified: cargo fmt --check, clippy -D warnings, and the three focused tests pass with this applied.

Suggested change
let legacy = serde_json::to_value(provider("prebid-server")).unwrap();
assert!(legacy.get("allow_insecure_http").is_none());
assert!(
!serde_json::from_value::<ProviderConfig>(legacy)
.unwrap()
.allow_insecure_http
);
let mut opted_in = provider("prebid-server");
opted_in.allow_insecure_http = true;
let encoded = serde_json::to_value(opted_in).unwrap();
assert_eq!(encoded["allow_insecure_http"], true);
assert!(
serde_json::from_value::<ProviderConfig>(encoded)
.unwrap()
.allow_insecure_http
);
let legacy = serde_json::to_value(provider("prebid-server"))
.expect("should serialize the default provider");
assert!(legacy.get("allow_insecure_http").is_none());
assert!(
!serde_json::from_value::<ProviderConfig>(legacy)
.expect("should deserialize the legacy provider shape")
.allow_insecure_http
);
let mut opted_in = provider("prebid-server");
opted_in.allow_insecure_http = true;
let encoded =
serde_json::to_value(opted_in).expect("should serialize the opted-in provider");
assert_eq!(encoded["allow_insecure_http"], true);
assert!(
serde_json::from_value::<ProviderConfig>(encoded)
.expect("should deserialize the opted-in provider")
.allow_insecure_http
);

}

#[test]
fn compiler_normalizes_only_prebid_server_origin_and_canonical_paths() {
for (configured, expected) in [
Expand Down
1 change: 1 addition & 0 deletions crates/trusted-server-core/src/auction/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,7 @@ mod tests {

fn provider(profile: &str, routing: RoutingMode) -> ProviderConfig {
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: profile.to_string(),
endpoint: format!("https://{profile}.example.test/openrtb"),
Expand Down
1 change: 1 addition & 0 deletions crates/trusted-server-core/src/auction_config_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ impl AuctionConfig {
(
id,
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "standard".to_string(),
endpoint: format!("https://{name}.example/openrtb2/auction"),
Expand Down
1 change: 1 addition & 0 deletions crates/trusted-server-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,7 @@ formats = [{ width = 300, height = 250 }]
settings.auction.providers.insert(
"aps-main".parse().expect("should parse APS provider ID"),
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "aps".to_string(),
endpoint: "https://aps.example.com/e/pb/bid".to_string(),
Expand Down
1 change: 1 addition & 0 deletions crates/trusted-server-core/src/creative_opportunities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2383,6 +2383,7 @@ mod tests {
providers: BTreeMap::from([(
ProviderId::from_str("pbs-primary").expect("should parse provider ID"),
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "prebid-server".to_string(),
endpoint: "https://pbs.example.test/openrtb".to_string(),
Expand Down
2 changes: 2 additions & 0 deletions crates/trusted-server-core/src/integrations/aps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1930,6 +1930,7 @@ pub fn register(
providers: BTreeMap::from([(
"aps".parse().expect("should parse APS provider ID"),
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "aps".to_string(),
endpoint: default_endpoint(),
Expand Down Expand Up @@ -3103,6 +3104,7 @@ mod tests {
providers: BTreeMap::from([(
"aps".parse().expect("should parse APS provider ID"),
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "aps".to_string(),
endpoint: default_endpoint(),
Expand Down
3 changes: 3 additions & 0 deletions crates/trusted-server-core/src/integrations/prebid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5466,6 +5466,7 @@ external_bundle_sri = "sha384-AAAA"
(
ProviderId::from_str("pbs-primary").expect("should parse provider ID"),
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "prebid-server".to_string(),
endpoint: "https://primary.example.test/openrtb".to_string(),
Expand All @@ -5478,6 +5479,7 @@ external_bundle_sri = "sha384-AAAA"
(
ProviderId::from_str("pbs-secondary").expect("should parse provider ID"),
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "prebid-server".to_string(),
endpoint: "https://secondary.example.test/openrtb".to_string(),
Expand Down Expand Up @@ -8988,6 +8990,7 @@ set = { networkId = 42 }
providers: BTreeMap::from([(
provider_id.clone(),
ProviderConfig {
allow_insecure_http: false,
protocol: "openrtb-2.6".to_string(),
profile: "prebid-server".to_string(),
endpoint: "https://pbs.example/openrtb2/auction".to_string(),
Expand Down
19 changes: 10 additions & 9 deletions docs/guide/auction-orchestration.md
Original file line number Diff line number Diff line change
Expand Up @@ -758,15 +758,16 @@ registered integration selected by `[auction].mediator`.

Common provider fields and defaults:

| Field | Default | Meaning |
| ---------------- | --------------- | -------------------------------------------------------------- |
| `protocol` | Required | `openrtb-2.6` |
| `profile` | `standard` | Typed OpenRTB behavior |
| `endpoint` | Required | Fixed absolute HTTPS endpoint |
| `timeout_ms` | Profile default | PBS 1000 ms, APS 800 ms, standard inherits auction timeout |
| `routing` | `explicit` | `explicit`, or `all_eligible` for non-PBS profiles |
| `profile_config` | `{}` | Profile-owned typed settings |
| `notifications` | No suppression | Common `nurl`/`burl` suppression by all bids or returned seats |
| Field | Default | Meaning |
| --------------------- | --------------- | ---------------------------------------------------------------------------------- |
| `protocol` | Required | `openrtb-2.6` |
| `profile` | `standard` | Typed OpenRTB behavior |
| `endpoint` | Required | Fixed absolute HTTPS endpoint |
| `allow_insecure_http` | `false` | Explicit per-provider HTTP opt-in for approved demos/tests; traffic is unencrypted |
| `timeout_ms` | Profile default | PBS 1000 ms, APS 800 ms, standard inherits auction timeout |
| `routing` | `explicit` | `explicit`, or `all_eligible` for non-PBS profiles |
| `profile_config` | `{}` | Profile-owned typed settings |
| `notifications` | No suppression | Common `nurl`/`burl` suppression by all bids or returned seats |

APS normally uses `all_eligible`, which sends every compatible banner slot but
never another provider's bidder parameters. `explicit` providers receive only
Expand Down
Loading