Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
6fbbd4a
Specify context-derived secret bindings
ChristianPavilonis Oct 16, 2026
3069d30
Specify label-free secret provisioning and implementation plan
ChristianPavilonis Oct 26, 2026
eea0867
Bind implicit credentials to approved contexts and provision securely
ChristianPavilonis Oct 26, 2026
6b2a235
Address binding review findings and verify local native startup
ChristianPavilonis Oct 27, 2026
4fb602f
Validate Spin manifests and refresh fixture generators
ChristianPavilonis Oct 27, 2026
67c5bee
Align shared dependencies with the secret-provisioning EdgeZero revision
ChristianPavilonis Oct 27, 2026
e6de377
Merge main into feature/1037-secret-bindings
ChristianPavilonis Oct 28, 2026
3c0ac2a
Separate credential-binding metadata from error paths to eliminate fi…
ChristianPavilonis Oct 28, 2026
535ed17
Merge branch 'main' into feature/1037-secret-bindings
aram356 Oct 29, 2026
4e9a10f
Verify native credential-binding security and separate rollout gates
ChristianPavilonis Oct 29, 2026
1b49fbc
Fix Axum credential setup and avoid logging loaded fixture state
ChristianPavilonis Oct 29, 2026
8bb2782
Merge branch 'main' into feature/1037-secret-bindings
ChristianPavilonis Oct 29, 2026
742c5ba
Merge main into feature/1037-secret-bindings
ChristianPavilonis Nov 2, 2026
5c8b11b
Wait for HTTP/1 connection readiness in proxy integration tests
ChristianPavilonis Nov 2, 2026
8ce273d
Merge main into feature/1037-secret-bindings
ChristianPavilonis Nov 2, 2026
c278e6a
Address approved secret-binding review feedback
ChristianPavilonis Nov 2, 2026
9b4312d
Remove sensitive identifiers from CodeQL-flagged test diagnostics
ChristianPavilonis Nov 2, 2026
96fdb10
Make the unsupported-fanout warning static to address the CodeQL aler…
ChristianPavilonis Nov 2, 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
14 changes: 7 additions & 7 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# 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.
# non-secret values. App config contains no credential values or key names.
# Provision generated native bindings separately; see docs/guide/secrets.md.
# 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>
# Use `ts secrets plan --adapter axum` for exact generated environment names.
# Inject their raw values through the launcher, not a tracked environment file.
# The commented examples below are CLI overlays for ordinary fields only.
# 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__DOMAIN=example.com
TRUSTED_SERVER__PUBLISHER__COOKIE_DOMAIN=.example.com
TRUSTED_SERVER__PUBLISHER__ORIGIN_URL=https://origin.example.com

# =============================================================================
# Synthetic ID Settings
Expand Down
58 changes: 53 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: "Run Tests"
name: 'Run Tests'

permissions:
contents: read
Expand Down Expand Up @@ -116,6 +116,13 @@ jobs:
TMPDIR: ${{ runner.temp }}
run: ./scripts/async-html-finalization-local-test.sh fastly --release --auction

- name: Verify native credential TLS, redirects, and config-only attacks
timeout-minutes: 10
env:
CARGO_TARGET_DIR: ${{ github.workspace }}/target
TMPDIR: ${{ runner.temp }}
run: ./scripts/secret-bindings-runtime.sh fastly

- name: Upload EOF runtime diagnostics on failure
if: failure()
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -165,6 +172,9 @@ jobs:
- name: Run Axum adapter tests
run: cargo test-axum

- name: Verify native security harness process cleanup
run: node --test scripts/secret-bindings-runtime.test.mjs

- name: Verify finalization harness process cleanup
run: cargo test --manifest-path crates/trusted-server-integration-tests/Cargo.toml --target x86_64-unknown-linux-gnu --features html-eof-runtime --test html_eof_runtime

Expand Down Expand Up @@ -239,6 +249,24 @@ jobs:
- name: Run Cloudflare adapter tests (native host)
run: cargo test-cloudflare

- name: Install pinned local Workers tools
shell: bash
run: |
WORKER_VERSION=$(awk '/^name = "worker"$/ { getline; gsub(/"/, "", $3); print $3; exit }' Cargo.lock)
WRANGLER_VERSION=$(awk '$1 == "wrangler" { print $2 }' .tool-versions)
cargo install worker-build --version "$WORKER_VERSION" --locked
npm install --prefix "$RUNNER_TEMP/secret-bindings-tools" --no-save "wrangler@$WRANGLER_VERSION"

