Skip to content

Add native secret-store config resolution - #1036

Merged
ChristianPavilonis merged 15 commits into
mainfrom
edgezero-secrets
Sep 8, 2026
Merged

ChristianPavilonis merged 15 commits into
mainfrom
edgezero-secrets

Conversation

@ChristianPavilonis

@ChristianPavilonis ChristianPavilonis commented Aug 17, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Store references to static credentials in Trusted Server configuration, then resolve those references from the platform's secret store after the signed configuration blob passes integrity checks.
  • Resolve publisher, Edge Cookie partner, handler, Tinybird, DataDome, and S3 credentials while loading typed application configuration. Runtime code receives redacted values and no longer reads these static credentials during requests.
  • Keep trusted_server_secrets as the logical store name while allowing adapters to map it to a physical store such as Fastly's ts_secrets. Missing or invalid secrets fail configuration loading without exposing their values.
  • Accept the old feature-specific secret_store selectors for one release, warn that they are ignored, and omit them when serializing configuration.
  • Make Edge Cookie partner api_token references optional. Partners without one remain available for source-domain lookup, bidstream EIDs, and outbound pull sync, but cannot authenticate to the inbound identify or batch-sync APIs. ts_pull_token remains required only when pull sync is enabled.

This fixes the deployment failure where a valid secret existed in Fastly but Trusted Server opened the logical store name instead of the mapped physical store.

Changes

File Change
.env.dev Clarify that the file contains non-secret development overlays and point local users to the config blob and secret-store setup.
.env.example Document logical-to-physical secret-store mapping and replace plaintext secret examples with platform secret-store guidance.
Cargo.toml Pin the EdgeZero revision that supports optional secret paths and persisted Fastly store mappings.
Cargo.lock Record the updated EdgeZero dependency graph.
crates/trusted-server-adapter-axum/src/app.rs Pass the Axum secret-store adapter into typed settings loading.
crates/trusted-server-adapter-cloudflare/src/app.rs Resolve configuration references through the Cloudflare Worker environment during startup.
crates/trusted-server-adapter-cloudflare/src/lib.rs Make the Worker environment available to startup configuration loading.
crates/trusted-server-adapter-cloudflare/src/platform.rs Expose the Cloudflare secret-store adapter within the crate for configuration resolution.
crates/trusted-server-adapter-cloudflare/wrangler.ci.toml Add fictional local secret bindings used by Cloudflare integration tests.
crates/trusted-server-adapter-cloudflare/wrangler.toml Document how operators provision Worker secrets referenced by application configuration.
crates/trusted-server-adapter-fastly/src/app.rs Load Fastly runtime store mappings, resolve typed secrets at startup and reload, and cover mapped-store behavior with tests.
crates/trusted-server-adapter-fastly/src/main.rs Build the Fastly application with the runtime environment mapping used by EdgeZero.
crates/trusted-server-adapter-fastly/src/tinybird.rs Use the Tinybird token resolved during configuration loading instead of reading a secret store during each request.
crates/trusted-server-adapter-spin/spin.toml Declare Spin secret variables for application-config references.
crates/trusted-server-adapter-spin/src/app.rs Pass the Spin secret store into startup configuration loading.
crates/trusted-server-adapter-spin/src/platform.rs Add the Spin adapter used to resolve typed application secrets.
crates/trusted-server-core/src/config.rs Mark secret-bearing fields, make partner API-token references optional, add conditional requirements, support the deserialize-only selector bridge, and split deploy-time structure checks from post-resolution validation.
crates/trusted-server-core/src/config_payload.rs Verify blob integrity before resolving references and add fail-closed tests for missing, malformed, inactive, and optional secrets, including partners without API tokens.
crates/trusted-server-core/src/ec/auth.rs Keep inbound bearer authentication fail-closed when a configured partner has no API token.
crates/trusted-server-core/src/ec/registry.rs Register every partner by source domain while hashing and indexing only configured API tokens; validate partner structure before deployment and defer token-value checks until references have been resolved.
crates/trusted-server-core/src/integrations/datadome.rs Load DataDome credentials into redacted runtime settings and ignore the old store selector.
crates/trusted-server-core/src/integrations/datadome/protection.rs Use the resolved DataDome key without a request-time secret-store lookup while retaining the configuration-gated test bypass.
crates/trusted-server-core/src/lib.rs Export the secret-resolution module.
crates/trusted-server-core/src/proxy.rs Use resolved publisher and S3 credentials and remove feature-specific runtime secret-store reads.
crates/trusted-server-core/src/publisher.rs Update publisher tests for DataDome's typed secret reference.
crates/trusted-server-core/src/secret_resolution.rs Add recursive typed resolution for nested objects, arrays, optional containers, and redacted errors.
crates/trusted-server-core/src/settings.rs Separate reference-bearing application configuration from resolved runtime settings, make partner API tokens optional, and sanitize validation failures.
crates/trusted-server-core/src/settings_data.rs Define the logical default secret store and thread it through config-store loading.
crates/trusted-server-integration-tests/Cargo.toml Make TOML parsing available to the integration config generator.
crates/trusted-server-integration-tests/fixtures/configs/trusted-server.integration.toml Replace integration fixture credentials with secret key names.
crates/trusted-server-integration-tests/fixtures/configs/viceroy-template.toml Add the local runtime mapping and fictional secret-store entries used by Viceroy.
crates/trusted-server-integration-tests/src/bin/generate-viceroy-config.rs Generate local secret-store data for references found in integration configuration.
crates/trusted-server-integration-tests/tests/common/config.rs Build test envelopes from the typed application-config representation.
crates/trusted-server-integration-tests/tests/environments/axum.rs Supply fictional referenced secrets to the Axum integration environment.
docs/guide/asset-routes.md Update asset-route examples to use the resolved publisher secret model.
docs/guide/configuration.md Explain reference syntax, conditional requirements, optional partner API access, compatibility behavior, redaction, and migration from feature-specific stores.
docs/guide/ec-setup-guide.md Clarify that the demo requires a partner API token because it exercises inbound identify and batch-sync APIs, while other partners may omit it.
docs/guide/fastly.md Document Fastly's logical trusted_server_secrets to physical ts_secrets mapping and provisioning requirements.
docs/guide/getting-started.md Add local setup instructions for config blobs and referenced secret values.
docs/guide/integrations/datadome.md Replace the old DataDome store selector with a typed key reference.
fastly.toml Configure the local Fastly runtime mapping and a placeholder physical secret store.
trusted-server.example.toml Replace plaintext credentials with key names, mark partner API tokens as optional for partners that do not use inbound APIs, and add Tinybird and DataDome reference examples.

Scope

This PR touches the core schema, each adapter startup path, integration fixtures, and operator documentation because secret references must behave the same on Fastly, Axum, Cloudflare, and Spin. The request-signing key collection, rotation stores, and Fastly management credentials remain outside this change because those stores are managed at runtime rather than loaded as static application configuration.

EdgeZero dependency

This PR depends on stackpop/edgezero#344, "Support optional typed secret paths and Fastly store mappings." That PR adds optional intermediate path handling and persists validated logical-to-physical store mappings during Fastly provisioning and staged deployment. Trusted Server pins its tested commit, 0d6ebf9b0250efa5f7031a93ec7b7f09f2c9bf34. All checks on the EdgeZero PR pass.

Closes

Closes #684

Test plan

  • 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, no JS source changed
  • JS format: cd crates/trusted-server-js/lib && npm run format, no JS source changed
  • Docs format: cd docs && npm run format
  • WASM build: the deployment workflow built and staged the Fastly artifact
  • Manual testing via fastly compute serve
  • Other: cargo test-cloudflare, cargo test-spin, adapter parity tests, CLI tests, Cloudflare and Spin WASM checks, all adapter-specific Clippy targets, and git diff --check
  • Staged Fastly deployment: run 32784895487 passed /health; a settings-load probe found no secret-resolution or application-state errors

Checklist

  • Changes follow CLAUDE.md conventions
  • No unwrap() in production code, use expect("should ...")
  • Logging follows project conventions; no direct stdout or stderr logging was added
  • New code has tests
  • No secrets or credentials committed

@ChristianPavilonis
ChristianPavilonis marked this pull request as draft August 18, 2026 18:29
@ChristianPavilonis ChristianPavilonis changed the title feat: add native secret-store config resolution Add native secret-store config resolution Aug 18, 2026
@aram356 aram356 added this to the 202608 milestone Aug 18, 2026
@aram356

aram356 commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

@ChristianPavilonis to test it before merging into #1019

@ChristianPavilonis
ChristianPavilonis marked this pull request as ready for review August 24, 2026 22:48
@ChristianPavilonis
ChristianPavilonis requested review from aram356 and prk-Jr and removed request for aram356 August 24, 2026 22:48

@prk-Jr prk-Jr 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

Moves static app-config credentials from plaintext blob values to secret-store key
references resolved after envelope verification, and fixes the logical-to-physical
store mapping that broke the Fastly deployment. The core design is sound: integrity
verification genuinely precedes resolution, resolution is atomic (the blob is left
untouched on failure), the deploy/load validation split keeps value checks on the load
path where PartnerRegistry::from_config still fails closed, and the two end-to-end
payload tests cover both the all-credentials-resolve and inactive-feature-skip arms.

Four blocking items: resolution discards the one diagnostic that would explain a
mis-mapped store, the documented migration order opens a total outage window, the
Fastly Hooks::routes() path reads the store mapping from the wrong source, and the
EdgeZero dependency is pinned to an unmerged upstream commit.

