Skip to content

Move Fastly names out of core - #1399

Open
jwrosewell wants to merge 50 commits into
feature/48-aws-signingfrom
feature/49-host-features
Open

jwrosewell wants to merge 50 commits into
feature/48-aws-signingfrom
feature/49-host-features

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Changes

Area Change
crates/trusted-server-core/src 39 files, 605 lines added, 653 removed
crates/trusted-server-adapter-fastly/src 9 files, 405 lines added, 50 removed
scripts/lint-core-platform-names.py 1 file, 250 lines added, 0 removed
docs/superpowers 1 file, 186 lines added, 0 removed
crates/trusted-server-adapter-fastly/build.rs 1 file, 76 lines added, 0 removed
crates/bot-protection/datadome 3 files, 41 lines added, 43 removed
docs/guide 5 files, 25 lines added, 20 removed
crates/trusted-server-cli/tests 1 file, 21 lines added, 27 removed
.github/workflows 1 file, 13 lines added, 0 removed
crates/trusted-server-core/README.md 1 file, 8 lines added, 8 removed
CHANGELOG.md 1 file, 5 lines added, 0 removed
crates/trusted-server-core/Cargo.toml 1 file, 3 lines added, 3 removed
trusted-server.example.toml 1 file, 3 lines added, 2 removed
crates/audience/permutive 1 file, 2 lines added, 2 removed
crates/device/README.md 1 file, 2 lines added, 2 removed
crates/device/fastly 1 file, 2 lines added, 2 removed
crates/identity/lockr 1 file, 2 lines added, 2 removed
AGENTS.md 1 file, 1 line added, 0 removed
Cargo.lock 1 file, 1 line added, 1 removed
crates/trusted-server-adapter-fastly/Cargo.toml 1 file, 1 line added, 0 removed
scripts/README.md 1 file, 1 line added, 0 removed
crates/trusted-server-core/build.rs 1 file, 0 lines added, 70 removed

Closes

Part of #777, being its Fastly row under "What is still named in core".

Spec

Spec: fastly-names-in-core

Test plan

CI on this pull request at d1aa512 passed all 27 checks.

  • cargo test-fastly && cargo test-axum
  • cargo clippy-fastly && cargo clippy-axum
  • cargo fmt --all -- --check
  • JS tests: cd crates/trusted-server-js/lib && npx vitest run
  • JS format: cd crates/trusted-server-js/lib && npm run format
  • Docs format: cd docs && npm run format
  • WASM build: cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1
  • Manual testing via fastly compute serve
  • Other: the full workflow set of this repository, run on this pull request at d1aa512

Checklist

  • Changes follow AGENTS.md conventions
  • No unwrap() in production code (clippy denies unwrap_used in CI)
  • Uses log macros as AGENTS.md asks (not println!)
  • New code has tests
  • No secrets or credentials committed

