Skip to content

Prevent 304 responses for SSAT root documents - #952

Merged
prk-Jr merged 17 commits into
mainfrom
fix/ssat-root-document-304
Aug 3, 2026
Merged

Prevent 304 responses for SSAT root documents#952
prk-Jr merged 17 commits into
mainfrom
fix/ssat-root-document-304

Conversation

@prk-Jr

@prk-Jr prk-Jr commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Ensure eligible SSAT publisher document requests always receive a complete origin body so HTML synthesis and auctions run on reload and range navigations.
  • Prevent browser and CDN revalidation by stripping conditional and range request headers, bypassing platform read-through caching, and returning synthesized HTML with Cache-Control: private, no-store.
  • Fail closed with a non-cacheable 502 Bad Gateway and abandon the dispatched auction if an eligible origin unexpectedly returns 304 Not Modified.
  • Prevent operator-configured CDN cache headers from restoring shared caching on private responses.

Changes

File Change
crates/trusted-server-core/src/platform/http.rs Add a default-off platform HTTP cache-bypass option.
crates/trusted-server-core/src/platform/test_support.rs Record cache-bypass behavior in the shared test client.
crates/trusted-server-adapter-fastly/src/platform.rs Map cache bypass to Fastly set_pass(true) for synchronous and asynchronous sends.
crates/trusted-server-adapter-cloudflare/src/platform.rs Map cache bypass to Cloudflare Workers CacheMode::NoStore.
crates/trusted-server-adapter-cloudflare/wrangler.toml Enable the compatibility flag required by Cloudflare's request cache option.
crates/trusted-server-adapter-cloudflare/wrangler.ci.toml Enable the same compatibility flag in CI configuration.
crates/trusted-server-core/src/response_privacy.rs Centralize CDN-targeted cache headers and prevent operator headers or cookie responses from restoring shared caching.
crates/trusted-server-core/src/publisher.rs Strip request validators and range headers, enforce private, no-store, reject unexpected eligible 304s, abandon auctions, and add regression coverage.
docs/superpowers/specs/2026-07-22-ssat-root-document-304-prevention-design.md Document the approved behavior and scope.
docs/superpowers/plans/2026-07-22-ssat-root-document-304-prevention.md Document the implementation and verification plan.

Closes #953

Test plan

  • cargo test-fastly
  • cargo test-axum
  • cargo test-cloudflare
  • cargo test-spin
  • cargo fmt --all -- --check
  • cargo clippy-fastly
  • cargo clippy-axum
  • cargo clippy-cloudflare
  • cargo clippy-cloudflare-wasm
  • cargo clippy-spin-native
  • cargo clippy-spin-wasm
  • JS tests: cd crates/trusted-server-js/lib && npx vitest run (411 tests using pinned Node 24.12.0; previous reviewed head)
  • JS format: cd crates/trusted-server-js/lib && npm run format (previous reviewed head)
  • Docs format: cd docs && npm run format (previous reviewed head)
  • WASM production build: cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1
  • Manual testing via fastly compute serve

Checklist

  • Changes follow CLAUDE.md conventions
  • No unwrap() in production code
  • Uses project logging macros (not println!)
  • New code has tests
  • No secrets or credentials committed

@prk-Jr prk-Jr self-assigned this Jul 22, 2026
@prk-Jr
prk-Jr changed the base branch from main to rc/july July 22, 2026 17:09
@prk-Jr
prk-Jr changed the base branch from rc/july to main July 22, 2026 17:13
@prk-Jr
prk-Jr requested review from ChristianPavilonis and aram356 and removed request for aram356 July 23, 2026 15:45

@ChristianPavilonis ChristianPavilonis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Reviewed PR #952 against main. The Fastly path is covered, but one supported-adapter cache-bypass gap needs follow-up; it is included inline.

Comment thread crates/trusted-server-core/src/publisher.rs Outdated
aram356 and others added 4 commits July 27, 2026 09:08
…t-304

# Conflicts:
#	crates/trusted-server-core/src/publisher.rs
CloudflareHttpClient::execute built its worker::RequestInit without ever
reading PlatformHttpRequest::bypass_cache, so an ad-stack-eligible
navigation could still be served from Cloudflare's subrequest cache
instead of a complete origin body.