3 of the inline comments below carry a one-click GitHub suggestion — use
Commit suggestion (or Add suggestion to batch) to apply them as commits on
the PR branch. The remaining comments describe the fix in prose because the change
spans multiple files, needs a new import, or adds code outside the diff. No
suggestion in this review was scratch-verified
— local runs were skipped for this
pass, so please re-run the matching checks after applying.

Blocking

🔧 wrench

  • Secret-store resolution throws away every adapter's diagnostic — see inline at crates/trusted-server-core/src/secret_resolution.rs:164
  • Documented migration order opens a full outage window — see Cross-cutting below
  • Hooks::routes() reads the wrong source for the store mapping — see inline at crates/trusted-server-adapter-fastly/src/app.rs:1261

❓ question

  • EdgeZero pinned to an unmerged upstream PR — see Cross-cutting below

Non-blocking

♻️ refactor / 🤔 thinking / ⛏ nitpick / 🌱 seedling

  • Required S3 secret references still have serde defaults — see inline at crates/trusted-server-core/src/settings.rs:767
  • Feature-enablement logic duplicated in three places — see inline at crates/trusted-server-core/src/config_payload.rs:63
  • EchoSecretStore makes resolution untestable — see inline at crates/trusted-server-core/src/config_payload.rs:145
  • expect() on the Tinybird token traps the Wasm guest — see inline at crates/trusted-server-adapter-fastly/src/tinybird.rs:57
  • Deploy validation misses duplicate partner key names — see inline at crates/trusted-server-core/src/ec/registry.rs:74
  • New docs bullets lost their markdown hard breaks — see inline at docs/guide/configuration.md:1620
  • partners = [] is redundant and a footgun — see inline at trusted-server.example.toml:17
  • Two overlapping ways to express leaf optionality — see inline at crates/trusted-server-core/src/secret_resolution.rs:64
  • Spin's five declared secret variables read as a contract — see inline at crates/trusted-server-adapter-spin/spin.toml:28

Cross-cutting / body-level findings

  • 🔧 Documented migration order opens a full outage window — docs/guide/configuration.md:60-72 gives the order: populate store, replace values with key names, ts config validate + ts config push, then "restart/redeploy instances as needed."

    Step 3 lands the reference-bearing blob while the old binary is still serving. On Fastly each request reads the config store fresh, so from that instant every request runs Ec::validate_passphrase — which requires at least 32 bytes on main today (MIN_PASSPHRASE_LENGTH = 32, crates/trusted-server-core/src/settings.rs) — against passphrase = "ec_passphrase" (13 bytes). That yields short_passphrase, config load fails, and the service returns its startup-error response for all traffic until the redeploy finishes.

    The reverse mismatch fails too: a new binary reading a plaintext blob resolves each plaintext secret as a key name. There is no safe intermediate state — the binary and the blob have to flip together, and the doc currently puts the break in the middle. Please correct the ordering and add an explicit warning that a mismatched binary/blob pair fails config load outright. The staged Fastly deployment cited in the PR description would not surface this, since no old binary is in play there.

  • ❓ EdgeZero pinned to an unmerged upstream PR — Cargo.toml:57-62 moves all six edgezero crates from stable tag v0.0.4 to git rev 0d6ebf9b0250efa5f7031a93ec7b7f09f2c9bf34, a commit on the still-open stackpop/edgezero#344. Merging this puts main on a branch commit of an unmerged PR: if #344 is rebased or force-pushed before it merges, that commit can become unreachable and main stops building.

    You disclosed this in the PR description, and the issue comment suggests this lands in #1019 first, so this may already be handled. It still needs an explicit answer because it constrains main: hold this PR until #344 merges and re-pin to a tag, or is a rev pin on main acceptable here?

  • 📝 CI coverage gap on the reviewed head — only Analyze (javascript-typescript) ran on 1315cdb1. The full gate suite (cargo fmt/test/clippy, all four adapters, cross-adapter parity, vitest, format-docs, integration and browser tests) last ran green on the merge commit 598f7100, three commits earlier. That leaves 070397f1 Resolve static credentials through typed config, b1e967e3, and 1315cdb1 without Rust, adapter, or lint coverage. Worth re-triggering the suite on the current head before merge, independent of the findings above.

  • 👍 validation_error_summary is a real leak fix — crates/trusted-server-core/src/settings.rs:2387-2424 walks ValidationErrors emitting only path: code, never validator's params, which hold the offending value. The previous code formatted ValidationErrors wholesale into a config error message.

  • 👍 Deleting S3_CREDENTIALS_CACHE removes a genuinely bad structure — crates/trusted-server-core/src/proxy.rs previously kept a process-global HashMap keyed on the plaintext secret access key, with unbounded growth and a poisoning-prone Mutex. Startup-resolved values are strictly better.

  • 👍 IntegrationSettings's custom Debug closes the DataDome-key leak that the flattened JsonValue map would otherwise print.

  • 👍 The two payload resolution tests are the right pair — resolves_all_static_credentials_from_the_mapped_default_store proves every path arm resolves through a mapped physical store, and inactive_optional_features_do_not_resolve_stale_secret_references proves disabled features do not demand stale references. Also good: dropping include_str!("trusted-server.example.toml") from the Spin and Cloudflare startup paths in favour of a hard error.

CI Status

  • Analyze (javascript-typescript): PASS
  • cargo fmt: not run on this head (PASS on 598f7100)
  • cargo test: not run on this head (PASS on 598f7100)
  • cargo test (axum native): not run on this head (PASS on 598f7100)
  • cargo test (cross-adapter parity): not run on this head (PASS on 598f7100)
  • cargo test (ts CLI, native): not run on this head (PASS on 598f7100)
  • cargo check (cloudflare native + wasm32-unknown-unknown): not run on this head (PASS on 598f7100)
  • cargo check/build/test (spin native + wasm32-wasip1): not run on this head (PASS on 598f7100)
  • integration tests: not run on this head (PASS on 598f7100)
  • integration tests (Fastly EC lifecycle): not run on this head (PASS on 598f7100)
  • browser integration tests: not run on this head (PASS on 598f7100)
  • prepare integration artifacts: not run on this head (PASS on 598f7100)
  • vitest: not run on this head (PASS on 598f7100)
  • format-typescript: not run on this head (PASS on 598f7100)
  • format-docs: not run on this head (PASS on 598f7100)
  • Analyze (rust): not run on this head (PASS on 598f7100)
  • Analyze (actions): not run on this head (PASS on 598f7100)
  • CodeQL: not run on this head (PASS on 598f7100)

No check reported a fail or cancel bucket. Branch protection reported no required checks for this PR.

Comment thread docs/guide/configuration.md Resolved
Comment thread trusted-server.example.toml Resolved
@ChristianPavilonis

Copy link
Copy Markdown
Collaborator Author

Review follow-up for b47ced81a:

  • The accepted diagnostics, DataDome pruning, Fastly mapping, EC validation, harness, example, and documentation changes are implemented. I replied to and resolved all 11 inline threads.
  • I am not adding migration orchestration for the plaintext-to-reference transition. Trusted Server has not entered production with the old plaintext configuration model, so secret references will be the production baseline rather than a live migration. Deployment machinery here would be speculative.
  • The EdgeZero revision remains an intentional dependency for RC validation. PR Support optional typed secret paths and Fastly store mappings edgezero#344 is still the upstream dependency; Add native secret-store config resolution #1036 should use its accepted final revision before a mainline merge if project policy requires that.
  • Local validation passed: all four adapter test suites, all six Clippy targets, Rust and docs formatting, git diff --check, focused regression tests, and both template-cache harness modes. GitHub checks are rerunning on the pushed head.

Re-requesting review from @prk-Jr.

@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-executed change: the resolution model (verify envelope, strip inactive references, resolve, validate runtime settings) is fail-closed, the push-time/runtime validation split is coherent across all four adapters, and the test coverage in config_payload.rs and secret_resolution.rs is thorough. Two blocking findings: a secret-exposure path in the resolution-failure error message, and the failed CodeQL check.

4 of the inline comments below carry a one-click GitHub suggestion — use Commit suggestion (or Add suggestion to batch) to apply them as commits on the PR branch. The remaining comments describe the fix in prose because the change touches multiple locations and can't be auto-applied.

Blocking

🔧 wrench

  • Resolution-failure error can log a plaintext secret from a legacy blob — see inline at crates/trusted-server-core/src/secret_resolution.rs:169
  • CodeQL check failed: 15 high rust/cleartext-logging alerts — see Cross-cutting below

Non-blocking

♻️ refactor / 🤔 thinking / ⛏ nitpick / 📝 note / 🌱 seedling

  • .env.example ships the Fastly store mapping active, breaking the documented Axum flow — see inline at .env.example:11 (suggestion)
  • Migration guide doesn't warn that previously legal short secrets now fail startup — see inline at docs/guide/configuration.md:70 (suggestion)
  • Missing required leaf reports "must be a string" instead of "missing" — see inline at crates/trusted-server-core/src/secret_resolution.rs:151 (suggestion)
  • server_side_key_secret_name holds the resolved key value at runtime — see inline at crates/trusted-server-core/src/integrations/datadome.rs:184 (suggestion)
  • validate_config_for_deploy uses HashMap<_, ()> as a set — see inline at crates/trusted-server-core/src/ec/registry.rs:77
  • Hooks::stores() duplicates edgezero.toml — see inline at crates/trusted-server-adapter-fastly/src/app.rs:1332
  • Cargo.lock rewrote prost's itertools edges — see Cross-cutting below

