Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Image Optimizer configuration now rejects profile and profile-set names within the same map that collide after trimming, even when their values match, instead of silently overwriting an entry. Empty names continue to be discarded.
- Browser integration bundles now share context providers and the configured log level with TSJS core (#1196). With Permutive enabled and cohorts available, `tsjs.requestAds()` now sends `config.permutive_segments` to `/auction`; the server retains that key only when `auction.allowed_context_keys` includes `"permutive_segments"`. This restores a previously inactive data flow, not a consent gate: review publisher consent handling before relying on it. Integration logs now follow `tsjs.setConfig` and `tsjs.log.setLevel`, and creative no longer raises the default `warn` level to `info`. The `?tsdebug=1` query parameter or `localStorage['tsdebug'] = '1'` intentionally enables `debug` logging across all bundles when creative installs, overriding any log level set earlier through `tsjs.setConfig` or `tsjs.log.setLevel`. Later calls to either API can change the level again.
- `[auction].allowed_context_keys` now serializes in sorted, deduplicated order, so ESI template-cache fingerprints and `ts config diff`/`push` envelope hashes are stable across loads. Template fingerprints also sort object keys independently of `serde_json/preserve_order`. Existing envelopes may show a one-time allowlist reorder after upgrading; push once to settle it. The updated fingerprint format causes one template-cache miss per cached page after deployment.
- TSJS-generated envelopes now send `trustedServer.params.storedRequest: false`, preventing accidental PBS stored lookups without suppressing eligible non-PBS demand. PBS filters unusable impressions after overrides; explicit `true` and omission in valid envelopes retain inline-first stored fallback. A malformed envelope disables stored fallback for the entire slot, including independent direct demand left unusable after overrides. Publisher intent survives repeated and refresh auctions. Deploy compatible server admission everywhere before serving the new JS, and retain it during rollback while cached clients remain. See the Prebid deployment guide.
Expand Down
81 changes: 79 additions & 2 deletions crates/trusted-server-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ impl<'de> Deserialize<'de> for TrustedServerAppConfig {
D: Deserializer<'de>,
{
let mut settings = Settings::deserialize(deserializer)?;
settings.normalize_deserialized();
settings
.normalize_deserialized()
.map_err(serde::de::Error::custom)?;
Ok(Self { settings })
}
}
Expand Down Expand Up @@ -796,6 +798,79 @@ formats = [{ width = 300, height = 250 }]
);
}

#[test]
fn app_config_image_optimizer_rejects_trimmed_key_collisions() {
for (section, first, second) in [
(
r#"
[image_optimizer.profile_sets.default_images]
default_profile = "medium"
[image_optimizer.profile_sets.default_images.profiles]
medium = "width=100"
" medium" = "width=200"
"#,
"medium",
" medium",
),
(
r#"
[image_optimizer.profile_sets.default_images.profiles]
default = "width=100"
[image_optimizer.profile_sets." default_images".profiles]
default = "width=200"
"#,
"default_images",
" default_images",
),
] {
let toml = crate_test_settings_str() + section;
let raw: Settings =
toml::from_str(&toml).expect("should deserialize unnormalized settings");
let value = serde_json::to_value(raw).expect("should serialize unnormalized settings");
let toml_error = toml::from_str::<TrustedServerAppConfig>(&toml)
.expect_err("should reject colliding app-config keys");
let json_error = serde_json::from_value::<TrustedServerAppConfig>(value)
.expect_err("should reject colliding app-config keys");

for message in [toml_error.to_string(), json_error.to_string()] {
for expected in [
"image_optimizer.profile_sets",
"default_images",
"collide after trimming",
&format!("{first:?}"),
&format!("{second:?}"),
] {
assert!(
message.contains(expected),
"should preserve collision context with {expected:?}: {message}"
);
}
}
}
}

#[test]
fn app_config_image_optimizer_normalizes_non_colliding_keys() {
let toml = crate_test_settings_str()
+ r#"
[image_optimizer.profile_sets." default_images "]
default_profile = " medium "
[image_optimizer.profile_sets." default_images ".profiles]
" medium " = " width=100 "
"#;
let app_config: TrustedServerAppConfig =
toml::from_str(&toml).expect("should deserialize non-colliding app config");
let profile_set = &app_config.settings().image_optimizer.profile_sets["default_images"];
assert_eq!(
profile_set.default_profile, "medium",
"should trim the default profile reference"
);
assert_eq!(
profile_set.profiles["medium"], "width=100",
"should trim profile keys and values"
);
}

#[test]
fn push_validation_accepts_secret_key_names() {
let mut settings = valid_settings();
Expand Down Expand Up @@ -919,7 +994,9 @@ formats = [{ width = 300, height = 250 }]
}));
settings.proxy.asset_routes.push(route);

settings.normalize_deserialized();
settings
.normalize_deserialized()
.expect("should normalize settings without key collisions");
let serialized = serde_json::to_string(&settings).expect("should serialize settings");

for legacy_store in [
Expand Down
Loading
Loading