# No shipping S3 route on this adapter. A test-utils integration drives
# the shared exporter through real workerd stores and transport.
- name: Verify native credential TLS, redirects, and config-only attacks
timeout-minutes: 10
env:
CARGO_TARGET_DIR: ${{ github.workspace }}/target
TMPDIR: ${{ runner.temp }}
WRANGLER_BIN: ${{ runner.temp }}/secret-bindings-tools/node_modules/.bin/wrangler
run: ./scripts/secret-bindings-runtime.sh cloudflare

test-spin:
name: cargo check/build/test (spin native + wasm32-wasip1)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -279,12 +307,32 @@ jobs:
TRUSTED_SERVER__PUBLISHER__ORIGIN_URL: http://127.0.0.1:8080
TRUSTED_SERVER__PUBLISHER__PROXY_SECRET: integration-test-proxy-secret
TRUSTED_SERVER__EC__PASSPHRASE: integration-test-ec-secret-padded-32
TRUSTED_SERVER__PROXY__CERTIFICATE_CHECK: "false"
TRUSTED_SERVER__PROXY__CERTIFICATE_CHECK: 'false'
run: cargo build --package trusted-server-adapter-spin --target wasm32-wasip1 --features spin --release

- name: Run Spin adapter tests (native host)
run: cargo test-spin

- name: Install pinned Spin runtime
shell: bash
run: |
VERSION=$(awk '$1 == "spin" { print $2 }' .tool-versions)
mkdir -p "$RUNNER_TEMP/secret-bindings-tools"
curl --fail --silent --show-error --location \
"https://github.com/spinframework/spin/releases/download/v$VERSION/spin-v$VERSION-linux-amd64.tar.gz" \
--output "$RUNNER_TEMP/spin.tar.gz"
tar -xzf "$RUNNER_TEMP/spin.tar.gz" -C "$RUNNER_TEMP/secret-bindings-tools" spin

# No shipping S3 route on this adapter. A test-utils integration drives
# the shared exporter through real Spin stores and transport.
- name: Verify native credential TLS, redirects, and config-only attacks
timeout-minutes: 10
env:
CARGO_TARGET_DIR: ${{ github.workspace }}/target
TMPDIR: ${{ runner.temp }}
SPIN_BIN: ${{ runner.temp }}/secret-bindings-tools/spin
run: ./scripts/secret-bindings-runtime.sh spin

test-parity:
name: cargo test (cross-adapter parity)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -328,7 +376,7 @@ jobs:
- name: Set up Rust toolchain
uses: ./.github/actions/setup-rust
with:
components: "clippy, rustfmt"
components: 'clippy, rustfmt'
cache-key: cargo-cli

- name: Read pinned Node.js version
Expand Down Expand Up @@ -408,7 +456,7 @@ jobs:
# main and this job only runs on PRs; `save-cache` keeps it
# restore-only if either changes.
cache-key: cargo-cli
save-cache: "false"
save-cache: 'false'

# Building `ts` compiles trusted-server-core, whose trusted-server-js
# build script runs the TSJS build with the `npm` on PATH, so pin Node
Expand Down Expand Up @@ -444,7 +492,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ steps.node-version.outputs.node-version }}
cache: "npm"
cache: 'npm'
cache-dependency-path: crates/trusted-server-js/lib/package.json

- name: Install dependencies
Expand Down
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
fastly 16.0.0
rust 1.98.1
nodejs 24.20.0
wrangler 4.83.0
viceroy 0.21.0
wasmtime 48.0.1
spin 4.1.0
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- **Breaking:** App credentials use context-derived native bindings, not operator-selected secret references. Remove all thirteen credential properties, including on disabled features, and provision raw values through reviewed `ts secrets plan` output. Partner inbound auth uses boolean `api_token_enabled`; optional S3 session credentials use boolean `session_token_enabled`, both default false. Enabled credentials and compiled destinations load as one snapshot. First-release direct `ts secrets set` supports Fastly only; all four adapters have native rendering. Cloudflare/Spin presence checks remain unsupported and return exit 3, distinct from missing bindings at exit 1 and operational errors at exit 2. Provisioning commands now run the same source validation as config push. `set --stdin` requires piped input and rejects trailing CR/LF without trimming. Endpoint grammar is stricter even on disabled features: pull URLs cannot contain queries or trailing-dot/IP hosts, allowlists may name only the endpoint host, and authenticated exports always verify certificates. Raw native credentials are no longer trimmed; unrecoverable write-only credentials require rotation with the continuity consequences described in the migration guide. A short enabled DataDome test-bypass credential now fails loading instead of disabling the bypass. Signing reads reject reserved `ts1_` names and their native aliases; migrate legacy signing keys in that namespace before upgrading. See the provisioning, protected CI, and migration guides. New keys do not revoke retired contexts, warm states need recreation, and an old-binary rollback restores the original attack. Native/provider/IAM and migration rehearsals remain release gates; this entry is not a completed security-release claim.