Cross-cutting / body-level findings

  • 🔧 CodeQL check failed: 15 high rust/cleartext-logging alerts. Not required under branch protection, but a CI gate this repo treats as blocking. I inspected all 15: they are taint over-approximation — CodeQL now treats everything flowing out of resolve_secret_references / validate_tinybird_secret / validate_admin_handler_passwords as secret-tainted and flags logs of plainly non-secret fields (asset-route prefixes in settings.rs, DataDome registration flags in datadome.rs:979, consent clamping in consent_config.rs, header names in response_privacy.rs, etc.). No alert is a real value leak — the nearest real vector is the inline finding at secret_resolution.rs:169. The alerts still need triage: dismiss each in the code-scanning UI with a justification (or add a CodeQL model/sanitizer exclusion), otherwise this check stays red here and re-fires on every future PR touching these paths.
  • ⛏ Cargo.lock moved prost's itertools dependency edges from 0.13.0 to 0.10.5. The edgezero pin update also rewrote prost-build/prost-derive's itertools edges down to the already-present 0.10.5 while 0.13.0 stays in the graph for other consumers — unintended churn from edge unification during the scoped update. Consider hand-restoring the 0.13.0 edges so the lock diff stays scoped to the edgezero bump.

CI Status

  • browser integration tests: PASS
  • integration tests (Fastly EC lifecycle): PASS
  • integration tests: PASS
  • CodeQL: FAIL
  • cargo test (ts CLI, native): PASS
  • cargo test (cross-adapter parity): PASS
  • cargo check/build/test (spin native + wasm32-wasip1): PASS
  • cargo check (cloudflare native + wasm32-unknown-unknown): PASS
  • format-docs: PASS (required)
  • cargo test: PASS (required)
  • cargo test (axum native): PASS
  • format-typescript: PASS (required)
  • Analyze (rust): PASS
  • Analyze (javascript-typescript): PASS
  • Analyze (javascript-typescript): PASS
  • cargo fmt: PASS (required)
  • prepare integration artifacts: PASS
  • vitest: PASS
  • Analyze (actions): PASS

Comment thread .env.example Resolved
Comment thread docs/guide/configuration.md Resolved

@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

Second pass, reviewing head 76f6f13. The feedback commit addresses every finding from the previous review: the resolution-failure error now drops both the key name and the underlying platform error (with a regression test asserting a legacy plaintext value never reaches diagnostics), the new 32-byte minimums were removed in favor of pre-PR behavior (bypass-credential strength enforcement moved back to request time, with a request-level test), .env.example no longer ships the Fastly mapping active, and the stores() metadata is now pinned to edgezero.toml by a manifest-parsing test. What remains blocking is the open CodeQL alert set; one stale doc claim and the lockfile nit round out the list.

1 of the inline comments below carries a one-click GitHub suggestion — use Commit suggestion to apply it as a commit on the PR branch.

Blocking

🔧 wrench

  • CodeQL: 15 high rust/cleartext-logging alerts still open — see Cross-cutting below

Non-blocking

⛏ nitpick

  • Stale "must be at least 32 bytes" claim for proxy_secret — see inline at docs/guide/configuration.md:361 (suggestion)
  • Cargo.lock prost itertools edges still rewritten — see Cross-cutting below

Cross-cutting / body-level findings

  • 🔧 CodeQL: 15 high rust/cleartext-logging alerts still open. Carried over from the previous review round. The code fix in 76f6f13 does not clear them — all 15 are taint over-approximation (CodeQL treats everything flowing out of resolve_secret_references / validate_tinybird_secret / validate_admin_handler_passwords as secret-tainted and flags logs of plainly non-secret fields), and all 15 remain open on this PR, so the CodeQL check will fail again once analysis reruns on this head. They need triage: dismiss each in the code-scanning UI with a justification, or add a CodeQL suppression/model exclusion — otherwise this check stays red here and re-fires on every future PR touching these paths.
  • ⛏ Cargo.lock still carries the rewritten prost itertools edges (0.13.0 → 0.10.5 while 0.13.0 stays in the graph for other consumers) — unaddressed nit from the previous review; the earlier inline thread on this stays open, so no new inline comment here. Hand-restoring the 0.13.0 edges keeps the lock diff scoped to the edgezero bump.

CI Status

GitHub checks have not yet run for head 76f6f13 — only one check has reported; everything else is pending/not started. Local verification was run in the reviewer worktree at this head instead: cargo fmt --all -- --check, cargo clippy-fastly, targeted cargo test-fastly for the modules this head touches (10 secret_resolution + 15 config_payload + 67 datadome + 43 registry + the fastly manifest-metadata test), and prettier for the changed docs — all pass.

  • Analyze (javascript-typescript): PASS
  • CodeQL: not run on this head (15 alerts from the prior analysis remain open)
  • browser integration tests: not run
  • integration tests (Fastly EC lifecycle): not run
  • integration tests: not run
  • cargo test (ts CLI, native): not run
  • cargo test (cross-adapter parity): not run
  • cargo check/build/test (spin native + wasm32-wasip1): not run
  • cargo check (cloudflare native + wasm32-unknown-unknown): not run
  • format-docs: not run (required; passes locally)
  • cargo test: not run (required; touched modules pass locally)
  • cargo test (axum native): not run
  • format-typescript: not run (required)
  • Analyze (rust): not run
  • cargo fmt: not run (required; passes locally)
  • prepare integration artifacts: not run
  • vitest: not run
  • Analyze (actions): not run

Comment thread docs/guide/configuration.md Resolved
ChristianPavilonis added a commit that referenced this pull request Aug 28, 2026
# Conflicts:
#	.env.example
#	Cargo.lock
#	crates/trusted-server-adapter-axum/src/app.rs
#	crates/trusted-server-adapter-fastly/src/app.rs
#	crates/trusted-server-core/src/config.rs
#	crates/trusted-server-core/src/config_payload.rs
#	crates/trusted-server-core/src/ec/registry.rs
#	crates/trusted-server-core/src/integrations/datadome.rs
#	crates/trusted-server-core/src/integrations/datadome/protection.rs
#	crates/trusted-server-core/src/proxy.rs
#	crates/trusted-server-core/src/secret_resolution.rs
#	crates/trusted-server-core/src/settings.rs
#	docs/guide/configuration.md
#	docs/guide/ec-setup-guide.md
#	docs/guide/getting-started.md
#	docs/guide/integrations/datadome.md
#	docs/guide/proxy-signing.md
#	scripts/template-cache-local-test.sh
#	trusted-server.example.toml
@aram356 aram356 modified the milestones: 202608, 202609 Sep 1, 2026

@prk-Jr prk-Jr 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

Adds secret-store reference resolution to app config: the blob carries key names, and each adapter resolves them into redacted runtime values after integrity verification. The core mechanism is well built and genuinely fail-closed — one central resolution point, envelope.verify() strictly before resolution on all four adapters, clone-then-swap so a partial resolution never reaches Settings, empty resolved values rejected centrally, no secret in any error message, and zero per-request app-config secret reads left anywhere. The api_token: Option auth change is fail-closed and verified from both directions. The Fastly logical→physical bug is genuinely fixed on the startup path and all three reload paths.

The blockers are concentrated in the operator-facing surface rather than the resolution logic. Three of them produce an outage or a dead local environment for anyone following the instructions as written.

10 of the inline comments below carry a one-click suggestion — use Commit suggestion (or Add suggestion to batch) to apply them. Every suggestion was applied in a scratch worktree at this head and verified: cargo fmt --all -- --check clean, clippy-fastly / clippy-axum / clippy-cloudflare / clippy-spin-native all clean at -D warnings, 2,477 tests passing across test-fastly (including 2,284 core tests), test-axum, test-cloudflare and test-spin, pinned prettier clean on both changed docs, and both changed TOML files re-parsed. The remaining comments describe the fix in prose because the change spans multiple files, needs a validator split first, or targets lines outside a RIGHT-side diff hunk.

Blocking

🔧 wrench

  • KeyInNamedStore fields would silently resolve from the default store — see inline at crates/trusted-server-core/src/secret_resolution.rs:29
  • trusted_client_ip.shared_secret is the one credential left plaintext in the blob — see inline at crates/trusted-server-core/src/config.rs:135
  • Local fastly compute serve cannot start — no app-config secrets seeded — see inline at fastly.toml:62
  • Migration runbook never creates or links the physical store — see inline at docs/guide/configuration.md:70
  • Deploy path repeats the same omission — see inline at docs/guide/getting-started.md:160
  • Axum quick-start cannot complete — starter config ships reserved placeholder domains — see inline at .env.dev:5 and docs/guide/getting-started.md:73
  • Stale guardrail claim: deploy validation no longer rejects a placeholder handler password — see inline at trusted-server.example.toml:41
  • CodeQL gate is red — see Cross-cutting below
  • PR is CONFLICTING; two conflicts are competing designs — see Cross-cutting below

❓ question

  • Spin hardcodes the config-store name, contradicting the docs this PR adds — see inline at crates/trusted-server-adapter-spin/src/app.rs:61
  • Unrelated Cargo.lock churn: prost's itertools 0.13.0 → 0.10.5 — see inline at Cargo.lock:3679
  • PR description is inaccurate in two places — see Cross-cutting below

Non-blocking

