Conversation
Bid width and height from the mock ad-server mediation response were cast from u64 to u32 with \`as\`, which silently wraps a value above u32::MAX into an unrelated small number that can pass the zero-check and flow onward with corrupted dimensions. Use u32::try_from and skip the bid, consistent with the existing zero-dimension handling. Closes #420 Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
The checked conversion correctly prevents oversized mock ad-server dimensions from entering auction consumers. One medium-severity test gap remains: the oversized fixture wraps to zero under the old implementation, so it does not reproduce the original nonzero-truncation defect.
prk-Jr
left a comment
There was a problem hiding this comment.
Summary
Reviewed commit 6f95f1d5096240b80fe7c44bb25ddd4c07edb3ea.
The checked conversion correctly rejects overflowing dimensions and preserves ordinary, zero, and missing-dimension behavior. One non-blocking test improvement remains.
Non-blocking
- 🤔 [P2] Exercise a nonzero wrapped value in the overflow regression — see inline at
crates/trusted-server-core/src/integrations/adserver_mock.rs:1343.
Validation
All 18 mock-adserver tests pass at the reviewed head. Mutation checks confirm that the original cast passes the current fixture, fails with a u32::MAX + 101 fixture, and the checked conversion passes with that stronger fixture. Scratch source was restored byte-for-byte. These checks establish the test gap; they are not a full release-gate validation of a proposed patch.
CI Status
- browser integration tests: PASS
- integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- CodeQL: PASS
- cargo test (ts CLI, native): PASS
- vitest: PASS
- Analyze (actions): PASS
- Analyze (javascript-typescript): PASS
- cargo fmt: PASS (required)
- cargo test (axum native): PASS
- format-typescript: PASS (required)
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo test (cross-adapter parity): PASS
- cargo test: PASS (required)
- CLAUDE.md symlink guard: PASS
- prepare integration artifacts: PASS
- Analyze (rust): PASS
- format-docs: PASS (required)
- Analyze (javascript-typescript): PASS
aram356
left a comment
There was a problem hiding this comment.
Summary
The production fix is correct and complete. I swept the repository for the JSON-accessor-to-narrowing-as idiom this PR removes and found zero remaining instances — adserver_mock.rs really was the last one, and Prebid, APS, and the generic OpenRTB path already use checked conversions. Skipping the bid rather than clamping is the right call, and the PR body justifies it well.
The blocking issue is in the test, not the fix: test_parse_mediation_response_skips_oversized_dimensions passes against the unfixed code, so it does not pin the regression it was written for.
1 of the inline comments below carries a one-click GitHub
suggestion— use Commit suggestion to apply it as a commit on the PR branch. The remaining comments are prose because they describe follow-ups or trade-offs rather than a mechanical edit.
Blocking
wrench
- Oversized-dimension test passes against the unfixed code — see inline at
crates/trusted-server-core/src/integrations/adserver_mock.rs:1343
Non-blocking
thinking / out of scope / nitpick
- Skip-vs-
unwrap_or(0)diverges from the cited prebid precedent — see inline atcrates/trusted-server-core/src/integrations/adserver_mock.rs:295 - Rejects float-encoded dimensions the shared helper accepts — see inline at
crates/trusted-server-core/src/integrations/adserver_mock.rs:301 - Skip logs drop the offending value — see inline at
crates/trusted-server-core/src/integrations/adserver_mock.rs:297 adserver_mockis the only provider not validating dimensions against requested slot formats — see cross-cutting below
Cross-cutting / body-level findings
-
Out of scope —
adserver_mockis the only bid parser that does not validate dimensions against the requested slot formats. Prebid and the generic OpenRTB path both callresolve_bid_dimensions(crates/trusted-server-core/src/auction/openrtb.rs:195), which rejects withDimensionMismatch; APS checksdimensions.contains(...)(crates/trusted-server-core/src/integrations/aps.rs:671) andcompatible_dimensions(aps.rs:1375).adserver_mock.rs:307only checkswidth == 0 || height == 0, so it still accepts an in-range but never-requested size such as1x1or9999x9999.This is pre-existing and arguably acceptable for a mock provider, so it is not a blocker for this PR. Worth noting that it is the reason this path needed a bespoke dimension check at all: routing it through
parse_optional_bid_dimension+resolve_bid_dimensionslike the other three providers would delete the hand-rolled logic and make the truncation fix fall out for free. A reasonable follow-up issue. -
Completeness check (informational). Two sweeps over all crates —
as_(u64|i64|f64)\(\).*\bas (u|i)(8|16|32)\bandunwrap_or\([0-9]+\) as— returned no hits after this change, so this PR closes the last instance of the pattern rather than one of several.
Verification performed
Scratch-verified in an isolated worktree at 6f95f1d50:
- The PR's two new tests were grafted onto the merge-base commit
6cae7f5da(the unfixed code). Both passed, confirming the oversized test does not detect the bug. - Changing only the test input to
u64::from(u32::MAX) + 101makes that test fail on the base commit (left: 3, right: 1) and pass on this PR's head — a real regression test. - With the suggested fix applied:
cargo fmt --all -- --check, all six clippy aliases (fastly,axum,cloudflare,cloudflare-wasm,spin-native,spin-wasm),cargo test-axum -p trusted-server-core(2669 passed),cargo test-cloudflare,cargo test-spin, and the cross-adapter parity suite (13 passed) all pass.
CI Status
All 20 reported checks PASS.
- cargo test: PASS
- cargo test (axum native): PASS
- cargo test (cross-adapter parity): PASS
- cargo test (ts CLI, native): PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo fmt: PASS
- vitest: PASS
- format-typescript: PASS
- format-docs: PASS
- CodeQL: PASS
- Analyze (rust): PASS
- Analyze (actions): PASS
- Analyze (javascript-typescript): PASS
- integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- browser integration tests: PASS
- prepare integration artifacts: PASS
- CLAUDE.md symlink guard: PASS
The fixture used u32::MAX + 1, which truncates to zero under the original as u32 cast and is already caught by the separate zero-dimension check, so the test passed even without the fix. Use u32::MAX + 101 so the test actually fails against the old truncating cast, and log the offending raw value when a width or height is rejected. Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
prk-Jr
left a comment
There was a problem hiding this comment.
Summary
Tight, well-scoped fix that does exactly what #420 asked: the truncating as u32 casts on bid w/h in parse_mediation_response become checked u32::try_from conversions that skip the bid on overflow, so a dimension above u32::MAX can no longer wrap into a small, plausible-looking value that sails past the zero-check. The PR picks the safer of the two options the issue offered (skip rather than clamp), states that choice explicitly, and matches the existing zero-dimension behaviour and the u32::try_from(v).ok() precedent in prebid.rs. No blocking findings.
1 of the inline comments below carries a one-click GitHub
suggestion— use Commit suggestion to apply it as a commit on the PR branch. The remaining comments are prose only.
Verification performed
Reviewed against head 855b49bf7d37b95a1c8d91b0aa0635811bfe8ed9 in an isolated worktree:
cargo test -p trusted-server-core --lib integrations::adserver_mock→ 18 passed, 0 failed.- Differential check: reverted only the production hunk back to the
as u32casts while keeping the new tests →test_parse_mediation_response_skips_oversized_dimensionsFAILS (left: 3, right: 1). The regression test genuinely pins the fix. cargo fmt --all -- --checkclean;cargo clippy-fastlyexit 0.- Behaviour for missing / negative / float-encoded
wis unchanged (skip before, skip after) — only the debug log wording differs, so #420's "missing dimension keeps its current behavior" criterion holds. bid["w"]on a missing key yieldsValue::Nullvia serde_json'sIndeximpl, so the{:?}in the newlog::debug!cannot panic.
Non-blocking
⛏ nitpick
oversized_widthis also used as thehvalue — see inline atcrates/trusted-server-core/src/integrations/adserver_mock.rs:1347-1368(one-click suggestion)
📝 note
- No test pins the accepting side of the boundary (
w == u32::MAX) — see inline atcrates/trusted-server-core/src/integrations/adserver_mock.rs:1394
👍 praise
- Regression test was hardened to actually fail without the fix — see inline at
crates/trusted-server-core/src/integrations/adserver_mock.rs:1342
Cross-cutting / body-level findings
-
🌱
trusted-server-corenow has three divergent OpenRTB bid-dimension parsers — after this PR the crate parses a bidw/hthree different ways:Site Behaviour crates/trusted-server-core/src/auction/openrtb.rs:167parse_optional_bid_dimensionu32::try_from, plus anf64fallback for float-encoded dimensions, plus a> 0filter, returningBidRejectionReason::InvalidBid. Alreadypub(crate)and consumed byprebid.rs.crates/trusted-server-core/src/integrations/prebid.rs:3039-3048hand-rolled as_u64().and_then(u32::try_from).unwrap_or(0)crates/trusted-server-core/src/integrations/adserver_mock.rs:295-314(this PR)hand-rolled let-else guards + a separate zero-check parse_optional_bid_dimensionalready subsumes both the overflow guard and the zero-check this function now does in two steps, and it additionally accepts a float-encoded728.0thatadserver_mocksilently drops.Explicitly not asking for this in this PR. The return type is
Result<Option<u32>, BidRejectionReason>whileparse_mediation_responsereturnsAuctionResponse, so the adaptation is non-mechanical, and AGENTS.md's "minimal changes" rule is on your side here.adserver_mockis a dev/mock integration, so the divergence is low-risk. Worth a follow-up issue rather than scope creep.
CI Status
- integration tests: PASS
- browser integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- prepare integration artifacts: PASS
- cargo test: PASS
- cargo test (axum native): PASS
- cargo test (ts CLI, native): PASS
- cargo test (cross-adapter parity): PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo fmt: PASS
- vitest: PASS
- format-typescript: PASS
- format-docs: PASS
- CLAUDE.md symlink guard: PASS
- CodeQL: PASS
- Analyze (rust): PASS
- Analyze (actions): PASS
- Analyze (javascript-typescript): PASS
- Analyze (javascript-typescript): PASS
All 20 reported checks pass; none pending, none failed.
aram356
left a comment
There was a problem hiding this comment.
Summary
Both blocking findings from the previous pass are resolved, and I confirmed that by re-running the original repro against the new merge-base rather than taking the diff at face value: the strengthened test now fails on the unfixed parser (left: 3, right: 1) and passes on the fix. It is a genuine regression test. The two merges from main are mechanical and touch nothing related to this code.
I then mutation-tested each production guard independently:
| Mutation | Result |
|---|---|
Revert the width guard to as u32 |
caught (left: 2) |
Revert the height guard to as u32 |
caught (left: 2) |
Delete || height == 0 from the zero-check |
not caught - all 2763 core tests still pass |
That last row is the one new finding. The rest are non-blocking observations and follow-ups.
1 of the inline comments below carries a one-click GitHub
suggestion- use Commit suggestion to apply it as a commit on the PR branch. The others are prose because they propose follow-ups rather than a mechanical edit.
Blocking
wrench
skips_zero_dimensionsdoes not cover zero height - see inline atcrates/trusted-server-core/src/integrations/adserver_mock.rs:1417
Non-blocking
thinking / seedling
- First
debug!inintegrations/to interpolate a raw upstream value - see inline atcrates/trusted-server-core/src/integrations/adserver_mock.rs:297 - Untested boundaries:
u32::MAXexactly, negatives, float-encoded dimensions - see inline atcrates/trusted-server-core/src/integrations/adserver_mock.rs:1347 - Silent degrade to
no_bidis a monitoring blind spot - see cross-cutting below
Cross-cutting / body-level findings
-
Seedling - an all-rejected mediation response is indistinguishable from genuine no-demand. When every bid is skipped,
parse_mediation_responsereturnsAuctionResponse::no_bid(...)atadserver_mock.rs:352, which is byte-identical to the mediator having had no demand: sameNoBidstatus, same emptybids, same emptymetadata. Thelog::info!atadserver_mock.rs:396reports the count after filtering, so it prints 0 in both cases, and the per-bidlog::debug!lines are filtered out at the defaultInfolevel. A mediator that starts emitting oversized dimensions on every bid is therefore invisible in a default deployment.The crate already ships the tool for this:
ResponseAdmissionDiagnosticsandBidRejectionReasonatcrates/trusted-server-core/src/auction/openrtb.rs:35-87, wired up by Prebid atcrates/trusted-server-core/src/integrations/prebid.rs:2259-2302, which attaches bounded rejection counts toresponse.metadata. Both types arepub(crate)in the same crate, so the change is roughly threediagnostics.record(BidRejectionReason::InvalidBid)calls at thecontinuesites plus oneattach_tobefore returning.This is a pre-existing property rather than something this PR introduces, so it is not a blocker. Raising it because this PR widens the set of silently-dropped bids. One caveat on how much it would buy you: I found the metadata written by Prebid but could not locate a reader for
metadata["response_admission"], so I cannot confirm it currently reaches a dashboard. Worth checking before investing in it.
Verification performed
Scratch-verified in an isolated worktree at 965449577 (merge-base a4e01eb55):
- Grafted the current tests onto the unfixed merge-base:
skips_oversized_dimensionsFAILED (left: 3, right: 1),skips_zero_dimensionspassed. The strengthened test pins the fix. - Mutation tests on the production guards, as tabulated above. Both
u32::try_fromguards are independently covered; theheight == 0half of the zero-check is not covered by any test in the repository. - With the suggested fix applied, the zero-check mutation fails loudly with
left: ["sidebar", "footer"], right: ["footer"]. - Full CI gate with the suggestion applied:
cargo fmt --all -- --check(the suggestion is rustfmt-canonical), all eight clippy aliases including the newly addedclippy-cliandclippy-codegen,cargo test-axum -p trusted-server-core(2763 passed),cargo test-cloudflare,cargo test-spin, and the cross-adapter parity suite (14 passed). Post-verify drift check: no drift.
CI Status
All 20 reported checks PASS. They were mid-flight when this pass started and were re-checked at the end.
- cargo test: PASS
- cargo test (axum native): PASS
- cargo test (cross-adapter parity): PASS
- cargo test (ts CLI, native): PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo fmt: PASS
- vitest: PASS
- format-typescript: PASS
- format-docs: PASS
- CodeQL: PASS
- Analyze (rust): PASS
- Analyze (actions): PASS
- Analyze (javascript-typescript): PASS
- integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- browser integration tests: PASS
- prepare integration artifacts: PASS
- CLAUDE.md symlink guard: PASS
Cover zero height separately from missing dimensions, accept u32::MAX exactly, skip negative dimensions, rename the shared oversized dimension variable, and document why the raw upstream value is logged with Debug formatting. Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
aram356
left a comment
There was a problem hiding this comment.
Summary
All three findings from the previous pass are resolved, and I verified each by mutation testing rather than by reading the diff. The production fix is correct, every guard in the dimension-validation path is now pinned by a test that fails when the guard is removed, and the full CI-equivalent gate is green.
Two non-blocking items remain, both raised before and neither in scope for a change the issue scoped to one truncating cast. I have measured the impact of both rather than describing them, so you can judge the priority yourself.
Mutation battery
Every mutation below was applied to the production code in an isolated worktree, with the full adserver_mock test module run against each.
| Mutation | Previous pass | This pass |
|---|---|---|
Revert the width guard to as u32 |
caught | caught |
Revert the height guard to as u32 |
caught | caught |
Delete the width == 0 half of the zero-check |
not tested | caught |
Delete the height == 0 half of the zero-check |
survived, 2763 tests green | caught |
| Delete the zero-check entirely | not tested | caught |
The fourth row was the blocking finding from the last pass, and it is now closed.
I also probed the two new boundary tests adversarially, because a boundary test that pins nothing is worse than no test at all:
accepts_u32_max_dimensions— I introduced an off-by-one by narrowing the guard tofilter(|v| *v < u64::from(u32::MAX)), simulating a<where<=belongs. The test failed. It genuinely pins the accept boundary, and it asserts the retained values rather than only the count.skips_negative_dimensions— I swappedas_u64()foras_i64().map(|v| v as u32), the plausible refactor that would start admitting negatives. That test and the oversized test both failed.
Also worth noting: moving the missing-dimensions bid to its own skyscraper slot was a good call, and better than what I suggested. It keeps the three rejection reasons independently attributable instead of collapsing two of them onto sidebar.
Non-blocking
refactor
- Float-encoded dimensions are silently dropped, contradicting the shared helper — see inline at
crates/trusted-server-core/src/integrations/adserver_mock.rs:297
Cross-cutting / body-level findings
-
Seedling — an all-rejected mediation response is byte-identical to genuine no-demand. This cannot anchor to a line inside the diff (the return is at
adserver_mock.rs:360, outside both hunks), so it lives here.I measured it rather than asserting it. Two responses through
parse_mediation_response: (A) an emptyseatbid, and (B) three bids priced $5.00, $9.00 and $7.00, all with corrupt dimensions.A genuine-no-demand: status=NoBid bids=0 metadata={} B all-3-rejected: status=NoBid bids=0 metadata={} INDISTINGUISHABLE: trueSame status, same empty bid vector, same empty metadata. The
log::info!atadserver_mock.rs:404reports the count after filtering, so it prints 0 in both cases, and the per-bidlog::debug!lines are filtered out at the defaultInfolevel (adapter-fastly/src/logging.rs:47-50). A mediator that starts emitting corrupt dimensions on every bid produces no distinguishable signal.The crate already ships the tool:
ResponseAdmissionDiagnosticsandBidRejectionReasonatcrates/trusted-server-core/src/auction/openrtb.rs:35-87, wired up by Prebid atprebid.rs:2259-2302, which attaches bounded rejection counts toresponse.metadata. Both arepub(crate)in the same crate, so the change is threerecord()calls at thecontinuesites plus oneattach_tobefore returning.Two reasons I am keeping this a seedling rather than asking for it here. First,
adserver_mockis documented as the mediator "used for auction mediation in dev/testing" (trusted-server.example.toml:654) and defaults toenabled = false, so a blind spot here is not a production revenue risk. Second, I looked for a consumer ofmetadata["response_admission"]and could not find one — Prebid writes it but I could not locate a reader, so adopting it may be wiring up a producer with no consumer. Worth confirming that before investing. Filing it as a follow-up issue would be reasonable.
Verification performed
Scratch-verified in an isolated worktree at bf96305b9. The merge-base is unchanged at a4e01eb55 (zero new commits on main since the last pass), so the previous pass's merge analysis still holds.
cargo fmt --all -- --check: pass- All eight clippy aliases, including the recently added
clippy-cliandclippy-codegen: pass cargo test-axum -p trusted-server-core: 2765 passedcargo test-cloudflare: 23 passed.cargo test-spin: 38 passedcargo test --manifest-path crates/trusted-server-integration-tests/Cargo.toml --test parity: 14 passed- Mutation battery and boundary-test probes as tabulated above
CI Status
All 20 reported checks PASS.
- cargo test: PASS
- cargo test (axum native): PASS
- cargo test (cross-adapter parity): PASS
- cargo test (ts CLI, native): PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo fmt: PASS
- vitest: PASS
- format-typescript: PASS
- format-docs: PASS
- CodeQL: PASS
- Analyze (rust): PASS
- Analyze (actions): PASS
- Analyze (javascript-typescript): PASS
- integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- browser integration tests: PASS
- prepare integration artifacts: PASS
- CLAUDE.md symlink guard: PASS
Reuse parse_optional_bid_dimension so integral float dimensions are accepted like in the other auction providers, and pin that behavior with a test. Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Build the requested-dimension index from the auction request and run mediated bids through resolve_bid_dimensions, so the mock provider rejects unrequested impressions and mismatched sizes and infers missing dimensions like the other auction providers. Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
Signed-off-by: dhruv8sh <dhruv8sh@proton.me>
|
Squash-merged into |
Summary
u64tou32withas, which silently wraps values aboveu32::MAXinto small, plausible-looking dimensions that pass the zero-check.u32::try_fromand skip the bid (log at debug) when a dimension doesn't fit, consistent with the existing zero-dimension handling.Changes
crates/trusted-server-core/src/integrations/adserver_mock.rsas u32casts on bidw/hwithu32::try_from, skipping the bid on overflow; add tests for oversized and zero/missing dimensionsCloses
Closes #420
Test plan
cargo test-fastly && cargo test-axumcargo clippy-fastly && cargo clippy-axumcargo fmt --all -- --checkcd crates/trusted-server-js/lib && npx vitest runcd crates/trusted-server-js/lib && npm run formatcd docs && npm run formatcargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1fastly compute serveChecklist
unwrap()in production code — useexpect("should ...")tracingmacros (notprintln!)