[WIP] do-not-merge -- fix/bind-nitro-attestation-to-sessions #6

Closed
Ghost wants to merge 0 commits from refs/pull/6/head into main
Ghost commented 2026-07-16 17:38:50 +00:00 (Migrated from codeberg.org)

⚠️ Old checkpoint PR, new work for merging is in #7

  • Bind Nitro attestation to each E2P v2 session so a verified attestation can't be replayed onto another session
  • Bound session admission and add a fixed replay window (caps stored sessions, rejects out-of-window sequence numbers)
  • Harden cross-origin v2 transport recovery (localhost/127.0.0.1 CORS + stale-worker recovery)
  • Serve SDK bootstrap scripts on passthrough paths so a controlling service worker won't capture its own code
  • Report the E2P protocol version in enclave metadata and force-exclude /e2p/ from passthrough

tests

  • Add local E2E harness: eval-attestation feature + steve-eval binary serving signed synthetic Nitro evidence, Docker fixture, browser demo, and Playwright suite
  • Synthetic eval attestation runs the real verifier path (only the trust anchor differs)
  • Add tests: 12 Playwright E2E, 6 JS unit, 3 Rust unit (incl. confirmation-rejection cases)
  • Run: ./scripts/test-e2e.sh (full E2E) or ./scripts/test-e2e.sh up → http://127.0.0.1:3000 (manual demo)
# ⚠️ Old checkpoint PR, new work for merging is in #7 - Bind Nitro attestation to each E2P v2 session so a verified attestation can't be replayed onto another session - Bound session admission and add a fixed replay window (caps stored sessions, rejects out-of-window sequence numbers) - Harden cross-origin v2 transport recovery (`localhost`/`127.0.0.1` CORS + stale-worker recovery) - Serve SDK bootstrap scripts on passthrough paths so a controlling service worker won't capture its own code - Report the E2P protocol version in enclave metadata and force-exclude `/e2p/` from passthrough **tests** - Add local E2E harness: `eval-attestation` feature + `steve-eval` binary serving signed synthetic Nitro evidence, Docker fixture, browser demo, and Playwright suite - Synthetic eval attestation runs the real verifier path (only the trust anchor differs) - Add tests: 12 Playwright E2E, 6 JS unit, 3 Rust unit (incl. confirmation-rejection cases) - Run: `./scripts/test-e2e.sh` (full E2E) or `./scripts/test-e2e.sh up` → http://127.0.0.1:3000 (manual demo)
Ghost commented 2026-07-21 15:17:02 +00:00 (Migrated from codeberg.org)

what it does vs v1 of steve

v1: Nitro evidence → process-lifetime Ed25519 key → signed ephemeral X25519 exchange → one AES key

  • Attestation establishes trust in a long-lived STEVE signing key.
  • That key can authorize many later exchanges without fresh attestation.
  • The AES session key is wrapped under another process-lifetime key and sent with every request.
  • Binding exists, but it is indirect and depends on those long-lived keys remaining enclave-only.

This PR: Nitro evidence → exact X25519 transcript + derived-secret binder → directional traffic keys

  • Fresh evidence directly includes both ephemeral keys, client nonce, session ID, origin context, transcript hash, and a binder derived from the shared secret.
  • The client then confirms possession of those exact derived keys.
  • No reusable Ed25519 channel authority or portable wrapped-key blob.
  • Adds per-session state, directional keys, authenticated sequence numbers, replay protection, expiry, and materially stronger forward secrecy.
## what it does vs v1 of steve ### v1: Nitro evidence → process-lifetime Ed25519 key → signed ephemeral X25519 exchange → one AES key - Attestation establishes trust in a long-lived STEVE signing key. - That key can authorize many later exchanges without fresh attestation. - The AES session key is wrapped under another process-lifetime key and sent with every request. - Binding exists, but it is indirect and depends on those long-lived keys remaining enclave-only. ### This PR: Nitro evidence → exact X25519 transcript + derived-secret binder → directional traffic keys - Fresh evidence directly includes both ephemeral keys, client nonce, session ID, origin context, transcript hash, and a binder derived from the shared secret. - The client then confirms possession of those exact derived keys. - No reusable Ed25519 channel authority or portable wrapped-key blob. - Adds per-session state, directional keys, authenticated sequence numbers, replay protection, expiry, and materially stronger forward secrecy.
Ghost commented 2026-07-24 13:43:26 +00:00 (Migrated from codeberg.org)

