Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
00de4e9
feat: add native secret-store config resolution
ChristianPavilonis Aug 17, 2026
0fdfcbe
Fix platform secret-store startup configuration
ChristianPavilonis Aug 18, 2026
2783e00
Resolve static credentials through typed config
ChristianPavilonis Aug 24, 2026
3dc7370
Update EdgeZero static-secret support revision
ChristianPavilonis Aug 24, 2026
0db2e11
Align EdgeZero deployment mapping revision
ChristianPavilonis Aug 24, 2026
3e2b3d2
Fix secret reference validation and guidance
ChristianPavilonis Aug 25, 2026
b47ced8
Address secret configuration review findings
ChristianPavilonis Aug 26, 2026
58730d7
Make Edge Cookie partner API tokens optional
ChristianPavilonis Aug 26, 2026
1b17ce0
Merge branch 'main' into edgezero-secrets
aram356 Aug 27, 2026
76f6f13
Address remaining secret configuration review feedback
ChristianPavilonis Aug 28, 2026
44e9eaf
feat: add native secret-store config resolution
ChristianPavilonis Aug 17, 2026
3f6e29d
Fix platform secret-store startup configuration
ChristianPavilonis Aug 18, 2026
d71c660
Resolve static credentials through typed config
ChristianPavilonis Aug 24, 2026
6c35ce9
Update EdgeZero static-secret support revision
ChristianPavilonis Aug 24, 2026
43c7577
Align EdgeZero deployment mapping revision
ChristianPavilonis Aug 24, 2026
edfbcb0
Fix secret reference validation and guidance
ChristianPavilonis Aug 25, 2026
22176da
Address secret configuration review findings
ChristianPavilonis Aug 26, 2026
3b2c64c
Make Edge Cookie partner API tokens optional
ChristianPavilonis Aug 26, 2026
c7c382a
Address remaining secret configuration review feedback
ChristianPavilonis Aug 28, 2026
55f0a23
Merge commit 'refs/pull/1036/head' of github.com:IABTechLab/trusted-s…
ChristianPavilonis Aug 31, 2026
e45ff8e
Correct proxy secret length guidance
ChristianPavilonis Aug 31, 2026
9831ee4
Correct proxy secret length guidance
ChristianPavilonis Aug 31, 2026
3c48d39
Merge commit 'refs/pull/1036/head' of github.com:IABTechLab/trusted-s…
ChristianPavilonis Sep 1, 2026
71df1aa
Merge branch 'main' of github.com:IABTechLab/trusted-server into edge…
ChristianPavilonis Sep 1, 2026
4258a6b
Fix secret-store resolution and deployment flows
ChristianPavilonis Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.dev
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
# Non-secret development overlays used while generating the Axum config blob.
# Sourcing this file alone does not configure the Axum server: also export the
# blob and referenced secret-store values as shown in docs/guide/getting-started.md.

# [publisher]
TRUSTED_SERVER__PUBLISHER__DOMAIN=localhost
TRUSTED_SERVER__PUBLISHER__COOKIE_DOMAIN=localhost
TRUSTED_SERVER__PUBLISHER__ORIGIN_URL=http://localhost:9090
Comment thread
ChristianPavilonis marked this conversation as resolved.

# [synthetic]
Expand Down
16 changes: 11 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
# Trusted Server Environment Variables
# Copy this file to .env.dev, .env.staging, or .env.production and fill in values
# See docs/guide/configuration.md for details
# Trusted Server development environment variables
# Copy this file to .env.dev, .env.staging, or .env.production and fill in
# non-secret values. App-config secrets are key names in the pushed blob and
# their values belong in the platform secret store; see the configuration guide.
# For Axum runtime loading, export the config blob as:
# TRUSTED_SERVER_CONFIG_TRUSTED_SERVER_CONFIG_TRUSTED_SERVER_CONFIG=<blob-envelope-json>
# and export one secret per key name as:
# TRUSTED_SERVER_SECRET_TRUSTED_SERVER_SECRETS_<KEY_NAME>=<secret-value>
# The commented examples below are CLI overlays for ordinary fields only.
Comment thread
ChristianPavilonis marked this conversation as resolved.
# Fastly example: map logical app-config secrets to physical `ts_secrets`.
# EDGEZERO__STORES__SECRETS__TRUSTED_SERVER_SECRETS__NAME=ts_secrets