On top of the AWS signing series (#1391). Issue: #777, the Fastly row of "What is still named in core".

What this adds

What changes.

Core names no hosting platform. The places core still named Fastly move to the Fastly adapter, and core keeps a neutral declaration or interface in the place of each. They are the configuration chunk format, the staging variable, the fastly-ssl, fastly-client-ip and x-debug-fastly-pop headers, the fastly device module selector, and the names of the asset proxy's two response conversions. Counted the way #777 counts, the shipped lines of core that name Fastly go from 43 to none.

[device]
module = "platform" # was "fastly"

[trusted_client_ip]
ip_header = "fastly-client-ip" # still valid, as a header ending -client-ip

How it works.

  • The config store returns whole documents. PlatformConfigStore::get returns an entry whole. The Fastly adapter's store (config_chunks.rs) follows a chunk pointer, reads each chunk and checks every length and hash, so core's settings load reads what any platform's store returns. A long document reads as before, and one whose chunks disagree with the pointer is refused as before.
  • The platform says whether it runs in staging. An adapter declares it with platform::declare_platform_staging, and a module asks platform::platform_is_staging. The Fastly adapter declares it from FASTLY_IS_STAGING, and the DataDome module's test bypass follows the declaration.
  • The platform names the request headers it sets. An adapter declares three lists. Its TLS headers are read for the scheme after the TLS status the adapter reports and the standard forwarding headers, and are removed from every request to an origin. Its other spoofable headers are stripped at entry with forwarded, x-forwarded-host and x-forwarded-proto. Its internal headers are never copied to a third party and are refused as trusted client IP headers. constants::is_internal_header answers for core's list and the declared one together.
  • Declared before serving. The Fastly adapter makes every declaration in app::declare_platform(), which run() calls before it serves and every state build also calls. Settings are validated as they load, so a declaration made only in the state build would come after the first validation.
  • The trusted client IP header. ip_header must start with x- or end with -client-ip, which is the shape of a platform's own client address header, because ts config validate checks a document where no adapter has declared anything. fastly-client-ip stays valid.
  • The device selector. [device] module = "platform" selects the platform's own device module, as [geo] module = "platform" selects the platform's geo lookup, and build_device_module takes a build_platform factory. The Fastly module keeps its id.
  • Names that were not Fastly's. platform_response_to_fastly and platform_response_to_fastly_asset are platform_response_to_buffered and platform_response_to_asset, because neither touches Fastly.

Why

Core is the interfaces, the routing, the origin fetch, the caches and their tests, and a hosting platform is a vendor to core. Each of these names was a place where a change by Fastly meant a change in core. Each name moves with the code that reads it, as the platforms' cache headers did in the last names series.

What breaks

  • A settings document that writes [device] module = "fastly" writes module = "platform". The old value is refused at startup as a device module no running module supplies.
  • For crates outside core, ENV_FASTLY_IS_STAGING and HEADER_X_DEBUG_FASTLY_POP leave core, SPOOFABLE_FORWARDED_HEADERS holds the three standard forwarding headers, build_device_module takes a build_platform factory, and a PlatformConfigStore returns each entry whole.
  • The Axum, Cloudflare and Spin adapters declare none of these headers. On those adapters a client's fastly-ssl is no longer removed from the request to the publisher's origin, and on Spin a client's fastly-ssl and fastly-client-ip are no longer stripped at entry. Trusted Server reads neither header there. A client's x-debug-fastly-pop is copied to a third party like any other x- header, and the DataDome test bypass no longer follows a FASTLY_IS_STAGING variable.

The commits

  1. A platform's config store returns a long document whole.
  2. The platform says whether it serves from its staging environment.
  3. The adapter names the request headers its platform sets.
  4. The platform's own device module is selected as platform.
  5. Proxy response conversions are named for what they produce.
  6. A written document is read back whole in the Fastly adapter's own test, because core reads a config store entry as the platform's store returns it.

Each carries its section of the series' design page, docs/superpowers/specs/2026-10-15-fastly-names-in-core-design.md, and each of the first four its changelog entry. The design page takes its round 1 review and round 2 corrections in commits of its own (b319bf0 and 67d34c2), and merges of the series below carry Tech Lab main at 6ef07ae. 6fee073 marks HTTPS in the handler tests of the header rewrite merged in main (#1238) with x-forwarded-proto rather than a platform's header, so they pass and core names no platform.

What is not in this pull request

Core still names Fastly in comments in about 25 files, and in the names and assertion messages of some tests, the publisher's surrogate cache tests among them. The Axum, Cloudflare and Spin adapters compare their behavior with the Fastly adapter's in comments, and two of their errors suggest the Fastly adapter for parallel bidder fan-out. The ts development proxy strips fastly-ssl. None of these is a shipped line of core.

A Fastly config store entry holds at most 8,000 characters, so a longer
settings document is stored as chunks with a pointer at its key. Core
followed that pointer itself while loading settings, which put the Fastly
entry limit and pointer format in platform-neutral core.

The Fastly adapter's config store now follows a chunk pointer when it reads
a key, checking every length and hash as before, so every reader receives
the whole document, and core's settings load reads the entry as the
platform's config store returns it. The error messages are unchanged. The
reassembly tests move to the adapter, with two more for a chunk whose hash
disagrees and for other JSON at the key.

Two comments on core's store selector helpers that named the Fastly
adapter now describe the behavior without naming the platform.
Core held the name of Fastly's staging variable, FASTLY_IS_STAGING, so that
the Fastly adapter and the DataDome module could each read it, and DataDome
gated its test bypass on that one platform's variable.

Core now has a neutral declaration, declare_platform_staging and
platform_is_staging, which the adapter makes once before it serves, as it
declares its cache headers. The Fastly adapter declares it from its own
variable, whose name it now holds, and uses the same reading for the
service's identity and the x-ts-env header. DataDome asks core whether the
platform serves from staging, so its test bypass works on any platform that
declares it, and its tests declare the flag rather than setting the
variable.
Core read and stripped `fastly-ssl` and `fastly-client-ip`, and kept
`x-debug-fastly-pop` among its internal headers. Each now comes from the
adapter, the way the cache headers do.

- `platform/request_headers.rs` holds three lists an adapter declares. TLS
  headers are read for the scheme and never forwarded to an origin,
  spoofable headers are stripped at entry, and internal headers are never
  forwarded to a third party.
- `SPOOFABLE_FORWARDED_HEADERS` keeps the three standard names, and
  `sanitize_forwarded_headers` also strips the declared TLS and spoofable
  headers.
- `detect_request_scheme` checks the declared TLS headers as its fourth
  signal, and the origin request strips them.
- `is_internal_header` joins `INTERNAL_HEADERS` with the declared internal
  headers. The trusted client IP validation, Permutive and Lockr use it.
- The trusted client IP header may be an `x-` header or a platform's own
  header ending `-client-ip`, so `fastly-client-ip` stays valid without
  core naming it. The guide states the new rule.
- The Fastly adapter declares its three headers in `declare_platform`,
  which `run` calls before serving so that settings validation sees them,
  and which every state build also calls. Its entry strip names its own
  two headers directly.
- The `build_state_with_registrations` doc comment had become attached to
  the cache header constant and is back on the function.
`[device] module = "fastly"` selected the module the Fastly adapter
builds, so core's device selection and the registry both named Fastly.
The value is now `platform`, as it already is for `[geo] module`, and
`build_device_module` takes a `build_platform` factory. The Fastly module
keeps its own id, `fastly`.

A document that still writes `fastly` names a device module no running
module supplies, which the registry refuses with a configuration error,
so documents move to `platform` with this change. The guide, the example
settings and the device crates' docs say `platform`.
`platform_response_to_fastly` returns a buffered response and
`platform_response_to_fastly_asset` an asset response that keeps a
stream, and neither touches Fastly. They are now
`platform_response_to_buffered` and `platform_response_to_asset`, and the
error and doc text that called the result a Fastly response says what it
is.
The review found two blocking issues. A client-sent x-debug-fastly-pop now reaches Permutive and Lockr on the Axum, Cloudflare and Spin adapters, and the declared spoofable header list is read only by a function whose one production caller declares nothing. The spec now states both as the code has them today and leaves the code fixes as open questions.

It also adds the effects on the other adapters, the scheme change on Axum and Cloudflare, where each refusal happens, the order for changing the package and the device selector together, the Fastly name that stays, the untested paths and the corrected counts and test descriptions. The changelog entry gains the x-debug-fastly-pop change and the DataDome guide names the staging condition as the platform's declaration.
@jwrosewell
jwrosewell added this pull request to stack #1401 October 15, 2026 17:56
The second review found no blocking issues. The changelog now gives the strip of declared spoofable headers its precondition, that the adapter calls sanitize_forwarded_headers, and adds the scheme change on the Axum and Cloudflare adapters and the move of the x-debug-fastly-pop refusal from ts config validate to the service load on Fastly.

The spec names that move in its Fastly sentence, corrects when the Axum, Cloudflare and Spin adapters strip fastly-client-ip, which each does where trusted_client_ip names it as ip_header, and lists the module comment in request_headers.rs among the comments left for a later change.
From this branch core reads a config store entry as the platform's store
returns it, and the Fastly adapter follows a chunk pointer itself in
config_chunks::whole_entry. The config push test that read a chunked
document back through core's reader therefore moves its round trip to
config_chunks.rs, as the selector design says.

Test: a_written_document_is_read_back_whole reads a fixture of the
entries `ts config push --local --key a.publisher.example` wrote for a
document too long for one entry, chunks first and the pointer last, and
checks whole_entry gives back the envelope with the pushed publisher
domain. The CLI test keeps its checks of the layout, that every chunk is
written before the pointer and that the pointer names the chunks in the
order they were written.
@jwrosewell
jwrosewell added this pull request to stack #1418 October 19, 2026 12:18
The handler tests of the header rewrite merged in main (#1238) build their
request with fastly-ssl: 1 to mark it HTTPS. This change takes that header
out of core, so core reads the request as plain HTTP and three of those
tests receive http:// where they expect https://. The core platform name
check also fails on the header's name. The helper now sends
x-forwarded-proto: https, which core reads on every platform and which the
origin redirect tests already use.

python scripts/lint-core-platform-names.py passes, and the three tests,
transformed_page_headers_map_origin_urls_to_serving_host,
bodiless_redirect_location_maps_origin_url_to_serving_host and
same_scheme_redirect_to_current_url_stays_on_serving_host, pass.
@jwrosewell
jwrosewell removed this pull request from stack #1418 October 19, 2026 16:30
@jwrosewell jwrosewell closed this Oct 19, 2026
@jwrosewell jwrosewell reopened this Oct 19, 2026
@jwrosewell
jwrosewell added this pull request to stack #1421 October 19, 2026 17:38
@jwrosewell
jwrosewell removed this pull request from stack #1421 October 19, 2026 18:29
@jwrosewell
jwrosewell added this pull request to stack #1422 October 19, 2026 19:20
@jwrosewell jwrosewell changed the title The Fastly names leave core for the Fastly adapter Move Fastly names out of core Oct 19, 2026
@jwrosewell
jwrosewell marked this pull request as ready for review October 19, 2026 20:24

This branch has not been deployed

No deployments
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.

1 participant