Added a local signed-synthetic E2E harness and debug UI for STEVE v2.

Included

  • eval-attestation feature and steve-eval binary using signed Nitro-shaped evidence with an evaluation trust root.
  • Docker/Playwright flow covering browser SDK → STEVE v2 → fixture application.
  • Live session diagnostics and an interactive request inspector showing plaintext application input/output and the actual encrypted CBOR envelopes.
  • Service-worker bootstrap/restart recovery hardening.
  • Confirmation rejection tests for transcript mismatch and incorrect session secrets.

Synthetic evidence exercises the protocol and verifier logic, but does not prove Nitro hardware isolation or approved-PCR policy.

Run it

./scripts/test-e2e.sh

# Manual demo
./scripts/test-e2e.sh up   # http://127.0.0.1:3000
./scripts/test-e2e.sh down

cargo test -p steve --features eval-attestation

Current validation: 12 Playwright E2E, 42 JavaScript, and 19 Rust tests.

**Added a local signed-synthetic E2E harness and debug UI for STEVE v2.** **Included** - `eval-attestation` feature and `steve-eval` binary using signed Nitro-shaped evidence with an evaluation trust root. - Docker/Playwright flow covering browser SDK → STEVE v2 → fixture application. - Live session diagnostics and an interactive request inspector showing plaintext application input/output and the actual encrypted CBOR envelopes. - Service-worker bootstrap/restart recovery hardening. - Confirmation rejection tests for transcript mismatch and incorrect session secrets. Synthetic evidence exercises the protocol and verifier logic, but does **not** prove Nitro hardware isolation or approved-PCR policy. **Run it** ```sh ./scripts/test-e2e.sh # Manual demo ./scripts/test-e2e.sh up # http://127.0.0.1:3000 ./scripts/test-e2e.sh down cargo test -p steve --features eval-attestation ``` Current validation: 12 Playwright E2E, 42 JavaScript, and 19 Rust tests.
Ghost commented 2026-07-28 00:05:18 +00:00 (Migrated from codeberg.org)

Careful pre-merge review found two issues I think should be fixed before merge:

  1. STEVE_CORS_ORIGINS="*" is accepted for the E2P API (crates/steve/src/lib.rs around cors_layer). With wildcard CORS, any browser origin can run the SDK, establish an E2P session, and send encrypted requests that STEVE forwards to the protected upstream. Since the decrypted envelope controls method/path/headers, this effectively authorizes arbitrary web origins to use STEVE as a cross-origin gateway to the upstream. I think production should require an explicit allow-list of authorized application origins, or wildcard should be gated to an explicit dev/eval mode.

  2. The service worker default passthrough list includes /, and isPassthrough() only checks the pathname (steve-js-sdk/src/enclave-sw.js). That means any controlled same-origin request to / bypasses E2P, including non-bootstrap requests such as POST / with a sensitive body. The bootstrap exception should be method/mode-aware (for example only top-level/document GET /) or / should not be a default passthrough path.

Careful pre-merge review found two issues I think should be fixed before merge: 1. `STEVE_CORS_ORIGINS="*"` is accepted for the E2P API (`crates/steve/src/lib.rs` around `cors_layer`). With wildcard CORS, any browser origin can run the SDK, establish an E2P session, and send encrypted requests that STEVE forwards to the protected upstream. Since the decrypted envelope controls method/path/headers, this effectively authorizes arbitrary web origins to use STEVE as a cross-origin gateway to the upstream. I think production should require an explicit allow-list of authorized application origins, or wildcard should be gated to an explicit dev/eval mode. 2. The service worker default passthrough list includes `/`, and `isPassthrough()` only checks the pathname (`steve-js-sdk/src/enclave-sw.js`). That means any controlled same-origin request to `/` bypasses E2P, including non-bootstrap requests such as `POST /` with a sensitive body. The bootstrap exception should be method/mode-aware (for example only top-level/document `GET /`) or `/` should not be a default passthrough path.
Ghost commented 2026-07-30 18:16:10 +00:00 (Migrated from codeberg.org)

