Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fde55b1
docs: design mobile ad-render trace endpoint
prk-Jr Sep 1, 2026
8899dca
docs: harden mobile ad trace design
prk-Jr Sep 1, 2026
534a691
Add server auction evidence to mobile trace design
prk-Jr Sep 1, 2026
20debdf
Merge branch 'main' into spec/mobile-ad-render-trace-endpoint
prk-Jr Sep 10, 2026
282d78b
Merge branch 'main' into spec/mobile-ad-render-trace-endpoint
prk-Jr Sep 14, 2026
c2aa6be
Merge branch 'main' into spec/mobile-ad-render-trace-endpoint
prk-Jr Sep 19, 2026
e3f371f
Reconcile mobile trace design contracts
prk-Jr Sep 19, 2026
488de2c
Merge branch 'main' into spec/mobile-ad-render-trace-endpoint
prk-Jr Sep 21, 2026
14c274c
Clarify mobile trace design contracts
prk-Jr Sep 21, 2026
7a82a94
Merge branch 'main' into spec/mobile-ad-render-trace-endpoint
aram356 Sep 24, 2026
155d3b0
Clarify trace body validation and browser cleanup
prk-Jr Sep 24, 2026
d361b8e
Merge branch 'main' into spec/mobile-ad-render-trace-endpoint
prk-Jr Sep 24, 2026
c4d664a
Merge branch 'main' into spec/mobile-ad-render-trace-endpoint
aram356 Sep 25, 2026
a180741
Merge branch 'main' into spec/mobile-ad-render-trace-endpoint
prk-Jr Sep 30, 2026
4811d98
Clarify trace failure and export cleanup contracts
prk-Jr Oct 1, 2026
162b3a4
Merge branch 'main' into spec/mobile-ad-render-trace-endpoint
prk-Jr Oct 1, 2026
2028bc6
Clarify mobile trace capture and truncation rules
prk-Jr Oct 2, 2026
20f4a0c
Merge branch 'main' into spec/mobile-ad-render-trace-endpoint
aram356 Oct 4, 2026
750ba8d
Add default-off mobile ad-rendering trace workflow
prk-Jr Oct 6, 2026
e076f77
Link trace release checklist to the repository plan
prk-Jr Oct 6, 2026
b2930d7
Preserve advertising behavior and trace controls
prk-Jr Oct 6, 2026
fb45d1c
Run skipped-page trace regression in CI
prk-Jr Oct 6, 2026
f6c3bd5
Record CodeQL test-fixture triage
prk-Jr Oct 6, 2026
b7e48c7
Merge branch 'main' into spec/mobile-ad-render-trace-endpoint
prk-Jr Oct 6, 2026
6e9e7c4
Bound trace capture cost and complete review corrections
prk-Jr Oct 6, 2026
e5becfc
Treat only commas that border reserved cookies as ambiguous
prk-Jr Oct 7, 2026
e1b062a
Lead the trace viewer with what happened and per-slot cards
prk-Jr Oct 7, 2026
dc69eb4
Pin trace path classification across Fastly request conversion
prk-Jr Oct 8, 2026
285c75f
Merge main into spec/mobile-ad-render-trace-endpoint
prk-Jr Oct 8, 2026
117373e
Merge branch 'main' into spec/mobile-ad-render-trace-endpoint
prk-Jr Oct 9, 2026
92dd203
Authenticate forwarded public origins before request dispatch
prk-Jr Oct 9, 2026
cf36844
Document random forwarder tokens and temporary digest comparison
prk-Jr Oct 9, 2026
5c66d64
Merge branch 'main' into spec/mobile-ad-render-trace-endpoint
aram356 Oct 11, 2026
b4a20f4
Address trace workflow review findings
prk-Jr Oct 12, 2026
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
118 changes: 104 additions & 14 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: "Integration Tests"
name: 'Integration Tests'

permissions:
contents: read
Expand Down Expand Up @@ -30,11 +30,14 @@ jobs:
uses: ./.github/actions/setup-integration-test-env
with:
origin-port: ${{ env.ORIGIN_PORT }}
install-viceroy: "false"
build-cloudflare: "true"
install-viceroy: 'false'
build-cloudflare: 'true'