- The Lockr browser module no longer polls for private SDK state or mutates its API host, removing the "Lockr SDK not detected" timeout warning (#1247). Root-relative Lockr XHR API requests and rewritten SDK URLs now stay on the page origin even with an external `<base href>`. Custom or unreplaced legacy SDKs that relied on the removed host rewrite must route their own API calls through `/integrations/lockr/api`; `rewrite_sdk = false` leaves static SDK tags unchanged but does not disable dynamic interception. See the [Lockr migration guide](docs/guide/integrations/lockr.md#migration-from-the-sdk-host-shim).
- Auction response collection now bounds network waiting by `[auction].timeout_ms`
measured from dispatch, including delayed headers and trickling bodies on all
Expand Down
20 changes: 12 additions & 8 deletions Cargo.lock

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

13 changes: 7 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,13 @@ cssparser = "0.36"
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/IABTechLab/edgezero", tag = "v0.0.11", default-features = false }
edgezero-adapter-cloudflare = { git = "https://github.com/IABTechLab/edgezero", tag = "v0.0.11", default-features = false }
edgezero-adapter-fastly = { git = "https://github.com/IABTechLab/edgezero", tag = "v0.0.11", default-features = false }
edgezero-adapter-spin = { git = "https://github.com/IABTechLab/edgezero", tag = "v0.0.11", default-features = false }
edgezero-cli = { git = "https://github.com/IABTechLab/edgezero", tag = "v0.0.11" }
edgezero-core = { git = "https://github.com/IABTechLab/edgezero", tag = "v0.0.11", default-features = false }
# The implicit-secret companion includes the ingress/pre-dispatch APIs from v0.0.11.
edgezero-adapter-axum = { git = "https://github.com/IABTechLab/edgezero", rev = "c199355e5893fe58e050ed7bae8c5f8462e2f511", default-features = false }

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.

🔧 wrench: At c199355e, ts secrets plan, check and set --adapter fastly cannot resolve resources against the real Fastly API.

fastly_secrets::resolve_resources requests the inventory for every store in edgezero.toml. Our manifest declares [stores.config], so it always fetches GET /resources/stores/config?limit=100 and parses the reply as a paginated {"data":[…],"meta":{…}} page. Fastly returns the config-store list as a bare JSON array. EdgeZero's own deploy path at this same revision already parses it that way (edgezero-adapter-fastly/src/cli.rs:801, parse_store_inventory(ResourceKind::Config, …)). The companion PR fixed this after the pinned commit, in IABTechLab/edgezero#444 commit c24cd9940 ("Fix Fastly secret resource resolution and native name validation").

The migration guide documents Fastly as the only adapter with direct ts secrets set. At this head, every Fastly plan, check and set stops with unable to resolve complete Fastly Config Store inventory, so operators cannot provision before cutover. CI stays green because FakeTransport in crates/trusted-server-cli/src/commands/secrets/tests.rs (lines 881-882) replies to /resources/stores/config? with the same paginated shape the pinned parser expects.

Reproduction: in a scratch copy of 96fdb107d, I changed only that fake to reply with [{"id":"fictional-config-store","name":"trusted_server_config"}]. secrets_cli_plan_and_stdin_create_replace_use_actual_shared_transport then fails with "unable to resolve complete Fastly Config Store inventory". I did not call the real Fastly API.

Proposed fix (apply manually; it spans Cargo.toml, Cargo.lock and tests.rs): this folds into the release-pin gate you already have. The released EdgeZero version must contain c24cd9940. When you repin, change the fake to answer GET /resources/stores/config with a bare array, so the test matches Fastly's real shape. #1479 also still records the older pin 4f645b1e.

edgezero-adapter-cloudflare = { git = "https://github.com/IABTechLab/edgezero", rev = "c199355e5893fe58e050ed7bae8c5f8462e2f511", default-features = false }
edgezero-adapter-fastly = { git = "https://github.com/IABTechLab/edgezero", rev = "c199355e5893fe58e050ed7bae8c5f8462e2f511", default-features = false }
edgezero-adapter-spin = { git = "https://github.com/IABTechLab/edgezero", rev = "c199355e5893fe58e050ed7bae8c5f8462e2f511", default-features = false }
edgezero-cli = { git = "https://github.com/IABTechLab/edgezero", rev = "c199355e5893fe58e050ed7bae8c5f8462e2f511" }
edgezero-core = { git = "https://github.com/IABTechLab/edgezero", rev = "c199355e5893fe58e050ed7bae8c5f8462e2f511", default-features = false }
env_logger = "0.11"
error-stack = "0.6"
esi = "0.7.2"
Expand Down
83 changes: 83 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,88 @@
# Testing auction orchestration

## Secret-binding release evidence

App config must contain no removed credential properties. Provision fictional
generated bindings from the same compiler/renderer as runtime; direct resolved
`Settings` unit fixtures are internal, not public TOML examples. See
[provisioning](docs/guide/secrets.md), [CI](docs/guide/secrets-ci.md), and
[migration](docs/guide/secret-binding-migration.md).

### Issue regression coverage

The config-only attacker model assumes the attacker cannot write native secrets,
change deployment bindings, or deploy code. Provisioning a generated binding is
an authorization decision made outside the mutable config blob. A checksum alone
never grants that authority.

Run the production-envelope-to-pull regressions and actual Axum HTTPS transport
checks separately:

```bash
cargo test -p trusted-server-core --lib secret_scoping_
cargo test -p trusted-server-adapter-axum --lib platform::https_tests
```

The pull regression has a successful dispatch control, then recomputes valid
checksums after changing destinations or injecting another role's generated key.
It asserts no original pull credential read, backend creation, or HTTP send.
Rotation tests distinguish retained snapshots from reconstructed state; retiring
a binding blocks replay on fresh state, not use already captured by old state.
The Axum tests use the production client policy with a test-only CA and loopback
DNS. They exercise synchronous and pending sends, trusted HTTPS, untrusted issuer,
wrong hostname, and all five redirect statuses to same-origin and other-origin
sinks. No production TLS bypass or trust configuration is added.

Run native WASM checks against isolated local TLS fixtures:

```bash
./scripts/secret-bindings-runtime.sh fastly
./scripts/secret-bindings-runtime.sh cloudflare
./scripts/secret-bindings-runtime.sh spin
```

The harness compiles fictional native bindings through the production compiler
and loads valid-checksum envelopes through the actual application. Fastly tests
its shipping S3 routes. Cloudflare and Spin do not currently dispatch S3 asset
routes; their `test-utils` builds use a test-only integration to exercise the
same core exporter with the real native stores and HTTP clients. This proves
the native core/adapter contract, not shipping S3 routing on those adapters.
Cases cover GET/HEAD, redirect sinks, bad certificates, and config-only mutations
without provisioning the mutated contexts. Each S3 route has distinct fictional credentials,
and signature expectations select that route's compiled bindings.

Fastly's bad-certificate case proves rejection by Viceroy's TLS verifier, not independent
enforcement of the application's `certificate_check` setting. An isolated mutation that
removed forced app-side verification still passed all 34 native assertions under Viceroy
0.21.0, including trusted-HTTPS positive controls and verified/unverified backend declarations.
The core `s3_secret_bindings_force_tls_and_never_follow_redirects` test catches that mutation
through its backend-spec assertion. Keep these two evidence claims separate; local Viceroy
verification does not prove the deployed Fastly setting.

Runtime tools must match the repository pins.
See [script prerequisites and coverage](scripts/README.md#secret-binding-runtime-checks).
These local checks do not certify deployed provider permissions or propagation.

### Deployment release checks

Track provider and rollout evidence in [issue #1479](https://github.com/IABTechLab/trusted-server/issues/1479), separately from the config-only attacker fix in #1037.

Host tests, native-name rendering, metadata presence, local runtime tests, and
provider operations are different evidence. Before production rollout, verify
isolated Fastly create/replace/read-through, provider propagation and limits,
config-publisher permissions, protected provisioning CI, and coordinated
migration/retirement/rollback. These deployment checks do not require production
application credentials; use fictional values in isolated resources. Missing
evidence remains a release risk, not a passing result or proof of a code defect.

Rehearse all four native walkthroughs with fictional values in isolated resources.
Test config/plan/overlay/compiler/manifest/service/store drift before input and
with zero mutations, repeated-role selection, unsupported adapter writes,
cancellation, invalid bytes/newlines, provider failures and uncertain outcomes.
Inspect argv/stdin/output without printing sentinel credentials. Test same-context
replacement, fresh versus retained states, S3 maintenance, and old-context replay
after deletion. Do not claim Tinybird response observation or fleet-wide refresh.

## Start the local server

Configure at least one reachable provider in `trusted-server.toml`, then start the
Expand Down
3 changes: 3 additions & 0 deletions crates/trusted-server-adapter-axum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ web-time = { workspace = true }
trusted-server-core = { workspace = true, features = ["test-utils"] }
axum = { workspace = true }
base64 = { workspace = true }
rcgen = { workspace = true }
rustls = { workspace = true }
temp-env = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
tokio-rustls = { workspace = true }
tower = { workspace = true, features = ["util"] }
Loading
Loading