Prebid external first-party bundle loading#743
Conversation
7699203 to
9385864
Compare
14a91cc to
bd85e3e
Compare
fc56059 to
580207c
Compare
aram356
left a comment
There was a problem hiding this comment.
Summary
Reworks Prebid to load an external first-party bundle proxied through the server (hard cut: external_bundle_url required when enabled, no embedded tsjs-prebid), adds the trusted-server-cli crate, the Osano consent mirror, and a hash-verified config-store payload. Overall this is high-quality, security-conscious work — the proxy path in particular is well-built. Verdict is REQUEST_CHANGES for one validation-parity bug plus a few open questions; the blocking fixes are small.
Reviewed against base
feature/ts-cli-next(this is a stacked PR — the range also carries Osano #773 and tester-cookie-clear #797). 1 of the inline comments carries a one-click, scratch-verified GitHubsuggestion; the rest describe fixes in prose because they span multiple files, touch new files, or are design questions.
Blocking
🔧 wrench
ts config validateskips external-bundle validation (CLI passes, server boot-fails) — see inline atcrates/trusted-server-core/src/integrations/prebid.rs:233(one-click suggestion, verified: 130 tests green)
❓ question
- External-bundle builder mutates git-tracked generated files; restore is
finally-only, not signal-safe — see inline atcrates/js/lib/build-prebid-external.mjs:263 config push --dry-runsafety rests entirely on the pinned edgezero-adapter rev — see inline atcrates/trusted-server-cli/src/edgezero_delegate.rs:329request_body_bytesdead scaffolding with a latent stream-drop (rotate-key → default-key rotation) — see inline atcrates/trusted-server-core/src/request_signing/endpoints.rs:27
Non-blocking
🤔 thinking / 🌱 seedling / 📝 note / ⛏ nitpick
- Infallible
Result+ unused arg churn on body helpers — see inline atcrates/trusted-server-core/src/publisher.rs:35 - CLI env/secret handling diverges between manifest-command and in-process paths — see inline at
crates/trusted-server-cli/src/edgezero_delegate.rs:108 - Redundant/split HTTPS redirect check — see inline at
crates/trusted-server-core/src/proxy.rs:1413 - Tester set/clear endpoints unauthenticated (design carry-over) — see inline at
crates/trusted-server-core/src/tester_cookie.rs:78 OsanoConfiglacksdeny_unknown_fields— see inline atcrates/trusted-server-core/src/integrations/osano.rs:21validate_enabled_integrationshardcodes the integration list — see inline atcrates/trusted-server-cli/src/config_command.rs:187- CLI crate is
edition = "2021"vs documented 2024 — see inline atcrates/trusted-server-cli/Cargo.toml:5
Cross-cutting / body-level findings
- 🤔 Open-proxy fallback when
proxy.allowed_domainsis empty —validate_external_bundle_config(prebid.rs:245) only enforces the allowlist when non-empty, andredirect_is_permitted(proxy.rs:1211) treats an empty allowlist as "permit all". With an empty list, a redirecting origin can send the bundle fetch to any HTTPS host (≤4 hops) and it's streamed as first-party JS with immutable cache;require_httpsis the only backstop. The host match itself is correctly dot-boundary-safe (evil-example.com≠*.example.com— verified). Consider requiring a non-empty allowlist wheneverexternal_bundle_urlis set. - 🤔 Two
edgezero-corerevs in the dependency graph —trusted-server-clipins@2eeccc97(edgezero-cli/adapter) while its deptrusted-server-corepins@38198f98, so the CLI's graph compiles two full copies ofedgezero-core. Verified benign for correctness today (onlyString/BTreeMapcross the boundary), but it's build-cost + a latent footgun the moment anedgezero_coretype crosses that seam. Align to one rev — the split looks accidental. - 🌱 Test-coverage gaps — no end-to-end test for the proxy fetch+sanitize happy path (
handle_external_bundlewith a mock streaming client); no mjs-level test assertingfilename sha256 == hash(bytes)and SRI derivation (the load-bearing CLI↔runtime contract);buildMirrorPlanuntested with USP+GPP+TCF all present at once. - 🤔 Build-tooling robustness (benign today) —
--outresolves against cwd in the Rust CLI but__dirnamein the mjs (safe only because the CLI always passes an absolute path); the mjs temp file is a fixed name withemptyOutDir:false, so concurrent/overlapping runs into one dir can hash a partial file. Align the mjs base toprocess.cwd()and PID-suffix the temp file. - 📝 Insecure-
certificate_checkwarning relocated — the old startuplog::warn!("INSECURE: proxy.certificate_check is disabled")inload_settingsis gone; the warning still fires per-backend at connect time (adapter-fastly/src/backend.rs:220), so it's not lost but the semantics shifted from once-at-startup to per-connection. Confirm operators still see it.
👍 Praise
- Proxy path disables both EC-ID and client-header forwarding for the static-asset fetch (
without_ec_id().without_forward_headers()), rebuilds responses from a strict header allowlist, stripsSet-Cookie, and forcesno-storeon non-200 to protect the immutable cache. Cache-mode/version logic robustly resists cache poisoning (exact-hash immutable mode, multi-vrejection). - sha256/SRI formats line up across all three layers (mjs emitter → CLI manifest validator → runtime config validator) — the invariant this whole PR rests on.
- Osano consent mirror is genuinely fail-safe (never fabricates a permissive signal on missing/malformed input) with a real
mirrorGenerationrace guard and test. write_atomic/ensure_output_dir_writableand the argv-arraynpminvocation (no shell) are careful, well-tested systems code; the config payload is hash-verified with no panics on operator input; real domains/secrets were removed from source control.
CI Status
- All GitHub checks: not run — the host-target CLI clippy/test workflows are added by this PR but haven't executed on this branch. Recommend pushing to trigger CI before merge, since the two-rev edgezero graph and the new CLI crate are only exercised there.
580207c to
2f0b7d0
Compare
Co-authored-by: Aram Grigoryan <132480+aram356@users.noreply.github.com>
b82ab1e to
0526575
Compare
Summary
bundle_mode, no embedded/deferredtsjs-prebidloading, andexternal_bundle_urlis required whenever Prebid is enabled./integrations/prebid/bundle.jsscript and keep publisher Prebid script interception/removal to prevent duplicate instances.proxy.allowed_domainsenforcement.external_bundle_sha256optional; when present it drives versioned first-party URLs, immutable cache headers, andsha256:ETags. SRI is validated if configured but is not required.build-prebid-external.mjs.Quick how-to: generate and configure a Prebid bundle
cd crates/js/lib npm run build:prebid-external -- \ --adapters=rubicon,appnexus,openx \ --user-id-modules=sharedIdSystem,uid2IdSystem \ --out=dist/prebidUpload the generated
trusted-prebid-<sha256>.jsfromdist/prebid/to an HTTPS asset host that is allowed byproxy.allowed_domains.Copy values from
dist/prebid/manifest.jsoninto config:Trusted Server injects the same-origin
/integrations/prebid/bundle.jsroute for browsers; publishers should not reference the external asset URL directly in page markup.Testing
cargo fmt --all -- --checkTSJS_SKIP_BUILD=1 cargo check -p trusted-server-coreTSJS_SKIP_BUILD=1 cargo test -p trusted-server-core prebid --libTSJS_SKIP_BUILD=1 cargo test -p trusted-server-core publisher::tests --libTSJS_SKIP_BUILD=1 cargo test -p trusted-server-core registry::tests --libTSJS_SKIP_BUILD=1 cargo test --workspaceTSJS_SKIP_BUILD=1 cargo clippy --workspace --all-targets --all-features -- -D warningscargo check -p trusted-server-jscd crates/js/lib && npm run buildcd crates/js/lib && npm run formatcd crates/js/lib && npx vitest runcd crates/js/lib && npm run build:prebid-external -- --adapters=rubicon --user-id-modules=sharedIdSystem --out=/tmp/trusted-server-prebid-external-hard-cutovercd docs && npm ci && npm run format