- name: Generate integration Viceroy configs
run: ./scripts/generate-integration-viceroy-configs.sh
run: |
./scripts/generate-integration-viceroy-configs.sh
INTEGRATION_APP_CONFIG_PATH=crates/trusted-server-integration-tests/fixtures/configs/trusted-server.trace.toml INTEGRATION_BIDDER_ORIGIN_URL="http://127.0.0.1:$ORIGIN_PORT" ARTIFACTS_DIR="$ARTIFACTS_DIR/trace" ./scripts/generate-integration-viceroy-configs.sh
INTEGRATION_APP_CONFIG_PATH=crates/trusted-server-integration-tests/fixtures/configs/trusted-server.trace-auth.toml ARTIFACTS_DIR="$ARTIFACTS_DIR/trace-auth" ./scripts/generate-integration-viceroy-configs.sh
env:
INTEGRATION_ORIGIN_PORT: ${{ env.ORIGIN_PORT }}

Expand Down Expand Up @@ -68,10 +71,10 @@ jobs:
uses: ./.github/actions/setup-integration-test-env
with:
origin-port: ${{ env.ORIGIN_PORT }}
install-viceroy: "true"
build-wasm: "false"
build-axum: "false"
build-test-images: "false"
install-viceroy: 'true'
build-wasm: 'false'
build-axum: 'false'
build-test-images: 'false'

- name: Download integration test artifacts
uses: actions/download-artifact@v4
Expand Down Expand Up @@ -105,6 +108,7 @@ jobs:
--target x86_64-unknown-linux-gnu
-- --include-ignored
--skip test_wordpress_fastly --skip test_nextjs_fastly
--skip trace_browser_workflow --skip trace_runtime_boundary
--test-threads=1
env:
WASM_BINARY_PATH: ${{ env.WASM_ARTIFACT_PATH }}
Expand All @@ -127,9 +131,9 @@ jobs:
uses: ./.github/actions/setup-integration-test-env
with:
origin-port: ${{ env.ORIGIN_PORT }}
install-viceroy: "true"
build-wasm: "false"
build-test-images: "false"
install-viceroy: 'true'
build-wasm: 'false'
build-test-images: 'false'

- name: Download integration test artifacts
uses: actions/download-artifact@v4
Expand All @@ -152,6 +156,84 @@ jobs:
VICEROY_CONFIG_PATH: ${{ env.ARTIFACTS_DIR }}/configs/viceroy.toml
RUST_LOG: info

trace-runtime-tests:
name: trace runtime and browser acceptance
needs: prepare-artifacts
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4

- name: Set up trace test runtime
id: shared-setup
uses: ./.github/actions/setup-integration-test-env
with:
origin-port: ${{ env.ORIGIN_PORT }}
install-viceroy: 'true'
build-wasm: 'false'
build-axum: 'false'
build-test-images: 'false'

- name: Download integration test artifacts
uses: actions/download-artifact@v4
with:
name: integration-test-artifacts
path: ${{ env.ARTIFACTS_DIR }}

- name: Restore runtime binaries and framework images
run: |
chmod +x "$AXUM_ARTIFACT_PATH"
mkdir -p crates/trusted-server-adapter-cloudflare/build
cp -r "$CF_BUILD_ARTIFACT_PATH/." crates/trusted-server-adapter-cloudflare/build/
docker load --input "$DOCKER_ARTIFACT_PATH"

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ steps.shared-setup.outputs.node-version }}
cache: npm
cache-dependency-path: |
crates/trusted-server-integration-tests/browser/package-lock.json
crates/trusted-server-js/lib/package-lock.json

- name: Install browser dependencies
run: |
npm ci --prefix crates/trusted-server-js/lib
npm ci --prefix crates/trusted-server-integration-tests/browser
cd crates/trusted-server-integration-tests/browser
npx playwright install --with-deps chromium
npm install -g wrangler@4.83.0