Map the flag to the Workers runtime's no-store cache mode. The mapping
goes through a target-independent OutboundCacheMode enum, gated the same
way as reject_multi_provider_fanout, because the worker crate is a
wasm32-only dependency and the native test binary cannot name
worker::CacheMode.
ChristianPavilonis added a commit that referenced this pull request Jul 28, 2026
Both parents of the updated #952 merge head are already integrated in the RC. Preserve the current tree while recording the exact latest head.

@aram356 aram356 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Well-designed and thoroughly tested fix: eligible SSAT navigations now strip browser validators, bypass the platform cache, return synthesized HTML as private, no-store without validators, and fail closed on an unexpected origin 304 with distinct abandoned-auction telemetry. One blocker on the Cloudflare surface: the new CacheMode::NoStore requires a compatibility date/flag the repo's wrangler configs don't have, so the eligible-navigation path would throw at runtime on that adapter.

Blocking

🔧 wrench

  • Cloudflare CacheMode::NoStore requires cache_option_enabled: wrangler.toml/wrangler.ci.toml pin compatibility_date = "2024-09-23", which predates the 2024-11-11 cache RequestInit support — every eligible navigation on the Cloudflare adapter would fail its origin fetch (crates/trusted-server-adapter-cloudflare/src/platform.rs:356; see inline comment).

Non-blocking

📝 note

  • Spec/plan/PR description omit the Cloudflare runtime change: the design doc still states adapters without a read-through cache need no runtime change (docs/superpowers/specs/2026-07-22-ssat-root-document-304-prevention-design.md:53; see inline comment).

🌱 seedling

  • Cloudflare CDN cache directives aren't stripped from synthesized HTML: CDN-Cache-Control/Cloudflare-CDN-Cache-Control pass through where Fastly's surrogate headers are removed — pre-existing gap, follow-up issue (crates/trusted-server-core/src/publisher.rs:2921; see inline comment).

CI Status

  • fmt: PASS
  • clippy/check jobs: PASS
  • rust tests (fastly/axum/cloudflare/spin + parity + CLI): PASS
  • js tests (vitest): PASS
  • integration + browser tests: PASS

Also verified locally: the 4 new ssat_cache_policy_tests under Viceroy, both Fastly apply_fastly_cache_bypass tests, both Cloudflare outbound_cache_mode tests, and cargo check-cloudflare (wasm).

Comment thread crates/trusted-server-adapter-cloudflare/src/platform.rs
Comment thread docs/superpowers/specs/2026-07-22-ssat-root-document-304-prevention-design.md Outdated
Comment thread crates/trusted-server-core/src/publisher.rs Outdated
prk-Jr and others added 2 commits July 29, 2026 12:47
The Cloudflare adapter maps the platform cache-bypass option to
`CacheMode::NoStore` on the outbound subrequest, but setting the `cache`
field on `RequestInit` requires the `cache_option_enabled` compatibility
flag, which is only on by default from compatibility date 2024-11-11.
Both wrangler files pin 2024-09-23, so the Workers runtime would have
thrown on every auction-eligible navigation, failing the origin fetch and
abandoning the auction. Set the flag explicitly in `wrangler.toml` and
`wrangler.ci.toml`, and document the coupling at the call site.

Also strip `CDN-Cache-Control` and `Cloudflare-CDN-Cache-Control` from
synthesized SSAT HTML alongside the existing surrogate headers. Without
this, an origin emitting either field could still instruct the Cloudflare
edge to store a per-navigation document, reproducing on that adapter the
bug this change closes on Fastly.

Update the design doc and plan file map to match the shipped behavior.
@prk-Jr
prk-Jr requested a review from aram356 July 29, 2026 07:48

@ChristianPavilonis ChristianPavilonis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

The main cache-bypass and unexpected-304 handling are well structured. I found one high-severity cache-privacy issue and one medium-severity partial-response correctness issue, noted inline. All displayed CI checks pass on the reviewed head.

Comment thread crates/trusted-server-core/src/publisher.rs Outdated
Comment thread crates/trusted-server-core/src/publisher.rs
Comment thread crates/trusted-server-adapter-cloudflare/wrangler.toml
@prk-Jr
prk-Jr merged commit 0ea9ee3 into main Aug 3, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Prevent 304 responses for SSAT root documents

3 participants