Security review notes from a pass over PR #6. I think the main things to address/check are:

  1. Workload identity is not enforced yet. The browser verifies a valid AWS Nitro attestation and the v2 session binding, but I don't see an expected PCR/module/image policy being checked (expectedPcrPolicy: 'not-checked'). A malicious service in some other valid Nitro enclave could bind its own session and pass unless clients also pin the intended STEVE workload identity. I'd fail closed on an expected PCR/manifest/verifier-key policy for production.

  2. Upstream redirects can escape the origin guard. upstream_url() constrains the initial path to the configured upstream, but the shared reqwest client uses default redirect handling. If the upstream app has an open redirect, STEVE can be made to fetch link-local/internal/cross-origin URLs. I'd set redirect(Policy::none()) or manually follow only same-origin redirects.

  3. Upstream responses are read fully with no explicit timeout/size cap. Both protected and plaintext forwarding call .bytes().await, and the reqwest client has no timeout. A large/slow/streaming upstream response can tie up tasks or enclave memory. I'd add timeouts and stream with a max response size.

  4. Service-worker config is persistently mutable by any controlled same-origin page. configure can change enclaveOrigin, passthrough/exclude rules, timeouts, and diagnostics, and it is persisted. This may be acceptable if same-origin JS is fully trusted, but for shared origins/user-content/partial-XSS it lets a lower-privilege page persistently weaken protection. Consider narrowing scope or authenticating privileged SW commands.

  5. Production container build should probably use --locked and an explicit package/bin so reviewed Cargo.lock is authoritative.

  6. Eval-only Rust artifacts still appear in cargo package --list (src/bin/steve-eval.rs, src/eval_attestation.rs). Not a container blocker with default features off, but worth tightening if source/Cargo release surfaces are meant to exclude eval material too.

I ran cargo test -p steve --all-targets --all-features (21 passed), NODE_OPTIONS=--experimental-global-webcrypto npm --prefix steve-js-sdk test (44 passed), and ./scripts/test-production-exclusions.sh (passed).

Security review notes from a pass over PR #6. I think the main things to address/check are: 1. Workload identity is not enforced yet. The browser verifies a valid AWS Nitro attestation and the v2 session binding, but I don't see an expected PCR/module/image policy being checked (`expectedPcrPolicy: 'not-checked'`). A malicious service in some other valid Nitro enclave could bind its own session and pass unless clients also pin the intended STEVE workload identity. I'd fail closed on an expected PCR/manifest/verifier-key policy for production. 2. Upstream redirects can escape the origin guard. `upstream_url()` constrains the initial path to the configured upstream, but the shared reqwest client uses default redirect handling. If the upstream app has an open redirect, STEVE can be made to fetch link-local/internal/cross-origin URLs. I'd set `redirect(Policy::none())` or manually follow only same-origin redirects. 3. Upstream responses are read fully with no explicit timeout/size cap. Both protected and plaintext forwarding call `.bytes().await`, and the reqwest client has no timeout. A large/slow/streaming upstream response can tie up tasks or enclave memory. I'd add timeouts and stream with a max response size. 4. Service-worker config is persistently mutable by any controlled same-origin page. `configure` can change `enclaveOrigin`, passthrough/exclude rules, timeouts, and diagnostics, and it is persisted. This may be acceptable if same-origin JS is fully trusted, but for shared origins/user-content/partial-XSS it lets a lower-privilege page persistently weaken protection. Consider narrowing scope or authenticating privileged SW commands. 5. Production container build should probably use `--locked` and an explicit package/bin so reviewed `Cargo.lock` is authoritative. 6. Eval-only Rust artifacts still appear in `cargo package --list` (`src/bin/steve-eval.rs`, `src/eval_attestation.rs`). Not a container blocker with default features off, but worth tightening if source/Cargo release surfaces are meant to exclude eval material too. I ran `cargo test -p steve --all-targets --all-features` (21 passed), `NODE_OPTIONS=--experimental-global-webcrypto npm --prefix steve-js-sdk test` (44 passed), and `./scripts/test-production-exclusions.sh` (passed).
Ghost commented 2026-07-31 12:04:25 +00:00 (Migrated from codeberg.org)

Will review the last comments on #7, let's use #7 as this one is just a linear checkpoint included in 7

Will review the last comments on #7, let's use #7 as this one is just a linear checkpoint included in 7
Ghost commented 2026-08-04 15:00:03 +00:00 (Migrated from codeberg.org)

This PR is included in #7, closing

This PR is included in #7, closing

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
public/steve!6
No description provided.