- name: Install the verified Spin runtime
run: |
trace_spin_dir="$RUNNER_TEMP/trace-spin"
mkdir -p "$trace_spin_dir"
curl --fail --location --silent --show-error https://github.com/spinframework/spin/releases/download/v4.0.0/spin-v4.0.0-linux-amd64.tar.gz --output "$trace_spin_dir/spin.tar.gz"
Comment thread
prk-Jr marked this conversation as resolved.
echo "e705c9bfd9484a9175f392a116856680862a40f31d1a85618bed331f34ffccfa $trace_spin_dir/spin.tar.gz" | sha256sum --check --strict
tar -xzf "$trace_spin_dir/spin.tar.gz" -C "$trace_spin_dir" spin
echo "$trace_spin_dir" >> "$GITHUB_PATH"

- name: Build the production Spin component
run: cargo build --package trusted-server-adapter-spin --target wasm32-wasip1 --features spin --release

- name: Run trace acceptance against prepared runtimes
run: |
cargo test --manifest-path crates/trusted-server-integration-tests/Cargo.toml --target x86_64-unknown-linux-gnu --test integration trace_runtime_boundary -- --ignored --test-threads=1 --nocapture
cargo test --manifest-path crates/trusted-server-integration-tests/Cargo.toml --target x86_64-unknown-linux-gnu --test integration trace_browser_workflow -- --ignored --test-threads=1 --nocapture
env:
WASM_BINARY_PATH: ${{ env.WASM_ARTIFACT_PATH }}
AXUM_BINARY_PATH: ${{ env.AXUM_ARTIFACT_PATH }}
CLOUDFLARE_WRANGLER_DIR: ${{ github.workspace }}/crates/trusted-server-adapter-cloudflare
INTEGRATION_ORIGIN_PORT: ${{ env.ORIGIN_PORT }}

- name: Upload trace browser evidence
uses: actions/upload-artifact@v4
if: always()
with:
name: trace-runtime-browser-evidence
path: crates/trusted-server-integration-tests/browser/test-results/trace-runtime-*/
retention-days: 7

browser-tests:
name: browser integration tests
needs: prepare-artifacts
Expand All @@ -165,9 +247,9 @@ jobs:
uses: ./.github/actions/setup-integration-test-env
with:
origin-port: ${{ env.ORIGIN_PORT }}
install-viceroy: "true"
build-wasm: "false"
build-test-images: "false"
install-viceroy: 'true'
build-wasm: 'false'
build-test-images: 'false'

- name: Download integration test artifacts
uses: actions/download-artifact@v4
Expand Down Expand Up @@ -204,6 +286,10 @@ jobs:
working-directory: crates/trusted-server-integration-tests/browser
run: node --test initial-render/pages.test.cjs

- name: Test trace fixture cleanup
working-directory: crates/trusted-server-integration-tests/browser
run: node --test trace-fixture.test.cjs

- name: Test initial render ownership
working-directory: crates/trusted-server-integration-tests/browser
# Let the runner build its Rubicon + Shared ID artifact; the bidder-only
Expand All @@ -225,6 +311,8 @@ jobs:
WASM_BINARY_PATH: ${{ env.WASM_ARTIFACT_PATH }}
INTEGRATION_ORIGIN_PORT: ${{ env.ORIGIN_PORT }}
VICEROY_CONFIG_PATH: ${{ env.ARTIFACTS_DIR }}/configs/viceroy.toml
TRACE_VICEROY_CONFIG_PATH: ${{ env.ARTIFACTS_DIR }}/trace/configs/viceroy.toml
TRACE_AUTH_VICEROY_CONFIG_PATH: ${{ env.ARTIFACTS_DIR }}/trace-auth/configs/viceroy.toml
TEST_FRAMEWORK: nextjs
PLAYWRIGHT_HTML_REPORT: playwright-report-nextjs
run: npx playwright test
Expand All @@ -244,6 +332,8 @@ jobs:
WASM_BINARY_PATH: ${{ env.WASM_ARTIFACT_PATH }}
INTEGRATION_ORIGIN_PORT: ${{ env.ORIGIN_PORT }}
VICEROY_CONFIG_PATH: ${{ env.ARTIFACTS_DIR }}/configs/viceroy.toml
TRACE_VICEROY_CONFIG_PATH: ${{ env.ARTIFACTS_DIR }}/trace/configs/viceroy.toml
TRACE_AUTH_VICEROY_CONFIG_PATH: ${{ env.ARTIFACTS_DIR }}/trace-auth/configs/viceroy.toml
TEST_FRAMEWORK: wordpress
PLAYWRIGHT_HTML_REPORT: playwright-report-wordpress
run: npx playwright test
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ jobs:
- name: Run tests
run: cargo test-fastly