# =============================================================================
# Publisher Settings
# =============================================================================
TRUSTED_SERVER__PUBLISHER__DOMAIN=publisher.com
TRUSTED_SERVER__PUBLISHER__COOKIE_DOMAIN=.publisher.com
TRUSTED_SERVER__PUBLISHER__ORIGIN_URL=https://origin.publisher.com
TRUSTED_SERVER__PUBLISHER__PROXY_SECRET=<your-proxy-secret>

# =============================================================================
# Synthetic ID Settings
# =============================================================================
TRUSTED_SERVER__SYNTHETIC__COUNTER_STORE=counter_store
TRUSTED_SERVER__SYNTHETIC__OPID_STORE=opid_store
TRUSTED_SERVER__SYNTHETIC__SECRET_KEY=<your-synthetic-secret>
# Template variables: client_ip, user_agent, first_party_id, auth_user_id, publisher_domain, accept_language
TRUSTED_SERVER__SYNTHETIC__TEMPLATE={{ client_ip }}:{{ user_agent }}:{{ first_party_id }}

Expand Down
57 changes: 41 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ criterion = { version = "0.5", default-features = false, features = ["cargo_benc
derive_more = { version = "2.0", features = ["display", "error"] }
directories = "5"
ed25519-dalek = { version = "2.2", features = ["rand_core"] }
edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.4", default-features = false }
edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.4", default-features = false }
edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.4", default-features = false }
edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.4", default-features = false }
edgezero-cli = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.4" }
edgezero-core = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.4", default-features = false }
edgezero-adapter-axum = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.8", default-features = false }
edgezero-adapter-cloudflare = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.8", default-features = false }
edgezero-adapter-fastly = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.8", default-features = false }
edgezero-adapter-spin = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.8", default-features = false }
edgezero-cli = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.8" }
edgezero-core = { git = "https://github.com/stackpop/edgezero", tag = "v0.0.8", default-features = false }
env_logger = "0.11"
error-stack = "0.6"
esi = "0.7.2"
Expand Down
11 changes: 8 additions & 3 deletions crates/trusted-server-adapter-axum/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use trusted_server_core::settings_data::{
use trusted_server_core::platform::RuntimeServices;

use crate::middleware::{AuthMiddleware, FinalizeResponseMiddleware, SanitizeRequestMiddleware};
use crate::platform::{AxumPlatformConfigStore, build_runtime_services};
use crate::platform::{AxumPlatformConfigStore, AxumPlatformSecretStore, build_runtime_services};

// ---------------------------------------------------------------------------
// AppState
Expand All @@ -60,8 +60,13 @@ pub struct AppState {
fn build_state() -> Result<Arc<AppState>, Report<TrustedServerError>> {
let store_name = default_config_store_name();
let config_key = default_config_key();
let settings =
get_settings_from_config_store(&AxumPlatformConfigStore, &store_name, &config_key)?;
let settings = get_settings_from_config_store(
&AxumPlatformConfigStore,
&AxumPlatformSecretStore,
&store_name,
&config_key,
&trusted_server_core::settings_data::default_secret_store_name(),
Comment thread
ChristianPavilonis marked this conversation as resolved.
)?;
build_state_with_settings(settings)
}

Expand Down
41 changes: 34 additions & 7 deletions crates/trusted-server-adapter-cloudflare/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use trusted_server_core::auction::endpoints::handle_auction;
use trusted_server_core::auction::{AuctionOrchestrator, build_orchestrator};
use trusted_server_core::cache_policy::EdgeCacheHeader;
#[cfg(target_arch = "wasm32")]
use trusted_server_core::config_payload::settings_from_config_blob;
use trusted_server_core::config_payload::{DEFAULT_SECRET_STORE_ID, settings_from_config_blob};
use trusted_server_core::ec::EcContext;
use trusted_server_core::ec::admin::{
admin_ec_lookup_not_supported as core_admin_ec_lookup_not_supported,
Expand All @@ -22,6 +22,8 @@ use trusted_server_core::ec::registry::PartnerRegistry;
use trusted_server_core::error::{IntoHttpResponse as _, TrustedServerError};
use trusted_server_core::integrations::{IntegrationRegistry, ProxyDispatchInput};
use trusted_server_core::platform::RuntimeServices;
#[cfg(target_arch = "wasm32")]
use trusted_server_core::platform::StoreName;
use trusted_server_core::proxy::{
handle_first_party_click, handle_first_party_proxy, handle_first_party_proxy_rebuild,
handle_first_party_proxy_sign,
Expand All @@ -44,11 +46,23 @@ use crate::platform::build_runtime_services;
// ---------------------------------------------------------------------------

#[cfg(target_arch = "wasm32")]
static CLOUDFLARE_CONFIG_JSON: std::sync::OnceLock<String> = std::sync::OnceLock::new();
thread_local! {
static CLOUDFLARE_CONFIG_JSON: std::cell::OnceCell<String> = const { std::cell::OnceCell::new() };
static CLOUDFLARE_ENV: std::cell::OnceCell<worker::Env> = const { std::cell::OnceCell::new() };
Comment thread
ChristianPavilonis marked this conversation as resolved.
}

#[cfg(target_arch = "wasm32")]
pub fn set_cloudflare_config_json(value: String) {
let _ = CLOUDFLARE_CONFIG_JSON.set(value);
CLOUDFLARE_CONFIG_JSON.with(|slot| {
let _ = slot.set(value);
});
}

#[cfg(target_arch = "wasm32")]
pub fn set_cloudflare_env(env: worker::Env) {

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.

🤔 thinkingOnceCell::set ignores every call after the first, so this pins the first request's worker::Env for the isolate's lifetime. lib.rs calls set_cloudflare_env(env.clone()) on every request, but from request two onward that clone is dropped and settings_from_cloudflare_config_json resolves secrets through the original handle.

routes() runs build_state() per request, so this stale handle is used on every startup, not just once. Holding a JsValue across the request boundary works in practice because the isolate persists, but it is not a documented guarantee, and if the runtime ever hands over a different bindings object you would read from the wrong environment. The config JSON has the same shape but is static data, so it is not exposed the same way.

Storing the current env each request would remove the question entirely:

thread_local! {
    static CLOUDFLARE_ENV: std::cell::RefCell<Option<worker::Env>> =
        const { std::cell::RefCell::new(None) };
}

#[cfg(target_arch = "wasm32")]
pub fn set_cloudflare_env(env: worker::Env) {
    CLOUDFLARE_ENV.with(|slot| {
        slot.replace(Some(env));
    });
}

with the reader in settings_from_cloudflare_config_json becoming slot.borrow().clone(). Was the first-wins behaviour deliberate here?

CLOUDFLARE_ENV.with(|slot| {
let _ = slot.set(env);
});
}

/// Application state built once at startup and shared across all requests.
Expand Down Expand Up @@ -76,18 +90,22 @@ fn load_startup_settings() -> Result<Settings, Report<TrustedServerError>> {

#[cfg(not(target_arch = "wasm32"))]
fn load_startup_settings() -> Result<Settings, Report<TrustedServerError>> {
Settings::from_toml(include_str!("../../../trusted-server.example.toml"))
Err(Report::new(TrustedServerError::Configuration {
message: "Cloudflare startup settings require a Worker config binding".to_string(),
})
.attach("use TrustedServerApp::routes_with_settings for host tests"))
}

#[cfg(target_arch = "wasm32")]
fn settings_from_cloudflare_config_json() -> Result<Settings, Report<TrustedServerError>> {
let raw_config = CLOUDFLARE_CONFIG_JSON.get().ok_or_else(|| {
let raw_config = CLOUDFLARE_CONFIG_JSON.with(|slot| slot.get().cloned());
let raw_config = raw_config.ok_or_else(|| {
Report::new(TrustedServerError::Configuration {
message: "Cloudflare TRUSTED_SERVER_CONFIG is required".to_string(),
})
.attach("set TRUSTED_SERVER_CONFIG to JSON containing the app_config blob envelope")
})?;
let value: serde_json::Value = serde_json::from_str(raw_config).map_err(|error| {
let value: serde_json::Value = serde_json::from_str(&raw_config).map_err(|error| {
Report::new(TrustedServerError::Configuration {
message: "invalid Cloudflare TRUSTED_SERVER_CONFIG JSON".to_string(),
})
Expand All @@ -101,7 +119,16 @@ fn settings_from_cloudflare_config_json() -> Result<Settings, Report<TrustedServ
message: "Cloudflare TRUSTED_SERVER_CONFIG missing app_config".to_string(),
})
})?;
settings_from_config_blob(envelope)
let env = CLOUDFLARE_ENV
.with(|slot| slot.get().cloned())
.ok_or_else(|| {
Report::new(TrustedServerError::Configuration {
message: "Cloudflare Worker environment is unavailable during startup".to_string(),
})
})?;
let secret_store = crate::platform::CloudflareSecretStoreAdapter { env };
let default_secret_store = StoreName::from(DEFAULT_SECRET_STORE_ID);
Comment thread
ChristianPavilonis marked this conversation as resolved.
settings_from_config_blob(envelope, &secret_store, &default_secret_store)
}

/// Build the application state from explicit settings.
Expand Down
1 change: 1 addition & 0 deletions crates/trusted-server-adapter-cloudflare/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub async fn main(req: Request, env: Env, ctx: Context) -> Result<Response> {
if let Ok(config) = env.var("TRUSTED_SERVER_CONFIG") {
app::set_cloudflare_config_json(config.to_string());
}
app::set_cloudflare_env(env.clone());

match edgezero_adapter_cloudflare::run_app::<app::TrustedServerApp>(req, env, ctx).await {
Ok(resp) => Ok(resp),
Expand Down
4 changes: 2 additions & 2 deletions crates/trusted-server-adapter-cloudflare/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -547,8 +547,8 @@ impl PlatformHttpClient for CloudflareHttpClient {
/// Bridges [`worker::Env`] secrets to [`PlatformSecretStore`] by calling
/// `env.secret(key)` synchronously. Writes and deletes return errors.
#[cfg(target_arch = "wasm32")]
struct CloudflareSecretStoreAdapter {
env: worker::Env,
pub(crate) struct CloudflareSecretStoreAdapter {
pub(crate) env: worker::Env,
}

#[cfg(target_arch = "wasm32")]
Expand Down
9 changes: 9 additions & 0 deletions crates/trusted-server-adapter-cloudflare/wrangler.ci.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,12 @@ id = "ci-local-kv"
# Placeholder replaced by the integration test harness with a JSON object that
# contains the runtime Trusted Server app-config blob envelope.
TRUSTED_SERVER_CONFIG = "{}"

# Fictitious integration-only secret values. `worker::Env::secret` reads these
# string bindings in local Wrangler runs; production values are provisioned with
# `wrangler secret put` instead of being committed to a manifest.
integration_admin_password = "integration-admin-password-32-bytes-ok"
integration_proxy_secret = "integration-test-proxy-secret-32-bytes-ok"
integration_ec_passphrase = "integration-test-ec-secret-padded-32"
integration_partner_token_alpha = "integration-test-token-alpha-32-bytes-ok"
integration_partner_token_bravo = "integration-test-token-bravo-32-bytes-ok"
Loading