20260514-172210 Review fixes for #621#705
Open
aram356 wants to merge 3 commits into
Open
Conversation
Addresses review finding on #621 (P2-2): handle_auction parsed the request body with serde_json::from_slice(&req.take_body_bytes()) and no size limit, so an authenticated client could buffer arbitrary bytes into the Fastly Compute worker before any check. Reject oversized bodies with 413 — Content-Length precheck for well-behaved clients, post-read length check for clients that lie about or omit the header. Cap is 256 KiB — comfortable headroom for realistic Prebid-derived auctions. The check is inlined (rather than calling the content_length_exceeds_limit helper that batch_sync uses) because the HTTP-types migration in flight in http_util.rs is moving away from fastly::Request, and a shared helper would either fight that direction or force compat conversions at every call site.
Addresses review finding on #621 (P2-8): handle_proxy took 7 arguments including &self and was guarded by #[allow(clippy::too_many_arguments)]. CLAUDE.md disallows >7 arguments — use a struct. Introduce ProxyDispatchInput bundling method/path/settings/kv/ec_context/req under one lifetime, destructure inside the method, drop the allow, and update the four unit-test call sites plus the adapter-fastly dispatch site.
Addresses review nitpicks on #621 (P2-10/11/12/13): - drop the dead VALID_SYNTHETIC_ID test constant + "synthetic ID" doc comment in test_support.rs (the EC system has no synthetic-ID concept any more) - rewrite the EcContext::consent_mut doc to stop citing the never- implemented /_ts/api/v1/sync endpoint - update the EcScenario doc comment to list only the EC endpoints that actually exist - extend the /identify response example in api-reference.md with cluster_size and note which fields are optional
This was referenced May 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements review findings for #621 so the branch is closer to a clean merge candidate. Stacked on top of #621 — merge into that branch to absorb the fixes. Rebased onto the latest
feature/edge-cookies-final(aa9c96eb) so it applies cleanly.Findings addressed
/auctionbody parsed without size cap (auction/endpoints.rs).handle_auctionpreviously didserde_json::from_slice(&req.take_body_bytes())with no limit, so an authenticated client could buffer arbitrary bytes into the Wasm worker. Now rejects with413via aContent-Lengthprecheck for well-behaved clients plus a post-read length check for clients that lie about or omit the header. Cap is 256 KiB. Check is inlined rather than calling a shared helper to avoid fighting the in-flight HTTP-types migration inhttp_util.rs.IntegrationRegistry::handle_proxy>7-arg#[allow(clippy::too_many_arguments)](integrations/registry.rs, adapter-fastly/main.rs). CLAUDE.md disallows >7 args. Bundled method/path/settings/kv/ec_context/req under one lifetime viaProxyDispatchInput; destructured inside; allow-attr removed; the four unit-test sites + the adapter dispatch site updated.VALID_SYNTHETIC_IDtest constant + "synthetic ID" doc comment, rewroteEcContext::consent_mutdoc to stop citing the never-implemented/_ts/api/v1/syncendpoint, updated theEcScenariodoc comment to list only endpoints that actually exist, and extended the/identifyresponse example withcluster_size+ a note on optional fields.Findings dropped this pass
aa9c96eb(integration-test-ec-secret-padded-32).content_length_exceeds_limittohttp_util) —http_utilhas migrated tohttp::Request<EdgeBody>butbatch_sync/auctionstill usefastly::Request. Sharing now would either specialize the helper tofastly::Request(against the migration direction) or force compat conversions at every call. Defer until those callers also migrate.Findings left for the author / privacy
These three weren't auto-implemented because they need a decision (policy or scope) rather than a code change. Each is laid out below with the evidence, the impact, and the options.
🔧 P2-3 —
transform_prebid_responsemints/ad-proxy/URLs that don't resolveWhat —
prebid.rs:422-502introducestransform_prebid_response,rewrite_ad_markup, andmake_first_party_proxy_url. The function is called unconditionally at parse time (prebid.rs:1138) for every Prebid auction response. It walks the winning bids and rewrites theadmHTML,nurl, andburlof any bid whose markup mentions one of five hardcoded CDN hostnames (cdn.adsrvr.org,ib.adnxs.com,rtb.openx.net,as.casalemedia.com,eus.rubiconproject.com) into URLs of the formhttps://<request_host>/ad-proxy/....Why it's broken in this PR — no
/ad-proxy/route exists anywhere in the codebase:adapter-fastly/main.rsrouting match arms don't include/ad-proxy/.PrebidIntegration::routes()(prebid.rs:299-313) declares onlyscript_patterns— no proxy endpoint.git grep '/ad-proxy/'returns only the rewriter and its own unit tests.So every winning bid from those five CDN families serves a creative whose resource loads 404 (image/script/CSS dependencies that were rewritten to
/ad-proxy/...), and thenurl/burlwin-notification + billing pixels also 404. Bidders that depend onnurl/burlbeing called will observe zero impressions and may pull back bidding entirely.Scope concern — this is a regression vs.
origin/main(where neithertransform_prebid_responsenorrewrite_ad_markupexists — confirmed viagit show origin/main:.../prebid.rs | grep -c transform_prebid_responsereturning 0) and doesn't appear in the EC scope of this PR. The unit tests verify the rewriting itself but not that the rewritten URLs are reachable.Options (need your call):
transform_prebid_response,rewrite_ad_markup,make_first_party_proxy_url, their two unit tests, and the call site at line 1138. Smallest change; ships the EC PR clean./ad-proxy/{type}/{*rest}through the existing/first-party/proxyhandler, or add a dedicated handler. Gate the rewriting behind a config flag so the un-routed state is impossible to ship.if config.first_party_proxy_enabledaround thetransform_prebid_responsecall, defaulting to off. Lets the code land without breaking bids, then a follow-up PR enables it once the route exists.You triaged this as "don't auto-drop", so I left it in place. Strong recommendation is option 1 or 3 — option 2 is correct but doesn't have to block this PR.
❓ P2-4 —
Jurisdiction::Unknownfail-closed → silent EC blackhole when geo is unwiredWhat —
consent/jurisdiction.rs:48-52returnsJurisdiction::UnknownwheneverGeoInfoisNone.consent/mod.rs:515then returnsfalsefromallows_ec_creationforUnknown, with the comment "Fail-closed: block EC creation as a precaution."Why it matters — EC depends on geo for two things, with very different criticality:
consent/jurisdiction.rsNone→Unknown→ fail-closedec/kv_types.rs::KvGeo::from_geo_infocountry/region/asn/dmaas KV metadata for downstream analyticsNone→country: "ZZ", no decisions blockedSo a misconfigured deployment where the Fastly geo lookup returns
None(a new service that didn't enable geo in the dashboard, a local Viceroy run without geo fixtures, or — concerningly — the#[allow(deprecated)]GeoInfo::from_requestpath atec/mod.rs:191starting to fail) will look healthy in every other respect but issue zero EC IDs: no organic generation,/identifyreturnsconsent: denied, auction EID decoration is empty. Nothing logs, nothing alerts.Defensive options (trade-off is compliance safety vs. operability):
None→NonRegulated[consent] default_jurisdiction = "unknown" | "non_regulated" | "gdpr", defaulting to"unknown"log::warn!when the first request's geo lookup returnsNoneNoneRecommendation: B + C for production-grade defence, or just C for a minimal, uncontroversial fix. C alone is ~10 lines and changes no behavior — I can add it without waiting for the policy answer if you'd like.
Why this is a question, not an auto-fix — the fail-closed semantics themselves are defensible (don't store identity for a user whose jurisdiction you can't determine). Flipping that without a deliberate decision changes the project's compliance posture.
❓ P2-5 — TCF storage-consent silently overrides US-Privacy
opt_out_sale=Yesin US statesWhat —
consent/mod.rs:485-510checks signals forUsStatein this order: GPC → TCF → GPP → US-Privacy. Once TCF is found andtcf.has_storage_consent()is true, the function returnstruewithout ever consulting US-Privacy. The unit testec_allowed_us_state_tcf_takes_priority_over_us_privacy(consent/mod.rs:1087-1103) explicitly encodes and asserts this precedence.Concrete failure mode — User visits the publisher from the EU first; the CMP writes a TCF consent string into the user's browser. Some time later the same user (or someone with the same
ts-eccookie / shared device) visits from a US state with a privacy law (CA/VA/CO/CT/UT/TX/...) and uses the publisher's CCPA banner to opt out of sale (us_privacy=1YYY,opt_out_sale=Yes). The stale TCF storage consent is still present, soallows_ec_creationreturnstrueand EC continues to operate as if the user hadn't opted out. The explicit, user-issued CCPA opt-out is silently ignored.The code's rationale (from the inline comment at line 491-494):
That argument is reasonable when the TCF and US-Privacy strings come from the same CMP at the same time and represent a coordinated decision. The code does not verify that — any TCF string with storage consent wins over any US-Privacy opt-out, regardless of age or source.
Options:
opt_out_sale=Yes" is the intended semantics across the stale-TCF case. Update the inline comment and test to make the cross-CMP assumption explicit.consent/mod.rs:494-496: check US-Privacy first; ifopt_out_sale == Yes, returnfalse; otherwise fall through to TCF/GPP. Safest from a CCPA-enforcement standpoint.Why this is a question, not an auto-fix — precedence between privacy frameworks is a policy decision with legal exposure. I won't flip it without a sign-off, even though the fix is one line.
Test plan
cargo fmt --all -- --checkcargo clippy --workspace --all-targets --all-features -- -D warningscargo test --workspace(1178 core + 21 openrtb + 20 adapter-fastly + 2 doc, 0 failed)cd crates/js/lib && npx vitest run(318 passed)prettier --checkon the modified doccargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1with the integration-test env vars