Uh oh!
There was an error while loading. Please reload this page.
Make auction creative rewriting optional - #916
Conversation
aram356
left a comment
There was a problem hiding this comment.
Summary
The runtime branch itself is well scoped: sanitization remains mandatory, rewriting is gated after sanitization, both modes have substantive tests, and /first-party/proxy remains independent. I am requesting changes for rollback compatibility and two operator-facing configuration/privacy contracts described in the inline comments.
Non-blocking
🏕 camp site
- Update the internal auction README:
crates/trusted-server-core/src/auction/README.md:139,:251, and:382still describe creative rewriting as unconditional. Please consider updating those passages to distinguish mandatory sanitization from default-on, configurable rewriting.
📌 out of scope
- Track the pre-existing
iframe[srcdoc]sanitizer gap: the generic handler atcrates/trusted-server-core/src/creative.rs:385neither removes nor recursively sanitizessrcdoc, while the normal renderer grantsallow-scriptsandallow-same-originatcrates/trusted-server-js/lib/src/core/render.ts:14. This exists at the base SHA and should not block this PR, but it deserves a security follow-up that removessrcdocand adds regression coverage through both rewrite modes.
👍 praise
- The implementation keeps sanitization strictly before the configuration branch, avoids logging creative contents, covers default/disabled behavior and legacy blob loading, and verifies that proxy HTML/CSS rewriting is independent. No new dependency, OS API, or WASM-incompatible construct is introduced.
CI Status
- fmt and all adapter/target clippy checks: PASS
- Rust tests and builds (Fastly, Axum, Cloudflare, Spin, parity, CLI): PASS
- JS formatting and Vitest: PASS
- integration, browser, Fastly EC lifecycle, and CodeQL checks: PASS
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
ChristianPavilonis
commented
Jul 17, 2026
Implemented and pushed the requested review fixes in Also addressed the non-blocking internal auction README cleanup. The pre-existing nested Validation completed locally:
|
prk-Jr
left a comment
There was a problem hiding this comment.
Summary
Adds a default-true [auction].rewrite_creatives setting: creative sanitization for POST /auction stays mandatory, while first-party resource/click rewriting and TSJS injection become optional. Backward-compatibility handling (default omitted from serialized/legacy blobs, explicit false preserved, legacy schema round-trip, EdgeZero env-overlay pre-existing-leaf quirk) is thorough and well tested. No blocking issues found.
Non-blocking
⛏ nitpick
- "unre-written" wording:
docs/guide/configuration.mdanddocs/guide/creative-processing.mdrepeat the hyphenation "unre-written" (e.g. "sanitized but unre-written HTML") several times. Suggest "not rewritten" or "unrewritten" for readability — no behavior impact.
👍 praise
- Backward-compatibility test coverage: the
skip_serializing_ifdefault-omission design plus tests acrosssettings.rs(TOML omitted/explicit-false),config_payload.rs(legacy JSON blob round-trip and legacy-schema deserialization), and the new CLI integration test (config_env_overlay.rs, proving the EdgeZero "env overlay only overrides pre-existing leaves" quirk is handled) directly cover the real rollback/migration edge cases operators will hit. Theproxy.rstest proving/first-party/proxyrewriting stays independent of the new flag is a good isolation check too.
📝 note
- Change table is stale: the PR description's file table lists 12 files; the actual diff touches 16, including
creative.rs(see inline comment — an independent exclusion-matching bug fix bundled here),crates/trusted-server-cli/tests/config_env_overlay.rs,crates/trusted-server-core/src/auction/README.md, anddocs/guide/cli.md. Worth syncing the table with the real diff before merge.
CI Status
- fmt: PASS
- clippy (fastly/axum/cloudflare native+wasm/spin native+wasm): PASS
- rust tests (fastly/axum/cloudflare/spin/cross-adapter parity/CLI): PASS
- js tests (vitest): PASS
- js/docs format: PASS
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
aram356
left a comment
There was a problem hiding this comment.
Summary
Narrowly-scoped, backward-compatible feature with strong test coverage (default, explicit false, TOML parse, blob round-trip, legacy-schema acceptance, CLI env overlay, and a regression test proving /first-party/proxy rewriting is unaffected) and careful documentation. Two items need attention before merge: an undeclared drive-by behavior change in to_abs, and the fact that the rollback-critical skip_serializing_if carries no in-code rationale. Both are inline.
Blocking
🔧 wrench
- Undeclared behavior change in
to_abs: moving the exclusion check onto the normalized URL is a real fix —is_excludednever matched protocol-relative URLs becauseurl::Url::parsefails on//host/path— but it is unrelated torewrite_creativesand appears in neither the PR description nor the CHANGELOG. It also silently tightens the/first-party/proxytsurlvalidator (proxy.rs:1633), which is untested. (crates/trusted-server-core/src/creative.rs:53-73) skip_serializing_ifis load-bearing but unexplained:AuctionConfigisdeny_unknown_fields, so omitting the default is what keeps pushed blobs readable by an older binary during rollback. That rationale exists only in docs and a TOML comment, so a future cleanup of the attribute silently breaks rollback. (crates/trusted-server-core/src/auction_config_types.rs:15-19)
❓ question
- Client-side behavior with
rewrite_creatives = false: disabling also dropsdata-tsclickand thetsjs-unified.min.jsruntime injection. The docs frame this purely as a privacy trade-off. Is the creative render bridge tolerant of a missing runtime, or does the creative fail to size/render? If rendering depends on it, that is a functional consequence and belongs in the warning block indocs/guide/creative-processing.md. ts config diffwith the leaf present: raised inline ontrusted-server.example.toml:119.
Non-blocking
♻️ refactor
- Gate the flag inside
creative:crates/trusted-server-core/src/auction/formats.rs:256-268(inline). - Warn when the privacy default is off:
crates/trusted-server-core/src/auction/formats.rs:258(inline).
🤔 thinking
- Hand-mirrored
LegacyAuctionConfigwill drift:crates/trusted-server-core/src/config_payload.rs:52-72(inline). - CLI test does not pin the working directory:
crates/trusted-server-cli/tests/config_env_overlay.rs:36-63(inline).
📌 out of scope
/first-party/proxysign handler bypassesto_absfor protocol-relative URLs: the//-prefixed branch builds the absolute URL inline and never callsto_abs, so it skips the exclusion check entirely — the exact inconsistency this PR fixes on the creative path. Pre-existing; worth a follow-up issue now that the sibling path was corrected. (crates/trusted-server-core/src/proxy.rs:1624-1632)[debug].inject_adm_for_testingembeds raw, unsanitizedadm: correctly documented here as debug-only, but it remains the one creative path with neither sanitization nor rewriting. Follow-up issue. (crates/trusted-server-core/src/publisher.rs:2144-2147)
⛏ nitpick
rewrite_modestring is unnecessary:crates/trusted-server-core/src/auction/formats.rs:264-268(inline).
Verification notes
- The docs change from "env vars baked at build time" to "typed CLI overlay" was verified accurate: no
TRUSTED_SERVER__handling remains incrates/trusted-server-core/build.rs. [debug].inject_adm_for_testingreferenced in the new docs exists as described (settings.rs:1923).convert_to_openrtb_responsehas exactly two production callers, both inauction/endpoints.rs(219, 340), so the new flag's blast radius matches what the docs claim.
CI Status
Taken from the PR's own checks — not re-run locally.
- fmt: PASS
- clippy / CodeQL (
Analyze (rust),Analyze (javascript-typescript),Analyze (actions)): PASS - rust tests (
cargo test, axum native, cloudflare, spin, ts CLI native, cross-adapter parity): PASS - js tests (vitest), format-typescript, format-docs: PASS
- integration tests, Fastly EC lifecycle, browser integration tests: still pending at review time
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Allow operators to retain sanitizer-accepted external URLs in POST /auction adm while preserving mandatory server-side sanitization and the existing default behavior.
51a374d to
e6edc23Compare
aram356
left a comment
There was a problem hiding this comment.
Summary
Re-review after the "Address creative rewriting review feedback" commits. Every finding from the previous review was addressed and verified (including that the new config-load log::warn! fires on the production blob path, not just TOML parse). One new blocking gap remains: the /first-party/sign protocol-relative branch does not honor exclude_domains, which is inconsistent with this PR's own "Fixed" changelog entry. Details inline on crates/trusted-server-core/src/proxy.rs.
Blocking
🔧 wrench
/first-party/signbypassesexclude_domainsfor protocol-relative URLs (crates/trusted-server-core/src/proxy.rs:1642-1648)The CHANGELOG adds: "Protocol-relative creative URLs now honor
rewrite.exclude_domains… excluded absolute URLs submitted to/first-party/signare rejected." But the sign handler's//branch buildsabsinline (format!("{}:{}", default_scheme, trimmed)) and never runs the exclusion check, while theelsebranch routes throughto_abs, which now does. Net result:https://cdn.example/asset.json an excluded host → rejected (proxy_sign_rejects_excluded_absolute_urlproves it);//cdn.example/asset.json the same excluded host → still signed and proxied.
build_proxy_url_with_extras→build_signed_url_forperforms no exclusion check either, so nothing downstream catches it. The two changelog clauses are each individually accurate (one is scoped to "absolute"), but the asymmetry undercuts the protocol-relative headline.The
//branch can't simply callto_abs— it intentionally preserves the request scheme rather than hardcodinghttps:— so the fix is a uniform post-check afterabsis computed, before signing:if settings.rewrite.is_excluded(&abs){returnErr(Report::new(TrustedServerError::Proxy{message:"unsupported url".to_string(),}));}
Either apply that (and add a
//-host case toproxy_sign_rejects_excluded_absolute_url), or narrow the changelog to state the sign path enforces exclusions only for absolute URLs and explain why protocol-relative is intentionally exempt.
Non-blocking
🌱 seedling
[debug].inject_adm_for_testingremains the one creative path with neither sanitization nor rewriting: pre-existing and correctly documented here as debug-only. Now that auctionadmis uniformly sanitized, this raw-admdebug path is the remaining asymmetry — worth a follow-up issue so it is not mistaken for a production-safe toggle. (crates/trusted-server-core/src/publisher.rs:2144-2147)
📝 note
- Prior review resolutions confirmed:
to_absbehavior change now carries a### FixedCHANGELOG entry plus proxy-validator test coverage;skip_serializing_ifhas an explanatory doc comment;config diffno-op is asserted; the sanitize+conditional-rewrite policy is centralized increative::process_auction_creative; the disabled-rewritewarnfires viafinalize_deserializedon both TOML and blob load paths;LegacyAuctionConfigcarries a drift-guard comment; the CLI test pins its working directory. - New public API surface is intentional:
creativeispub mod, soprocess_auction_creativeis public — consistent with itspubsiblingssanitize_creative_html/rewrite_creative_html.
CI Status
Taken from the PR's own checks (full run, all green):
- fmt: PASS
- clippy / CodeQL (
Analyze (rust),Analyze (javascript-typescript),Analyze (actions)): PASS - rust tests (
cargo test, axum native, cloudflare, spin, ts CLI native, cross-adapter parity): PASS - integration tests, Fastly EC lifecycle, browser integration tests: PASS
- js tests (vitest), format-typescript, format-docs: PASS
aram356
left a comment
There was a problem hiding this comment.
Inline version of the blocking finding from the prior change-request review (that finding could not be anchored inline because the sign-handler lines are unchanged by this PR; anchoring it here on the new test, which is what needs extending).
Uh oh!
There was an error while loading. Please reload this page.
prk-Jr
left a comment
There was a problem hiding this comment.
Summary
Makes winning-bid creative rewriting configurable via a default-true [auction].rewrite_creatives, keeps sanitization mandatory, and fixes protocol-relative URL matching against rewrite.exclude_domains. The serialization design (default omitted, explicit false retained) and its rollback rationale are solid and well covered by tests. Three blocking issues: the setting does not reach the server-side auction (SSAT) inline adm render path, two guides describe that path incorrectly, and the protocol-relative exclusion fix does not reach /first-party/sign.
Blocking
🔧 wrench
[auction].rewrite_creativesis not honored on the SSAT inlineadmpath:build_bid_mapcallssanitize_creative_htmlthenrewrite_inline_creative_htmlunconditionally (crates/trusted-server-core/src/publisher.rs:3259-3274). That is the production server-side-auction render path since #899 and it is what populateswindow.tsjs.bids. Withrewrite_creatives = false,POST /auctionreturns sanitized-only markup while page-rendered SSAT creatives still receive first-party proxy/click URLs anddata-tsclick. Either gate that rewrite call on the setting (keeping sanitization unconditional), or scope the setting name and docs explicitly toPOST /auction. No test covers the SSAT path under the new flag.Two guides describe the page-bids
admpath incorrectly (docs/guide/creative-processing.md:80-82,docs/guide/auction-orchestration.md:583-584): both state the path is "debug-only[debug].inject_adm_for_testing" and "may include rawadm".inject_adm_for_testingis passed asinclude_debug_bid(publisher.rs:3698) and gates only thedebug_biddiagnostic blob (publisher.rs:3281). Theadminsertion atpublisher.rs:3259-3274is unconditional, and the value is always sanitized and rewritten — never raw.Protocol-relative exclusion fix does not reach
/first-party/sign(crates/trusted-server-core/src/proxy.rs:1642-1655): thetrimmed.starts_with("//")branch builds the absolute URL from the request scheme and bypassesto_absentirely, so nois_excludedcheck runs.https://excluded.example/a.pngis now rejected (covered by the new test), but//excluded.example/a.pngis still signed. Creative HTML leaves that URL direct while the signing endpoint mints a proxy token for it. Add the exclusion check to that branch and a matching protocol-relative test.
Non-blocking
🤔 thinking
- 502 for client-supplied URLs at
/first-party/sign:unsupported urlmaps toBAD_GATEWAY;400fits client input better. Pre-existing, but the new test now pins 502 as the contract.
👍 praise
- Rollback-safe serialization:
skip_serializing_ifplus thedeny_unknown_fieldsLegacyAuctionConfigmirror test is the right way to prove a defaulted field stays readable by the previous binary schema. to_absexclusion fix addresses the actual root cause —is_excludedparses withurl::Url::parse, which fails on//host/pathand silently returnedfalse. Dropping the explicitdata:/javascript:/blob:skip-list is safe: the finalelsereturnsNonefor all of them.
CI Status
All 19 checks pass (fmt, clippy/check across fastly, axum, cloudflare, spin, parity, CLI tests, vitest, docs format, integration and browser suites).
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
ChristianPavilonis
commented
Jul 28, 2026
Implemented and pushed the remaining review fixes in
Validation completed locally:
|
# Conflicts: # CHANGELOG.md # crates/trusted-server-core/src/auction/formats.rs # crates/trusted-server-core/src/auction/orchestrator.rs # crates/trusted-server-core/src/auction_config_types.rs # crates/trusted-server-core/src/config_payload.rs # crates/trusted-server-core/src/settings.rs # docs/guide/auction-orchestration.md # docs/guide/configuration.md # docs/guide/creative-processing.md # trusted-server.example.toml
# Conflicts: # CHANGELOG.md # crates/trusted-server-core/src/creative.rs # docs/guide/configuration.md # docs/guide/creative-processing.md
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
PR #916 was squash-merged into main and this PR was retargeted to main, so the previously merged base content re-conflicted without shared history. Every conflicted region resolves to this branch's version, which already contains the base content plus this PR's changes; the only main-side delta adopted inside a conflicted file is the pub(crate) visibility on process_auction_creative. Main's GPT diagnostics overlay (#974) and lint scope (#984) changes merged cleanly.
Summary
Changes
CHANGELOG.mdcrates/trusted-server-cli/tests/config_env_overlay.rscrates/trusted-server-core/src/auction/README.mdcrates/trusted-server-core/src/auction/endpoints.rs/auctionsanitization and rewrite behavior.crates/trusted-server-core/src/auction/formats.rscrates/trusted-server-core/src/auction/orchestrator.rscrates/trusted-server-core/src/auction_config_types.rscrates/trusted-server-core/src/config_payload.rscrates/trusted-server-core/src/creative.rscrates/trusted-server-core/src/proxy.rscrates/trusted-server-core/src/publisher.rscrates/trusted-server-core/src/settings.rsdocs/guide/auction-orchestration.mddocs/guide/cli.mddocs/guide/configuration.mddocs/guide/creative-processing.mdtrusted-server.example.tomlCloses
Closes#914
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 servecargo test-cloudflare,cargo test-spin, all target-matched clippy aliases, and./scripts/test-cli.shChecklist
unwrap()in production code — useexpect("should ...")tracingmacros (notprintln!) — N/A; this project requireslogmacros.