- name: Run trace emitted-script regressions
run: |
cargo test -p trusted-server-core --target x86_64-unknown-linux-gnu --lib trace_document_skipped_emitted_script_leaves_ad_state_untouched -- --ignored
cargo test -p trusted-server-core --target x86_64-unknown-linux-gnu --lib trace_document_emitted_script_deep_freezes_owned_context_in_node -- --ignored

- name: Run tests (reusable-sandbox feature)
run: cargo test-fastly-reuse

Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Explicit TS Console activation with `?ts_console=1` now sets a 30-minute diagnostics cookie instead of a browser-session cookie, independently of the mobile trace flag. Ordinary requests do not refresh it; end and explicitly enable diagnostics again to adopt the lifetime for an existing session.
- Publisher documents with diagnostics active now use private, no-store responses and strip conditional and range request headers before fetching the origin. An unexpected origin `304` becomes a private `502` because it cannot supply an instrumented document, including when mobile tracing is disabled.
- **Breaking:** `ts prebid bundle` is now `ts prebid client`, alongside the new `ts prebid server` namespace. Update scripts and runbooks to use `ts prebid client` with the same arguments. The old `bundle` spelling is no longer accepted and has no compatibility alias.
- The S2S `/_ts/api/v1/batch-sync` endpoint now validates the full batch and calls the CAS-protected update path once per distinct normalized EC ID. The last valid UID wins within a group, and infrastructure failures reject the failing and each unprocessed group, so accepted and `kv_unavailable` input indexes may interleave.
- **Breaking:** Auction providers and bidder routes now use the configuration-first `[auction.providers.<id>]` and `[auction.bidders.<id>]` maps. The removed `[auction].providers = [...]` list and removed server fields under `[integrations.prebid]` and `[integrations.aps]` are rejected even when those integrations are disabled, and `ts config push` rejects the old shape before publication. Move PBS `server_url` to provider `endpoint`, server timeout to provider `timeout_ms`, request controls and bidder-parameter overrides to the `prebid-server` `profile_config`, notification suppression to `notifications`, and each former server bidder to an `[auction.bidders.<id>]` route. Move APS endpoint, timeout, account, inventory, debug, and creative controls to an `aps` provider and its `profile_config`. Browser Prebid settings remain under `[integrations.prebid]`; values such as timeout and debug that previously affected both browser and server behavior must now be configured for each owner. Provider endpoints must be absolute HTTPS URLs. Only bidder codes present in `[auction.bidders]` are folded into Trusted Server requests; unlisted publisher bids remain native browser demand. Provider response names now use the configured provider ID, such as `pbs-main`, instead of the legacy literal `prebid`; audit consumers that match `AuctionResponse.provider`. This schema has no mixed-version-safe deployment order: old binaries reject the maps and new binaries reject the retired fields, so activate the new binary and config blob together. Rollbacks must restore an old-schema blob together with the old binary.
Expand All @@ -27,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Security