🤔 thinking / ♻️ refactor / 🏕 camp site / ⛏ nitpick

  • Rollback window: an old binary uses the documented key name as a live HMAC key — see inline at crates/trusted-server-core/src/config.rs:136
  • A suppressed telemetry error became a per-request 500 — see inline at crates/trusted-server-adapter-fastly/src/tinybird.rs:60
  • validate_config_for_startup / _for_deploy are byte-identical, so resolved_secrets is a no-op — see inline at crates/trusted-server-core/src/config.rs:284
  • pull_sync_enabled read with as_bool() but deserialized with from_value_or_str — see inline at crates/trusted-server-core/src/config_payload.rs:84
  • resolve_leaf discards the PlatformError cause, losing the primary triage signal — see inline at crates/trusted-server-core/src/secret_resolution.rs:167
  • No test that a required reference failing lookup fails the load — see inline at crates/trusted-server-core/src/config_payload.rs:455
  • ts_pull_token's requirement has zero coverage on either side — see inline at crates/trusted-server-core/src/ec/registry.rs:403
  • require_nonempty_token is a flag that earns nothing — see inline at crates/trusted-server-core/src/ec/registry.rs:348
  • Cloudflare re-resolves every secret per request, against request #1's Env — see inline at crates/trusted-server-adapter-cloudflare/src/app.rs:51
  • Cloudflare is the only adapter with no store mapping and no comment saying why — see inline at crates/trusted-server-adapter-cloudflare/src/app.rs:130
  • spin.toml's request-signing config variables are now silently inert — see inline at crates/trusted-server-adapter-spin/src/platform.rs:127
  • EDGEZERO__STORES__SECRETS__…__NAME silently redirects the Axum lookups — see inline at docs/guide/getting-started.md:104
  • Only worked example puts the passphrase in argv — see inline at docs/guide/fastly.md:326
  • Five secret key names live in five files with nothing keeping them in sync — see inline at crates/trusted-server-integration-tests/fixtures/configs/trusted-server.integration.toml:4
  • validation_error_summary drops the validator message — see inline at crates/trusted-server-core/src/settings.rs:3403
  • Dead deprecation branches in try_new — see inline at crates/trusted-server-core/src/integrations/datadome.rs:390
  • S3Credentials rebuilt with three allocations per signing call — see inline at crates/trusted-server-core/src/proxy.rs:878
  • Axum reaches for a fully-qualified path instead of the import above it — see inline at crates/trusted-server-adapter-axum/src/app.rs:68
  • pub mod secret_resolution should be pub(crate) — see inline at crates/trusted-server-core/src/lib.rs:67
  • tinybird.remove("access_token_secret") is dead — see inline at crates/trusted-server-core/src/config_payload.rs:73
  • Duplicate use lines — see inline at crates/trusted-server-core/src/settings_data.rs:6
  • Two assertions without messages — see inline at crates/trusted-server-adapter-fastly/src/app.rs:1432
  • EcPartner::api_token doc still says "Plaintext API token" — see inline at crates/trusted-server-core/src/settings.rs:377
  • .env.example ordering reads backwards — see inline at .env.example:9
  • Real deployed Fastly hostname retained — see inline at docs/guide/ec-setup-guide.md:31

📝 note

  • Severity change: DataDome went from request-time fail-open to boot-time fail-closed — see inline at crates/trusted-server-core/src/integrations/datadome/protection.rs:86
  • Trimming the resolved bypass credential invalidates whitespace-carrying values — see inline at crates/trusted-server-core/src/integrations/datadome.rs:408
  • Every Fastly request now opens a config store and makes ~10 dictionary reads — see inline at crates/trusted-server-adapter-fastly/src/main.rs:93
  • stores().kv is declared but nothing on the Fastly path consumes it — see inline at crates/trusted-server-adapter-fastly/src/app.rs:1338
  • jq dependency not in Prerequisites — see inline at docs/guide/getting-started.md:80
  • configuration.md S3 table vs example key mismatch — see inline at docs/guide/configuration.md:1099

👍 praise

  • Hand-written IntegrationSettings Debug closes a real leak — see inline at crates/trusted-server-core/src/settings.rs:220
  • hooks_store_metadata_matches_edgezero_manifest pins exactly the right invariant — see inline at crates/trusted-server-adapter-fastly/src/app.rs:1394
  • Order of operations is right and centrally enforced — see inline at crates/trusted-server-core/src/config_payload.rs:38

Cross-cutting / body-level findings

  • 🔧 CodeQL gate is red — 15 open rust/cleartext-logging high alerts. Not in branch protection's required set, so not merge-blocking mechanically. All 15 read as false positives: validate_tinybird_secret (settings.rs:1971) formats only "{setting} must be non-empty after secret resolution" — the setting name, no value; the flagged sinks log route.prefix, a cache rule id, a path, and DataDome's sdk_origin/rewrite_sdk/enable_protection; the named source try_new_with_secret_validation does not exist anywhere in the tree; and 9 of the 15 sinks are in files this PR does not touch (consent_config.rs, storage/kv_store.rs, response_privacy.rs, auction/orchestrator.rs, management_api.rs, axum/src/platform.rs). Root cause is field-insensitive taint: Settings now carries resolved plaintext secrets, so every log of any Settings-derived field lands in a taint path. The architectural signal is real and new even though each alert is not — before this PR, "log a Settings field" could not leak a credential. Resolve by dismissing the 15 with a written justification, so the next true positive in this rule is visible again; or better, give resolved secrets a wrapper whose Debug/Display/Serialize cannot emit the value. Note Redacted is serde-transparent, so it does not close the serialize half.

  • 🔧 PR is CONFLICTING; two of the six conflicts are competing designs. git merge-tree origin/main conflicts in Cargo.lock, Cargo.toml, crates/trusted-server-adapter-fastly/src/app.rs, crates/trusted-server-adapter-fastly/src/main.rs, crates/trusted-server-core/src/config.rs and docs/guide/configuration.md. In config.rs, main already carries a secret_fields() stub returning Vec::new() whose comment defers secret-store references "plus operator migration work tracked separately" — worth confirming that deferred work is what this PR delivers. In the Fastly adapter, main threads &EnvConfig with config_store_name(env) / config_key(env), while this PR replaces that seam with RuntimeStoreConfig / DEFAULT_CONFIG_STORE_ID / env_config_from_runtime_dictionary. Separately, this PR rewrites "EdgeZero's env overlay" → "The pinned EdgeZero loader" in three places in configuration.md; that phrasing only holds while pinned to a rev and goes stale as soon as the pin returns to a tag.

  • ❓ The PR description is inaccurate in two places. (1) It says generate-viceroy-config.rs "Generate[s] local secret-store data for references found in integration configuration." It does not — build_app_config_envelope (lines 113-134) only swaps Settings::from_toml + validate_settings_for_deploy for toml::from_str::<TrustedServerAppConfig> + TrustedServerAppConfig::new, and generated_config_store_blocks (148-156) still emits only the config-store block. Every secret value is hand-maintained. (2) It describes a "signed configuration blob"; envelope.verify() is a self-computed canonical SHA-256 (edgezero-core/src/blob_envelope.rs:87-99), not a signature. The ordering is right and it is the correct primitive for the chunked Fastly path, but it defends against truncation and corruption — not against someone with config-store write access. config_payload.rs:25-26 already says "integrity verification", which is the accurate wording.

  • 📝 The EdgeZero pin is a commit that exists on no upstream branch or tag. Cargo.toml:57-62 pins all six edgezero crates to rev 0d6ebf9b0250efa5f7031a93ec7b7f09f2c9bf34. Verified against stackpop/edgezero: it is not an ancestor of origin/main, git branch -a --contains and git tag --contains are both empty, and it is reachable only as a bare refs/commit/<sha>. It is 140 commits behind v0.0.7 (5c9886e5), which main now uses — so merging as-is downgrades EdgeZero across adapter-{axum,cloudflare,fastly,spin}, cli and core. Upstream stackpop/edgezero#344 is still open with mergeCommit: null, and its current head is 055f7e94, from which the pinned rev has also diverged (behind 140, ahead 12) — the branch was rebased since. If #344 lands squashed or rebased, this SHA becomes unreferenced and can be garbage-collected, at which point cargo fetch fails on every cold cache including CI. All seven edgezero packages in Cargo.lock do move consistently, so nothing is left behind on the old tag. Flagged as informational — the merge-ordering call belongs to the author and the release owner.

  • 🤔 Viceroy "Option A" in getting-started.md:46-62 gained a required setup step documented nowhere. That section is untouched by this PR and still reads fastly compute serve with no config or secret preparation, but local serve now requires hand-seeded secrets (see the fastly.toml comment). grep -rn "local_server.secret_stores" docs/ returns only key-rotation.md and request-signing.md, both about signing_keys. Option B got a full rewrite; Option A got nothing. The recipe already exists at scripts/template-cache-local-test.sh:227-243. Body-level because line 46 is outside every RIGHT-side hunk.

  • ♻️ resolve_secret_references deep-clones the whole config for an atomicity guarantee no caller uses (secret_resolution.rs:29 and :43). The only production caller is config_payload.rs:53, which passes a local data that is dropped on the error path anyway; the clone's sole beneficiary is does_not_mutate_data_when_resolution_fails (:396-407). It costs a full deep copy of the config JSON per instance boot inside Wasm. Resolving in place is equivalent for every real caller. Body-level because it targets the same lines as the KeyInNamedStore suggestion.

  • ⛏ Stale S3Credentials doc claims a runtime store read and a deleted cache (crates/trusted-server-core/src/s3_sigv4.rs:34-36). Both claims are now false: apply_asset_origin_auth builds these from already-resolved config, and S3_CREDENTIALS_CACHE was removed by this PR. s3_sigv4.rs is not in the diff, so this cannot be an inline comment. Proposed replacement:

    /// Values are already resolved from the app-config secret store when settings are
    /// built, so the caller passes them straight through without a runtime store read.
    /// Temporary credentials can include a session token, which becomes the signed
    /// `x-amz-security-token` header.
    
  • ⛏ Struct doc at settings.rs:355-356 is now false. "the plaintext is never stored at runtime" holds for PartnerConfig (hash only) but not for EcPartner.api_token, which holds the resolved plaintext in Settings for the process lifetime. Worth narrowing to "the registry stores only the hash". Outside a hunk, so body-level.

  • 📌 Pre-existing real-world values in fastly.toml, all outside every diff hunk. fastly.toml:4 authors = ["jason@stackpop.com"], :10 service_id = "dysUw6h73VzeomD61eal85", and :50 data = "NVnTYrw5xoyTJDOwoUWoPJO3A6UCCXOJJUzgGTxxx7k=" — a base64 32-byte Ed25519-shaped value in signing_keys whose embedded xxx suggests deliberate mangling. viceroy-template.toml:55-59 carries an explicit "generated for testing, never used in production" attestation for the same value; fastly.toml has none. Not asked for in this PR, but a secrets-hardening PR is the natural place to file it.

  • 👍 Verified clean and worth stating explicitly, so it is clear these were checked rather than skipped: verification-before-resolution ordering on all four adapters; a swallowed-failure scan of every added line (ok(), unwrap_or_default(), unwrap_or(false), let _ = — the only hits are two intentional OnceCell::set calls); per-request versus startup secret reads (the whole per-request class is closed, and the only remaining secret_store() callers read the separate request_signing.secret_store_id); the api_token: Option auth change traced from both directions; the deploy/runtime validation split, where runtime is a strict superset for value checks and a net tightening versus main; the legacy-selector compatibility bridge (all three warn, none survives a round trip, and init_cli_logger sets LevelFilter::Info so the warnings are actually visible); recursive resolution edge cases (empty arrays, Option containers, null versus absent, the internally-tagged AssetOriginAuth, non-string leaves, and no overlapping SecretField paths); spin.toml's secret-variable encoding byte-for-byte against spin_secret_variable_name; the Wrangler CI binding names and their fictional values; naming drift across code and all five manifests; WASM gating on every new item; secret exposure via every runtime Settings serialization path; and scripts/template-cache-local-test.sh end to end including its CI greps. No real secrets or real-world values are introduced anywhere by this PR, and this PR structurally reduces the tracked-fastly.toml secret-leak risk.