- Reserve the application-visible `/_ts/trace*` prefix locally on every adapter, including when mobile tracing is disabled. Existing authentication runs first; disabled or unknown trace routes then return a local `404` instead of forwarding to the publisher.
Comment thread
prk-Jr marked this conversation as resolved.
- Stop deriving the public host and scheme from unauthenticated `Forwarded`, `X-Forwarded-Host` and `X-Forwarded-Proto` fields on every adapter, and strip those fields before routing. Fastly and Spin already removed them; Axum and Cloudflare previously let a client-supplied value steer first-party URL rewriting. A deployment behind a TLS-terminating or Host-rewriting proxy, including `ts dev proxy` against Axum, must now configure the opt-in `[trusted_forwarder]` section and send its matching credential to preserve the public origin. The section authenticates one publisher-domain-bounded `X-Forwarded-Host`/`X-Forwarded-Proto` pair with a secret-store-resolved shared secret; without it, first-party URLs use the transport host and scheme. Axum uses `http://<Host>` even when the proxy's browser-facing connection uses HTTPS.
- `/first-party/sign` now rejects valid targets outside `proxy.allowed_domains` before minting a proxy token. The creative runtime keeps image and iframe assignments blocked after this `403` policy response instead of loading the rejected URL directly; fetch-time checks still cover the initial target and every redirect.
- Reserved the complete admin namespace at the publisher-fallback boundary. Percent-encoded separators (`/_ts/admin%2Fec`, `%2f`, and double-encoded forms) matched the `^/_ts/admin` Basic-auth handler but escaped the literal-slash namespace check, so an authenticated request fell through to publisher fallback and forwarded its `Authorization` header and body to the publisher origin. The reservation now spans the whole `/_ts/admin` prefix plus the retired `/admin/keys` aliases — including trailing, descendant, and encoded-separator forms — evaluated on the raw path and on each of its bounded percent-decodings, so multi-encoded separators such as `/admin%252Fkeys/rotate` cannot survive to fallback for a proxy or origin to decode again, and applies to every adapter.
- Validate synthetic ID format on inbound values from the `x-synthetic-id` header and `synthetic_id` cookie; values that do not match the expected format (`64-hex-hmac.6-alphanumeric-suffix`) are discarded and a fresh ID is generated rather than forwarded to response headers, cookies, or third-party APIs
Expand All @@ -41,6 +45,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Add opt-in mobile ad-render tracing through `[integrations.gpt_diagnostics].trace_page_enabled` (default `false`, requiring GPT diagnostics). The same-tab `/_ts/trace` viewer presents bounded, redacted browser-local request, auction and GPT observations with copy, download, share and independent local/server cleanup controls. See the GPT diagnostics guide for deployment and rollout checks.
- Add the opt-in `[trusted_forwarder]` section with required `auth_header` and secret-store `shared_secret` reference fields for authenticated public-origin forwarding.
- Added the `[auction].rewrite_creatives` (default `true`) and `[auction].sanitize_creatives` (default `false`) options. `rewrite_creatives` rewrites winning-bid adm to first-party endpoints across `POST /auction` and publisher SSAT/page-bids delivery (proxy/click URL conversion, bidder `<base>` removal; creative TSJS injection on `POST /auction` only). Enabling `sanitize_creatives` strips executable markup from winning-bid adm before delivery.
- `creative_opportunities.slot.gam_unit_path` is now a template supporting `{network_id}`, `{slot_id}`, and `{section}`, so a publisher whose ad unit varies by site section expresses it in one slot rule instead of one per (slot × section). `{section}` derives from the request path: `[creative_opportunities].section_segment` selects which path segment names the section (0-based, default `0`; set `1` for locale-prefixed URLs), and `section_root` supplies the value for paths with no such segment. `section_root` is required when a template uses `{section}`. Existing static and absent `gam_unit_path` configs are unchanged. Startup rejects a blank `gam_network_id` only when an absent/default path or `{network_id}` template consumes it. Trusted Server conservatively caps whole rendered dynamic paths at 100 UTF-8 bytes, informed by Google's 100-character per-ad-unit-code limit; an over-limit request-specific path omits that slot without failing the response. During typed/startup finalization, every placeholder-bearing template that omits `section_segment` materializes `section_segment = 0`, so an older binary rejects the blob loudly. Static and absent paths remain legacy-schema compatible only when both `section_root` and `section_segment` are omitted. Before rolling back below this feature, replace or remove dynamic paths, remove both keys, re-push and finalize the config, then roll back the binary.
- Added opt-in APS HTTP debug metadata for controlled test sites, exposing the direct request and response under `/auction` provider metadata using the Prebid Server `debug.httpcalls` shape.
Expand Down
Loading
Loading