Recommendation

Hold. The resolution mechanism is sound and the auth change is correct — the work needed is on the operator-facing surface: the three docs/local-dev blockers, the KeyInNamedStore guard, and either closing the trusted_client_ip.shared_secret gap or stating the deferral explicitly. The merge will also need a decision on the RuntimeStoreConfig-versus-&EnvConfig seam against main.

CI Status

  • browser integration tests: PASS
  • integration tests: PASS
  • integration tests (Fastly EC lifecycle): PASS
  • prepare integration artifacts: PASS
  • CodeQL: FAIL
  • Analyze (rust): PASS
  • Analyze (actions): PASS
  • Analyze (javascript-typescript): PASS
  • cargo test: PASS (required)
  • cargo test (axum native): PASS
  • cargo test (ts CLI, native): PASS
  • cargo test (cross-adapter parity): PASS
  • cargo check (cloudflare native + wasm32-unknown-unknown): PASS
  • cargo check/build/test (spin native + wasm32-wasip1): PASS
  • cargo fmt: PASS (required)
  • format-typescript: PASS (required)
  • format-docs: PASS (required)
  • vitest: PASS

Comment thread fastly.toml Resolved
Comment thread .env.dev Resolved
Comment thread docs/guide/configuration.md Resolved
Comment thread docs/guide/getting-started.md Resolved
Comment thread docs/guide/ec-setup-guide.md Resolved
Comment thread docs/guide/configuration.md Resolved
aram356 added a commit that referenced this pull request Sep 3, 2026
# Conflicts:
#	crates/trusted-server-adapter-fastly/src/app.rs

@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

Third pass, reviewing head 4258a6b. The branch now merges main through #1077 and adds two substantive changes, both reviewed in full: the pass-2 docs suggestion applied verbatim, and a tip commit that integrates the newly-merged [trusted_client_ip] feature into the secret-reference model and bumps EdgeZero to v0.0.8. The new work held up under scrutiny — no new findings. What remains blocking is the open CodeQL alert set, carried over a second time.

Verified on the tip commit specifically:

  • trusted_client_ip.shared_secret is wired end-to-end: required leaf under an optional section, push-time reference check, and the ≥32-graphic-ASCII validator split onto the field leaf so it prunes at push time (proven by the new test using a 31-char key name) while still enforcing on the resolved value at runtime. A present section with a missing or unresolvable key fails closed with the path-only error message.
  • The json_bool_or_string_is_true fix for pull_sync_enabled matches from_value_or_str's bool::from_str exactly, so there is no accepted spelling the strip logic misses; tinybird.enabled and the DataDome flags are strict bools that fail deserialization loudly, so they need no equivalent.
  • The DataDome bypass credential is no longer normalized (restoring legacy request-time semantics, with a preservation test); the server-side key keeps its legacy trim — per-field legacy behavior, deliberate.
  • Merge resolutions for #1048/#1070/#1077 are correct; notably, client-IP resolution runs before header sanitization, and when app state fails to build the resolver receives no config and falls back to the untrusted peer address.

Blocking

🔧 wrench

  • CodeQL: 14 high rust/cleartext-logging alerts still open — see Cross-cutting below

Non-blocking

⛏ nitpick

  • Cargo.lock prost itertools edges still rewritten — see Cross-cutting below

Cross-cutting / body-level findings

  • 🔧 CodeQL: 14 high rust/cleartext-logging alerts still open. Carried over from the two previous review rounds. One of the original 15 is now marked fixed; the remaining 14 are open and none were dismissed, so the CodeQL check will fail again once analysis reruns on this head. As established in the first round, all of them are taint over-approximation (Settings-derived values flowing through the resolution/validation functions into logs of plainly non-secret fields), not real value leaks — but they still need triage: dismiss each in the code-scanning UI with a justification, or add a CodeQL suppression/model exclusion, otherwise the check stays red here and re-fires on every future PR touching these paths.
  • ⛏ Cargo.lock still carries the rewritten prost itertools edges (0.13.0 → 0.10.5 while 0.13.0 stays in the graph for other consumers) — unaddressed nit; the inline thread from the first review round stays open, so no new inline comment here.

CI Status

GitHub checks have not yet started for head 4258a6b (the latest complete runs — Run Format, Run Tests, Integration Tests, CodeQL Advanced, all green at the workflow level — are on the preceding merge commit 71df1aa). Local verification was run in the reviewer worktree at this head instead: cargo fmt --all -- --check, cargo clippy-fastly, cargo check-axum / cargo check-cloudflare / cargo check-spin under the EdgeZero v0.0.8 bump, targeted cargo test-fastly for the modules the tip commit touches (22 trusted_client_ip + 10 secret_resolution + 19 config_payload + 68 datadome + the fastly manifest-metadata test), cargo test-spin for the variable-encoder tests, and prettier for the changed docs — all pass.

  • Run Format: not started on this head (passes locally)
  • Run Tests: not started on this head (touched modules pass locally)
  • Integration Tests: not started on this head
  • CodeQL: not started on this head (14 alerts from the prior analysis remain open)

@prk-Jr prk-Jr 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

This lands the #846 secret-store migration cleanly: app config carries stable secret-store key names, resolution happens once after envelope verification, and runtime code no longer reads static credentials per request. The metadata contract, the deploy/runtime validation split, and the fail-closed test coverage are all well built.

One blocking problem: the tip commit (4258a6bf) bumped the EdgeZero pin from rev 0d6ebf9b to tag v0.0.8, and v0.0.8 changed how runtime_env_config reads the edgezero_runtime_env store. Rev 0d6ebf9b read unscoped EDGEZERO__* keys; v0.0.8 reads only service-scoped EDGEZERO__SERVICES__<service_id>__* keys, and its own doc comment states "Legacy unscoped entries are not read." The manifests and one doc snippet were left on the unscoped form, so the logical-to-physical secret store mapping is inert and every Fastly request returns 500.

4 of the inline comments below carry a one-click GitHub suggestion — use Commit suggestion (or Add suggestion to batch) to apply them as commits on the PR branch. The remaining comments describe the fix in prose because the change spans multiple files or lines outside the diff.

How the blocking finding was confirmed

Built the debug wasm at this head and ran it under Viceroy against the harness's generated config:

Config Result
this head as-is (ts_secrets store + unscoped mapping) HTTP 500 — failed to resolve secret reference at 'publisher.proxy_secret' from secret store 'trusted_server_secrets'
local secret store renamed to trusted_server_secrets, mapping removed HTTP 200
EDGEZERO__SERVICES__dysUw6h73VzeomD61eal85__… (real service id) HTTP 500
EDGEZERO__SERVICES____… (empty id) HTTP 500
EDGEZERO__SERVICES__0000000000000000000000__… (Viceroy's service_id()) HTTP 200

Note the store name in the error is the logical trusted_server_secrets, not the mapped ts_secrets — the mapping never reached EnvConfig. No "edgezero_runtime_env not found" warning appeared, so the store opened fine; only the key form was wrong.

Effect on the CI gates: scripts/template-cache-local-test.sh esi fails 0 passed / 13 failed at this head (every request 500). With the one-line key change it is 21 passed / 0 failed, and inline mode is 8 passed / 0 failed.

Blocking

🔧 wrench

  • edgezero_runtime_env mapping is inert under the v0.0.8 pin; every Fastly request 500s — see inline at fastly.toml:81
  • Same inert key in the Fastly integration fixture — see inline at crates/trusted-server-integration-tests/fixtures/configs/viceroy-template.toml:89
  • fastly.md documents the persisted mapping key in the form the runtime ignores — see inline at docs/guide/fastly.md:281

❓ question

  • No migration path for configs that currently hold real secret values — see the cross-cutting section below

Non-blocking

♻️ refactor / 🤔 thinking / ⛏ nitpick

  • Dead docs anchor #secret-store-migration — see inline at docs/guide/ec-setup-guide.md:41
  • validate_config_for_deploy is identical to validate_config_for_startup; the resolved_secrets flag is dead — see inline at crates/trusted-server-core/src/integrations/datadome.rs:480
  • First request's worker::Env is pinned for the isolate's lifetime — see inline at crates/trusted-server-adapter-cloudflare/src/app.rs:62
  • access_token_secret is dropped silently while every sibling deprecated field warns — see inline at crates/trusted-server-core/src/settings.rs:1901
  • Resolved DataDome server-side key is trimmed, but the resolved bypass credential deliberately is not — see inline at crates/trusted-server-core/src/integrations/datadome.rs:395
  • Harness now appends secret entries the tip commit made redundant, with different values — see inline at scripts/template-cache-local-test.sh:226

👍 praise

  • Disabled features never need their secret provisioned — see inline at crates/trusted-server-core/src/config_payload.rs:63

Cross-cutting / body-level findings

  • ❓ No migration path for configs that currently hold real secret values. validate_settings_for_deploy no longer calls reject_placeholder_secrets, and secret-reference validation is only "non-empty after trim". An operator who pushes their existing trusted-server.toml — the one whose publisher.proxy_secret, ec.passphrase, and handlers[*].password hold real credential values — gets those values written into the config-store blob, which is exactly the exposure this change sets out to remove, and then a hard startup failure on the next deploy because no secret-store key by that name exists. The docs describe the greenfield ordering well but I could not find a "migrating an existing config" path. Is there an intended step this PR should carry: a ts config push warning when a referenced key is absent from the target secret store, a docs section, or a release note?

  • 🤔 Per-request startup cost on Cloudflare and Spin. TrustedServerApp::routes() calls build_state() → load_startup_settings() on every request in both adapters, so each request now performs one secret-store read per declared secret field on top of the envelope parse and SHA-256 verify. Spin additionally opens and reads the KV config store per request, where it previously parsed a compile-time include_str! of trusted-server.example.toml. Fastly is inherently per-instance so it is unaffected. Worth confirming this is acceptable, or caching the built state per isolate.

  • 📌 docs/guide/cli.md:69 still says "EdgeZero v0.0.4". docs/guide/configuration.md:191 was de-versioned in this PR ("The pinned EdgeZero loader…") for exactly this reason; cli.md keeps the literal version while Cargo.toml now pins v0.0.8. Not a changed file, so it cannot carry an inline comment.

  • 📌 trusted-server.example.toml still ships deprecated selectors. Line 193 has # secret_store = "s3-auth" and line 511 has # credential_secret_store = "ts_secrets"; both fields are now deserialize-only, warn-and-ignore. The bypass block's comment also still says the credential "is loaded from the Secret Store at runtime (>= 32 bytes of high-entropy material)", which is now resolved at startup instead. Both lines fall outside this PR's diff hunks.

  • 👍 The validation split is carefully built. Moving every secret check to a field-level #[validate(custom(...))] — including splitting validate_trusted_client_ip's struct-level schema validator so only the header checks remain there — is what lets validate_excluding_secrets strip exactly the secret leaves without dropping unrelated structural errors. The IntegrationSettings custom Debug that prints only integration ids closes a real leak, since resolved DataDome credentials now live in that raw JSON map. And the error paths never carry secret values, with tests asserting the negative.

CI Status

Only one check reported on head 4258a6bf:

  • Analyze (javascript-typescript): PASS

Run Tests, Run Format, Integration Tests, and CodeQL's Rust analysis are not run on this head — their last runs were on the parent commit 71df1aa3, which is before the EdgeZero v0.0.8 bump that introduces the blocking finding. Branch protection reports no required checks on this branch. Results from running the full gate list locally against this head:

Gate Result
cargo fmt --all -- --check PASS
clippy-fastly / -axum / -cloudflare / -cloudflare-wasm / -spin-native / -spin-wasm PASS
clippy -p trusted-server-cli PASS
cargo test-fastly (167 + 2291 + 2 + 21 + 3) PASS
cargo test-axum / test-cloudflare / test-spin PASS
parity suite (13) PASS
cargo test -p trusted-server-cli (152 + 5 + 29 + 1) PASS
integration-tests --bins (8) PASS
docs prettier --check PASS
template-cache-local-test.sh esi FAIL — 0 passed, 13 failed
template-cache-local-test.sh inline FAIL

The two harness failures are the blocking finding, and both pass once it is fixed.

Comment thread fastly.toml Resolved
Comment thread docs/guide/fastly.md Resolved
Comment thread docs/guide/ec-setup-guide.md Resolved
Comment thread scripts/template-cache-local-test.sh Resolved
Comment thread Cargo.lock Resolved

@prk-Jr prk-Jr 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

Moves every static app-config credential from a plaintext value in the pushed blob to a secret-store key name resolved after envelope verification, on all four adapters. The deploy/runtime validation split is the right shape (validate_settings_for_deploy checks key-name structure, validate_settings_for_runtime runs every value validator against resolved secrets), and the fail-closed test matrix in config_payload.rs covers the hard cases: missing key, malformed path, non-string leaf, invalid UTF-8, empty resolved value, inactive-feature stripping, string-boolean flags in both directions, placeholder-after-resolution, and no-mutation-on-failure. I read every changed file and found no correctness bug.

The one thing blocking a clean review is a question about rollout, not a defect in the code: this is an unconditional cutover with no blob shape that both the old and new binary accept.

2 of the inline comments below carry a one-click GitHub suggestion — use Commit suggestion (or Add suggestion to batch) to apply them as commits on this branch. Both were applied in a scratch worktree at this head and verified before posting. The remaining comments describe the fix in prose because the change spans multiple files or lines outside the diff hunks.

Blocking

❓ question

  • No blob/binary version that both the old and new binary accept — see "Cross-cutting" below

Non-blocking

🔧 wrench

  • Broken doc anchor #secret-store-migration — see inline at docs/guide/ec-setup-guide.md:41 (suggestion)
  • trusted-server.example.toml still promises a push-time placeholder gate that no longer exists — see inline at trusted-server.example.toml:44

♻️ refactor

  • Resolver silently treats a named-store secret as a default-store secret — see inline at crates/trusted-server-core/src/secret_resolution.rs:30-33 (suggestion)
  • validate_config_for_deploy and validate_config_for_startup are byte-identical — see inline at crates/trusted-server-core/src/integrations/datadome.rs:480

🤔 thinking

  • "Is this secret required" now lives in three hand-maintained places — see inline at crates/trusted-server-core/src/config_payload.rs:63
  • Cloudflare keeps the first request's Env forever — see inline at crates/trusted-server-adapter-cloudflare/src/app.rs:51
  • Fastly now does N secret-store round trips per request — see "Cross-cutting" below

📌 out of scope

  • Spin request-signing config moved stores in a secrets PR — see inline at crates/trusted-server-adapter-spin/src/platform.rs:126

📝 note

  • PR body's EdgeZero pin is stale — see "Cross-cutting" below

Cross-cutting / body-level findings

  • ❓ No blob/binary version that both the old and new binary accept. There is no [secrets] mode flag and no plaintext fallback, so for an already-deployed service both deploy orders are a full outage until both halves land:

    • New WASM + currently-deployed plaintext blob → resolve_secret_references treats the live 32-byte proxy_secret as a key name, misses in the secret store, settings_from_config_blob errors → startup_error_router answers every route.
    • Key-name blob pushed + old WASM still live → Ec::validate_passphrase rejects "ec_passphrase" (13 chars < MIN_PASSPHRASE_LENGTH = 32) → the same startup-error router.

    That second direction only fails closed because key names happen to be short. A key name of 32+ characters passes validate_passphrase, is not in Ec::PASSPHRASE_PLACEHOLDERS, and the old binary then HMACs every EC ID off the key name — every visitor identifier rotates silently. publisher.proxy_secret has no length floor at all, so a rollback there always silently re-keys existing signed proxy URLs rather than failing.

    The configuration guide covers first-time setup ordering ("Start or deploy instances after the store and pushed config are both ready"), which has no analogue for a service that is already serving. Two questions: does trusted-server-deployer land the blob and the WASM atomically, or is a maintenance window assumed? And should the docs (or a deploy-time key-name check) steer operators away from 32+ character key names, so that a binary rollback stays fail-closed instead of silently re-keying EC identity?

  • 🤔 Fastly now does N secret-store round trips per request. Fastly Compute spawns a Wasm instance per request, so build_state → load_settings_from_config_store → resolve_secret_references runs on every request, and FastlyPlatformSecretStore::get_bytes performs a fresh SecretStore::open per key (crates/trusted-server-adapter-fastly/src/platform.rs:102). The starter config is 3 keys = 6 host calls; a config with partners, S3 asset auth, DataDome, and Tinybird is several times that. This replaces lazy per-feature reads (Tinybird per emit, DataDome per protected request, S3 behind a process LazyLock) with unconditional work on the TTFB path. Not a blocker, and the redacted-at-rest win is worth real latency — but the cheap mitigation is to open the store once and reuse the handle for every key in a single resolution pass, rather than per key.

  • 📝 PR body's EdgeZero pin is stale. The description says the branch pins commit 0d6ebf9b0250efa5f7031a93ec7b7f09f2c9bf34 from the unmerged stackpop/edgezero#344. Cargo.toml and Cargo.lock actually pin the released tag v0.0.8 (5679641), which is strictly better than a commit on an open PR branch. Worth correcting in the description so a future reader does not go looking for an unmerged dependency.

  • 👍 Resolved secrets cannot leak through validation errors. validation_error_summary (crates/trusted-server-core/src/settings.rs:3382) replaces format!("{err}") on ValidationErrors, whose params carry the offending value — so a short resolved passphrase can no longer print itself into the startup error. IntegrationSettings's hand-written Debug printing only sorted integration ids is the same instinct, and both have tests asserting the secret is absent from the output. That is exactly the failure mode this change would otherwise have introduced, caught before it shipped.

  • 👍 The fail-closed test matrix is the hard half of this feature, and it is actually covered. resolves_all_static_credentials_from_the_mapped_default_store, active_partner_pull_sync_fails_when_its_token_is_missing, inactive_optional_features_do_not_resolve_stale_secret_references, string_true/string_false_pull_sync_flag_*, does_not_mutate_data_when_resolution_fails, and failed_lookup_reports_safe_reference_context_without_secret_values together pin both the resolution semantics and the no-leak guarantee.

CI Status

  • integration tests: PASS
  • browser integration tests: PASS
  • integration tests (Fastly EC lifecycle): PASS
  • prepare integration artifacts: PASS
  • cargo test: PASS (required)
  • cargo test (axum native): PASS
  • cargo test (cross-adapter parity): PASS
  • cargo test (ts CLI, native): PASS
  • cargo check (cloudflare native + wasm32-unknown-unknown): PASS
  • cargo check/build/test (spin native + wasm32-wasip1): PASS
  • cargo fmt: PASS (required)
  • format-docs: PASS (required)
  • format-typescript: PASS (required)
  • vitest: PASS
  • CodeQL: PASS
  • Analyze (rust): PASS
  • Analyze (javascript-typescript): PASS
  • Analyze (actions): PASS

Comment thread docs/guide/ec-setup-guide.md Resolved
Comment thread trusted-server.example.toml Resolved
@ChristianPavilonis

ChristianPavilonis commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator Author

Review follow-up to the comments submitted against b5400020. The branch has since merged main at a5e9fe66; no findings-related source changes were made.

The rollout concern does not apply to the current deployment state. This is intentionally a hard cutover, and that deployment model is already documented. The secret-reference configuration and binary have already been deployed to production, so merging this branch to main does not create an old-binary/new-blob transition and will not cause deployment downtime.

We are not adding a transitional mode, plaintext fallback, migration orchestration, or key-length convention.

For the other body-level observations:

  • Fastly’s per-request state construction and secret resolution are known. No measured regression was provided, so no store-handle optimization is planned in this PR.
  • Cargo.toml and Cargo.lock pin the released EdgeZero v0.0.8 tag. No PR-description edit is planned.

The seven inline replies record the remaining no-change dispositions. All checks passed on the reviewed head; CI is rerunning after the merge from main.

Re-requesting review from @prk-Jr.

@ChristianPavilonis
ChristianPavilonis merged commit 125ed24 into main Sep 8, 2026
19 checks passed
prk-Jr added a commit that referenced this pull request Sep 9, 2026
Resolve 146 conflict hunks across 38 files. Most were the same work in two
shapes: rc had already merged the impl branches of #1016, #1103 and #1036
before those branches were force-pushed and took further review fixes, which
then reached main as squashes.

Notable resolutions:

- Upgrade edgezero from the pre-merge feature rev 055f7e94 to the released
  tag v0.0.8, which contains that branch as upstream #344 plus a Fastly CLI
  fix. Reword the now-stale "pinned feature branch" comments.
- Take main's reviewed refinements: the precomputed bid dimension index, the
  separate wait-delay queue in the platform test double, trusted_client_ip
  secret resolution, whitespace-only secret key rejection, and the removal of
  the lgtm annotations.
- Keep rc's later work: access telemetry, managed Prebid User ID modules, and
  request timing instrumentation.
- Keep rc's secret-reference documentation, which stays consistent with the
  surrounding text, and its example.com placeholders.

Fix three semantic conflicts that produced no markers: implement the new
EcKvStore::key_exists on rc's TimedKvStore wrapper, ungate the HashSet import
now used outside tests, and drop duplicate test-scope definitions that only
the target-matched clippy runs surface.
@aram356
aram356 deleted the edgezero-secrets branch September 10, 2026 19:55
aram356 added a commit that referenced this pull request Sep 10, 2026
Reconciles the EdgeZero store-registry migration with two months of main,
principally PR #1036 (native secret-store config resolution) and PR #1016
(configuration-driven auction providers).

Resolution highlights:
- edgezero moves from the PR #306 branch pin to main's v0.0.8 tag, which
  contains the State<T> + nested #[secret] work the pin waited on.
- Main's startup secret resolution is kept and made async end to end
  (settings_from_config_blob, resolve_secret_references, and
  get_settings_from_config_store now await PlatformSecretStore reads);
  the recursive path walker returns a LocalBoxFuture.
- get_settings_from_config_store reads the blob through the branch's
  EdgeZero ConfigStoreHandle and resolves secret references through the
  supplied secret store; the duplicated Fastly chunk resolver stays deleted
  (EdgeZero's config store reassembles chunks).
- Fastly boot resolves secrets through the registry-backed composite, so
  logical-to-physical mappings such as trusted_server_secrets -> ts_secrets
  apply at startup exactly as at request time.
- Axum boot keeps the branch's file-backed config store and resolves secret
  references via the EdgeZero EnvSecretStore (env vars named exactly after
  the secret key); integration-test env vars renamed accordingly.
- Cloudflare and Spin keep main's startup secret adapters, asyncified.
- Request-time secret reads deleted by #1036 (Tinybird, DataDome server-side
  key, S3 credentials, per-route secret_store selectors) stay deleted; the
  branch's async conversions of those paths are dropped with them.
- The Fastly-only consent wrapper stays replaced by the core fail-closed
  resolve_consent_kv guard, now also applied to page-bids after main's
  cross-site gate.
- Hooks::stores() returns the shared STORES_METADATA on all adapters,
  superseding main's Fastly-only literal.
- Rotation test double now reports absent keys as PlatformError::NotFound,
  matching the composite's not-found contract.

Verified: fmt; test-fastly (2675 core + 198 fastly), test-axum,
test-cloudflare, test-spin; parity 13/13; all six clippy targets; JS tests
(901) and format; docs format.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aram356 added a commit that referenced this pull request Sep 10, 2026
…remainder

Record that Phase 0 shipped in edgezero v0.0.8, Phase 1 landed in this PR,
and mainline #1036 delivered Phase 3's secret externalization at startup
(with the TrustedServerAppConfig wrapper and Redacted<T> retained as
accepted deviations). Amend D7 to allow provisioning-time
EDGEZERO__STORES__*__NAME mappings, mark D6 resolved as the write-only
management path, note the legacy Fastly path is already deleted, and
rescope the remaining work to full migration: the Cloudflare boot
config-store read, the from_toml_and_env deletion, the EC identity graph
through the registry, and the run_app/app!/extractor convergence gated on
P0-C and P0-D.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aram356 added a commit that referenced this pull request Sep 10, 2026
Four failures traced to two real causes plus two lint/docs issues:

- The v0.0.8 CLI capability matrix marks the axum, cloudflare, and spin
  adapters Single-capable for secrets, and `ts config push` runs that
  check across every adapter the manifest declares — so the four-id
  [stores.secrets] declaration failed every push (surfaced by the
  template-cache ESI harness). [stores.secrets] now declares only the
  default id; the management-provisioned `signing_keys` store binds
  registry-locally through the new RUNTIME_ONLY_SECRET_IDS const in the
  Fastly and Axum registry builders. The retired `ts_secrets` and
  `s3_auth` logical ids are dropped: since #1036 those credentials
  resolve at startup from the default store.
- The Fastly registry builders now bind each logical id to its
  runtime-env-mapped physical store name, so the boot-time secret
  resolution passes the logical default id and a
  trusted_server_secrets -> ts_secrets mapping resolves identically at
  boot and request time.
- Cloudflare's v0.0.8 dispatcher treats every declared [stores.kv] id
  as a required binding and failed each request with a 500 when only
  TRUSTED_SERVER_KV existed, which the integration harness reported as
  "runtime not ready". wrangler.toml and wrangler.ci.toml now bind all
  four logical KV ids by their lowercase logical names, matching the
  registry lookup, and cloudflare.toml follows. Verified locally: the
  wordpress, nextjs, and cookie-boundary Cloudflare integration tests
  pass under CI=1 with a locally built worker bundle.
- Clippy doc_markdown: backtick EdgeZero in the integration-test doc
  comment. VitePress: backtick two raw State<T> tokens that the Vue
  parser read as unclosed HTML elements; docs build is green locally.

The ESI harness passes 21/21 locally; fmt, all six clippy targets,
test-fastly/axum/cloudflare/spin, and parity are green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jwrosewell added a commit to jwrosewell/trusted-server that referenced this pull request Sep 14, 2026
Brings main at 066ea3c into split/1-ec-provider. Twelve files
conflicted and one more needed a change without a textual conflict.
Most resolutions keep both sides. Five needed a decision, recorded here
so that no behavior changes silently.

Edge Cookie generation, in ec/mod.rs. Main's IABTechLab#885 creates a row only
when no row holds the key, retries on a collision and binds the request
snapshot to the new row. This branch creates identifiers through the
selected provider. Both are kept. Each attempt asks the provider for a
candidate through the new EcContext::candidate_id, which runs the
reserved-header and identifier-bound checks, then creates the row with
create_if_absent under the provider's canonical key and binds the
snapshot to that key.

Orphan recovery, in ec/finalize.rs. Main's IABTechLab#885 rotates an orphaned
cookie by generating an HMAC identifier directly, which would give a
vendor-provider deployment a built-in identifier. Recovery now asks the
selected provider through candidate_id, keeps main's proof of absence
and retry limit, and does nothing when no provider is selected. Any
headers the provider asks for while creating the replacement are
applied to the response.

Withdrawal, in ec/finalize.rs. Main's IABTechLab#1113 hardening is kept whole,
being finalize_unusable_consent, the existence check inside
write_withdrawal_tombstone, the snapshot write-back and
log_tombstone_outcome. The tombstones are keyed by this branch's
withdrawal_kv_keys, the canonical keys of the cookie and the active
identifier, in place of withdrawal_ec_ids, and the write-back compares
against the active identifier's canonical key. EID ingestion on the
returning and generated paths uses main's collect-then-upsert form
under the canonical key.

Secret references, in config.rs. Main's IABTechLab#1036 resolves secret settings
from the secret store by the paths listed in secret_fields. This branch
makes the legacy ec.passphrase optional and adds the
[ec.providers.hmac] passphrase, which was not listed, so it would have
been read as a literal value. Both are now listed as optional paths and
checked as key references when the configuration is pushed, and the
integration fixture and the example configuration name the secret key
rather than a value.

Spin start-up, in the Spin adapter. Main's IABTechLab#1036 loads settings from
the config store with secret resolution, which fixes the same start-up
failure this branch's Spin commit fixed. Main's loader is taken and
SpinPlatformConfigStore, which nothing else uses, is removed.

The four adapters keep this branch's composition-root provider check,
and Axum, Cloudflare and Spin keep its error propagation from
build_ec_context, alongside main's compiled auction plan from IABTechLab#1016.

Tests. Main's collision-retry tests and its orphan-rotation tests built
their context with no provider selected, which main's code did not
need. They now select the built-in HMAC provider, as an HMAC deployment
does, because with no provider there is nothing to create or rotate. No
other test reaches identifier creation or orphan recovery without a
provider selected. Tests from each side were moved onto the other
side's interfaces, being this branch's six-parameter context helpers,
main's mutable context in ec_finalize_response, and the
[ec.providers.hmac] passphrase in main's two secret validation tests.
jwrosewell added a commit to jwrosewell/trusted-server that referenced this pull request Sep 14, 2026
Main's IABTechLab#1036 resolves secret settings from the secret store by the
paths TrustedServerAppConfig::secret_fields lists, and push validation
skips the validators on those paths because they hold key names.
Merging main registered the [ec.providers.hmac] passphrase, but not this
branch's [ec.providers.host-signals] passphrase. A key name there failed
the passphrase length check when the configuration was pushed, and a
key name long enough to pass would have been used unresolved as the
HMAC key.

The path is now listed as an optional secret and checked as a key name.
EdgeZero matches secret paths against validation error keys verbatim,
and the derived validation keyed the block's errors by the Rust field
name host_signals, so EcProviders now validates each built-in block
under the key the configuration uses. New tests cover pushing a
host-signals key name, resolving one from the mapped store, and still
rejecting a short resolved value.
jwrosewell added a commit to jwrosewell/trusted-server that referenced this pull request Sep 14, 2026
Brings upd/split/4-client-resolve at 69e7d57, which carries main at
066ea3c through split/1 to split/4, into split/5-response-hook-docs.
Five files conflicted, all in the documentation set and the example
configuration, and all for one reason. This branch documents split/1's
move of the Edge Cookie passphrase under [ec.providers.hmac] behind
provider = "hmac", still as a plaintext value, while main's IABTechLab#1036 made
the passphrase the name of a secret-store key. Every resolution keeps
this branch's layout with main's key-name model.

In configuration.md, ec-setup-guide.md, error-reference.md and
fastly.md, the TOML examples select provider = "hmac" with
passphrase = "ec_passphrase" under [ec.providers.hmac], and the
environment overrides use TRUSTED_SERVER__EC__PROVIDERS__HMAC__PASSPHRASE
with the key name. Three examples merged cleanly with the plaintext
placeholder still under [ec.providers.hmac] and take the key name too.
The [ec.providers.hmac] reference table gains main's note that the
passphrase is a key name, and its passphrase row says so. The security,
staging and placeholder bullets are main's, because this branch's
bullets described plaintext values in environment variables. The
validation bullets keep this branch's provider bullet and take main's
three, naming providers.hmac.passphrase.

trusted-server.example.toml keeps this branch's host-signal provider
block with a key-name placeholder, because that passphrase is also
resolved from the secret store (a75571d on split/2), and takes main's
partner comment.
jwrosewell added a commit to jwrosewell/trusted-server that referenced this pull request Sep 15, 2026
…split/5

Brings upd/split/5-response-hook-docs at 672cc60, which carries main at
066ea3c and the permission signal work through split/1 to split/5, into
split/7-integration-seam-impl. CLAUDE.md and 22 files conflicted.

The auction follows main. Main's IABTechLab#1016 replaced the auction provider
table this branch had opened with a compiled auction plan, which the
orchestrator and the integration registry share. This branch no longer
changes the auction, so AuctionProviderBuilder, its two function types,
build_orchestrator_with_providers, the public
AuctionOrchestrator::register_provider, the seam probe's auction provider
and the test that composed it are gone. compile_auction_plan,
build_orchestrator_with_plan, AuctionOrchestrator::from_plan and the
test-only register_provider stand as main has them, and the auction
module, its README and the auction guide take main's text. The
generalized BidRenderer stays, with the APS renderer types in
integrations/aps.rs and an unchanged serialized form.

- The registry. with_plan gains with_plan_and_registrations(settings,
  plan, extra). Prebid and APS register from the plan first, as on main,
  and then the builders run with the duplicate id and preparer handling.
  The Prebid and APS ids are recorded with the builders, so an outside
  builder claiming either is still refused naming both sources, as it
  was when they were builders. new and with_registrations are test-only,
  as new is on main.
- BUILT_IN_BUILDERS holds main's twelve integrations in main's order,
  with js_asset_proxy first, through IntegrationBuilder::new. aps and
  prebid leave it, because main makes them plan providers.
- Validation follows main's validate_settings_for_deploy and
  validate_settings_for_runtime, with the builder loop in place of main's
  list of named integrations. Deploy validation leaves placeholder secret
  values to runtime validation, as main does since IABTechLab#1036, because secret
  fields hold key names at deploy time. Prebid, APS and adserver_mock are
  validated by name, as on main, and the new
  validation_reaches_every_plan_backed_integration plants a bad block for
  each, at deploy and at runtime. It failed on adserver_mock before that
  check was restored. js_asset_proxy validates through its builder and
  still checks a disabled block. DataDome's deploy validator had the same
  body as its startup validator and no caller left, so it goes, and so
  does adserver_mock::validate.
- Identity. This branch's asynchronous EdgeCookieProvider::generate,
  which takes RuntimeServices, meets main's orphaned identifier recovery
  in finalize. ec_finalize_response and the two recovery functions are
  asynchronous and take the services, and the Fastly entry point blocks
  on the finalize as it does on its other asynchronous steps. The geo
  lookup main added in Didomi is asynchronous and takes the services.
- The adapters. Axum, Cloudflare, Fastly and Spin compile the plan, check
  it for their target, build the orchestrator from it, and build the
  registry with their registrations over the same plan, so the Edge
  Cookie provider a module supplies still comes from the registry.
- CLAUDE.md is a symlink to AGENTS.md again, where this branch had
  committed the plain file a Windows checkout writes.
- The integration guide no longer describes an auction provider builder.

Tests. The merged tree passes cargo fmt, Clippy with warnings denied on
the Fastly, Axum, Cloudflare and Spin adapters on their native and wasm
targets and on the CLI, codegen and parity crates, and the release wasm
builds for Fastly and Spin. Core passes 2,937 tests natively and 2,930
under Viceroy with 7 ignored, and 18 doctests. The Fastly adapter passes
192 tests under Viceroy, Axum 72 including 9 seam probe round trips,
Cloudflare 47, Spin 89, the seam probe crate 8 and a doctest, the parity
suite 13, the CLI 87 on Linux and the codegen crate 7, and the benchmark
smoke run passes.
jevansnyc added a commit that referenced this pull request Oct 5, 2026
Resolve eleven conflict hunks across six files, plus two semantic
conflicts the merge did not mark.

Import and structural hunks are unions of both sides: the Fastly adapter
keeps TimedKvStore, RouteClass/RouteMetadata and response_builder
alongside main's StoreName, RuntimeStoreConfig and auction-plan symbols;
the integration registry keeps main's enabled_integration_ids loop with
the branch's route-path element added to the router value; the config
template keeps both the new [observability] leaf and main's auction
provider examples. main.rs keeps the AppBuild timing span wrapping
main's runtime-store-based config open and app build.

Port access telemetry onto main's resolved-secret model (#1036).
tinybird.access_token_secret becomes Option<Redacted<String>>, resolved
through config.rs as an optional secret reference like the auction
token, so the Fastly access emitter reads it from settings instead of
opening a Secret Store per emission; load_access_token and
validate_secret_store_key_name are gone. Secret-reference validation now
gates the auction token on auction_enabled so an access-only
configuration is expressible. Drop main's guard rejecting
access_enabled, which this branch wires the emitter for.

Forward EcKvStore::key_exists through TimedKvStore, added to the trait
by main's Edge Cookie withdrawal hardening, and restore
validate_tinybird_secret for the resolved-value checks.
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.

Add secret-store backed config references for secret values

3 participants