Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
b11f95b
Replace path-to-key engine with a Merkle-anchored block store
aruokhai Aug 8, 2026
89b8d5c
docs: record what remains before an enclave deployment
aruokhai Aug 8, 2026
dfe0061
Add enclave-runtime, and merge s3fs-wasmtime into s3fs-host
aruokhai Aug 9, 2026
8af045f
Add a SQLite conformance and benchmark workload
aruokhai Aug 9, 2026
6d12113
Cover the rest of SQLite, and measure what a commit actually costs
aruokhai Aug 9, 2026
3fad755
Serve the guest trusted time from /dev/ptp0
aruokhai Aug 9, 2026
b289a45
Serve the guest entropy from the Nitro Security Module
aruokhai Aug 9, 2026
7e2652f
Verify NSM entropy in an emulated Nitro enclave
aruokhai Aug 9, 2026
9871c74
Serve a wasi:http guest over the block store
aruokhai Aug 9, 2026
f8f2d0e
Request, parse and verify NSM attestation documents
aruokhai Aug 9, 2026
a0bcbc8
Terminate TLS in the enclave and bind the certificate to attestation
aruokhai Aug 9, 2026
a08b2cd
Give the enclave a network over vsock
aruokhai Aug 9, 2026
5809997
Obtain Let's Encrypt certificates without the key leaving the enclave
aruokhai Aug 9, 2026
d689040
Document the serving path
aruokhai Aug 9, 2026
8680d8d
Name the rustls crypto provider instead of letting it be inferred
aruokhai Aug 9, 2026
298fde0
Build the enclave image reproducibly, and run the whole stack under QEMU
aruokhai Aug 11, 2026
de85559
Delete s3fs-runner
aruokhai Aug 15, 2026
dbc8e7a
Fold s3fs-host into enclave-runtime
aruokhai Aug 15, 2026
e252079
feat(nitro-nsm): add PCR description and extension methods
aruokhai Aug 15, 2026
3df1ef8
fix(tests): repair the suites the boot machine broke
aruokhai Aug 16, 2026
9334951
fix(store): read past delete markers, which Object Lock does not stop
aruokhai Aug 16, 2026
68bd3d2
fix(fs): a read-only handle may not set timestamps either
aruokhai Aug 16, 2026
0ea5b59
fix(serve): a hung guest no longer wedges the server
aruokhai Aug 16, 2026
f90b1e0
feat(serve): a guest instance can outlive the request that created it
aruokhai Aug 16, 2026
c84edde
feat(serve): the runtime knows who is connected, and the guest cannot…
aruokhai Aug 16, 2026
2be6371
fix(serve): a client keeps its identity across a certificate renewal
aruokhai Aug 19, 2026
6906cf9
revert(serve): an instance never outlives the request that created it
aruokhai Aug 30, 2026
208d5f6
docs(serve): write down the invariant and who is calling
aruokhai Aug 31, 2026
17d5a49
perf(serve): measure what a guest instance actually costs
aruokhai Aug 31, 2026
83730d4
feat(keys): KMS releases the master secret only to an attested enclave
aruokhai Aug 31, 2026
974dac2
feat(fs): a path cannot leave the subtree it started in
aruokhai Aug 31, 2026
30668c9
feat(serve): one directory, one warm instance and one lock per client
aruokhai Aug 31, 2026
181ba0a
feat(runtime): make per-client views reachable from a deployment
aruokhai Aug 31, 2026
8666eed
feat(auth): the guest is unreachable without a passkey for that request
aruokhai Sep 1, 2026
0c4e52f
feat(deploy): the image requires a passkey, and the e2e proves it
aruokhai Sep 1, 2026
905171a
feat(guest-io): a guest's output is bounded, tagged, and shipped off …
aruokhai Sep 5, 2026
19335a4
fix(s3): an S3 request that stalls no longer hangs whatever called it
aruokhai Sep 5, 2026
b65a380
feat(attest): every response carries a proof of the connection it cam…
aruokhai Sep 5, 2026
d467483
fix(serve): a long stream is not a runaway, and an abandoned call is …
aruokhai Sep 6, 2026
87b576c
feat(serve): negotiate HTTP/2, without changing what HTTP/1.1 clients…
aruokhai Sep 6, 2026
3c2924b
feat(grpc): a guest answers while the client is still asking
aruokhai Sep 6, 2026
401f75f
feat(auth): opening a channel is not permission to sign on it
aruokhai Sep 6, 2026
5f13765
test(grpc): check the guest's framing against a client from outside t…
aruokhai Sep 6, 2026
436e200
docs: say beside the enrollment token what a stream-open approval buys
aruokhai Sep 6, 2026
63da263
feat(auth): approve an interaction, not a payload
aruokhai Sep 6, 2026
bbbee4d
feat(guest): a guest change is a policy edit, not an image rebuild
aruokhai Sep 11, 2026
c8ca3e6
feat: implement background task scheduling for tenants
aruokhai Sep 12, 2026
d07487a
feat(auth): registration is open, and CI runs what it ships
aruokhai Sep 12, 2026
600d6fd
feat(notify): wake a tenant's devices without telling anyone what hap…
aruokhai Sep 13, 2026
5a9a16c
refactor: enclave-runtime is the whole idea, not one crate among four
aruokhai Sep 14, 2026
4652708
feat(qemu): an enclave to develop against, and a document worth verif…
aruokhai Sep 14, 2026
db7e056
feat(auth): an Android app can use the passkey its domain vouched for
aruokhai Sep 16, 2026
c2a1b02
feat(qemu): a dev enclave a phone app can sign for
aruokhai Sep 16, 2026
48c34d0
fix(tasks): a failed background task says why
aruokhai Sep 16, 2026
3aa2947
docs(tasks): run-task is given a run id, not the enqueued id
aruokhai Sep 16, 2026
9866746
fix(auth): register passkeys Android can find again
aruokhai Sep 16, 2026
b99c29d
docs: clarify attestation document handling in responses and client i…
aruokhai Sep 16, 2026
909ebee
feat(serve): a deployment may name the origins its guests reach
aruokhai Sep 16, 2026
2a88c2b
feat(qemu): keep the dev store across restarts, and let the guest rea…
aruokhai Sep 16, 2026
f55a6ac
feat(runtime): a connection the runtime holds, so a counterparty can …
aruokhai Sep 20, 2026
e11fb68
enhance dev-enclave.sh and lib.sh for public host support with Let's …
aruokhai Sep 23, 2026
e4113de
fix: close the review findings on the block store, the filesystem and…
aruokhai Sep 23, 2026
521e7ed
fix: close the re-review findings on root claims, guest cleanup and s…
aruokhai Sep 24, 2026
7ee630d
docs: rebuild the README around the whole runtime
aruokhai Sep 24, 2026
928e84c
test: let the e2e job past its first stage, and race a claim on real S3
aruokhai Sep 24, 2026
c8d80d0
style: format and lint the tree, so the unit job gets past its first …
aruokhai Sep 25, 2026
b752d98
ci: build MinIO from source, now that its images cannot be pulled
aruokhai Sep 25, 2026
932bc27
build: pin the toolchain to 1.98.0, so a Rust release cannot fail the…
aruokhai Sep 25, 2026
dcbf744
ci: drop the Magic Nix Cache from the e2e job
aruokhai Sep 25, 2026
89567bf
fix(nix): make a payload file an input of the image, not a string nam…
aruokhai Sep 25, 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
38 changes: 38 additions & 0 deletions .github/actions/rust/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Rust toolchain and cache
description: >
Install the toolchain rust-toolchain.toml names, and restore the cargo cache
for this job's workspaces.

inputs:
shared-key:
description: >
Cache scope. Jobs sharing a key share one cache, so group jobs that build
the same artifacts and separate ones that do not.
required: true
workspaces:
description: >
Cargo workspaces to cache, one per line, as `path -> target`. The examples
are each their own workspace and are invisible to the default `.`.
required: false
default: "."

runs:
using: composite
steps:
# rust-toolchain.toml is the single source of truth for the channel, the
# components and the wasm target. rustup honours it the moment cargo runs
# inside the repo, so `rustup show` installs exactly what it names — and a
# toolchain change stays a one-file edit rather than one edit per job that
# can silently drift from the file.
- run: rustup show
shell: bash

- uses: Swatinem/rust-cache@v2
with:
shared-key: ${{ inputs.shared-key }}
workspaces: ${{ inputs.workspaces }}
# Pull requests restore but never save. Four jobs each saving a
# multi-gigabyte cache is what pushes the repository past the 10 GB
# limit, and the eviction that follows throws away the entry the next
# run needed. main writes the cache every branch then restores.
save-if: ${{ github.ref == 'refs/heads/main' }}
165 changes: 91 additions & 74 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,99 +6,116 @@ on:
pull_request:
branches: [main]

# Two pushes to one pull request used to run two complete fans to completion,
# and only the second was ever read.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1

# Four stages, each a checkout, a toolchain, and one script. The scripts are the
# recipes: they live in scripts/ so a job can be run locally with the same
# command CI uses, and so deploy/qemu-nitro/run-e2e.sh can share them instead of
# keeping a second copy that has to agree.
#
# There are deliberately no `needs:` edges. Gating the long jobs behind the
# short one would put its minutes on the front of the critical path to save
# runner time only on runs that were failing anyway; cancel-in-progress above
# removes the waste that actually dominates, which is superseded runs.
jobs:
build:
name: build + test (default features)
unit:
name: fmt, clippy, unit tests
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- uses: ./.github/actions/rust
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: cargo build
run: cargo build --workspace --all-targets
- name: cargo test (lib)
run: cargo test --workspace --lib
- name: cargo clippy
run: cargo clippy --workspace --all-targets -- -D warnings
shared-key: host
- run: scripts/ci-check.sh

build-aws:
name: build + test (aws feature)
bench:
name: benchmarks
runs-on: ubuntu-latest
timeout-minutes: 45
# Writing the tracked history back to gh-pages on main.
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: ./.github/actions/rust
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- name: cargo build --features aws
run: cargo build --workspace --features aws --all-targets
- name: cargo test --features aws (lib)
run: cargo test --workspace --features aws --lib
- name: cargo clippy --features aws
run: cargo clippy --workspace --features aws --all-targets -- -D warnings

minio-integration:
name: MinIO integration tests
runs-on: ubuntu-latest
services:
minio:
image: minio/minio:RELEASE.2025-02-28T09-55-16Z
ports:
- 9000:9000
env:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
options: >-
--health-cmd "curl -f http://localhost:9000/minio/health/ready"
--health-interval 5s
--health-timeout 3s
--health-retries 12
ENTRYPOINT /usr/bin/docker-entrypoint.sh server /data
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: cargo test (minio integration)
run: cargo test -p s3fs-core --features aws --test minio_integration -- --ignored --test-threads=1
shared-key: guests
workspaces: |
. -> target
examples/guest-http -> target
- run: scripts/ci-bench.sh

fmt:
name: rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: benchmark-action/github-action-benchmark@v1
with:
components: rustfmt
- run: cargo fmt --all -- --check
tool: cargo
output-file-path: bench.txt
github-token: ${{ secrets.GITHUB_TOKEN }}
# History is written only from main. A pull request compares against
# it and says so in the log, but must not rewrite the baseline.
auto-push: ${{ github.ref == 'refs/heads/main' }}
save-data-file: ${{ github.ref == 'refs/heads/main' }}
# A shared runner's neighbours move these numbers more than most
# commits do, so a regression is reported and never fails the build.
# Tightening this is worth doing once there is enough history to know
# what the noise floor actually is.
alert-threshold: "200%"
comment-on-alert: true
fail-on-alert: false

guest-build:
name: example guest builds for wasm32-wasip2
# There is deliberately no SQLite job. The runtime measures its guest into
# PCR16 before it will serve, and that reads the NSM — which a hosted runner
# does not have, so the binary refuses to start there at all. The workload
# lives on in scripts/ci-sqlite.sh for a machine with an enclave.
e2e:
name: the whole stack, in an emulated enclave
runs-on: ubuntu-latest
# The integration suites, then a cold Nix build of the image, then the
# emulated boot. This is the only integration signal, so it carries the
# suites that used to have their own job.
timeout-minutes: 150
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: DeterminateSystems/nix-installer-action@main
# No Magic Nix Cache. It relays every store lookup through the Actions
# cache API, which rate-limits it, and Nix treats the refusals as fatal:
# the image build failed on "rate limit exceeded" with nothing wrong in
# it. nixpkgs comes from cache.nixos.org regardless; what is lost is only
# this repository's own derivations, rebuilt each run.
- uses: ./.github/actions/rust
with:
targets: wasm32-wasip2
- uses: Swatinem/rust-cache@v2
- name: Install wasi-sdk
run: |
curl -sLO https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz
mkdir -p $HOME/wasi-sdk
tar xf wasi-sdk-25.0-x86_64-linux.tar.gz -C $HOME/wasi-sdk --strip-components=1
- name: build guest-fsdemo (with bundled SQLite)
shared-key: guests
workspaces: |
. -> target
examples/guest-http -> target
examples/guest-grpc -> target
- uses: actions/cache@v4
with:
path: target/qemu-nitro/tools
# The pinned version lives in the script, so the key follows the
# script rather than being a second place to update.
key: vhost-device-vsock-${{ hashFiles('scripts/ci-e2e.sh') }}
- run: scripts/ci-e2e.sh
env:
CC_wasm32_wasip2: ${{ github.workspace }}/../wasi-sdk/bin/clang
AR_wasm32_wasip2: ${{ github.workspace }}/../wasi-sdk/bin/ar
run: |
export CC_wasm32_wasip2=$HOME/wasi-sdk/bin/clang
export AR_wasm32_wasip2=$HOME/wasi-sdk/bin/ar
export CFLAGS_wasm32_wasip2="--sysroot=$HOME/wasi-sdk/share/wasi-sysroot -DSQLITE_THREADSAFE=0 -DHAVE_USLEEP=1"
cd examples/guest-fsdemo
cargo build --release --target wasm32-wasip2
# A runner boots the emulated enclave more slowly than a workstation,
# and the default is tuned for the latter.
TIMEOUT: 480
# Nested virtualisation on hosted runners works but is not something
# GitHub supports, so this is the job most likely to fail for reasons
# outside this repository. Its logs are the only way to tell that apart
# from a real regression.
- uses: actions/upload-artifact@v4
if: failure()
with:
name: e2e-logs
path: |
target/qemu-nitro/e2e/*.log
if-no-files-found: ignore
17 changes: 16 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/target
# Any crate's build directory, not only the workspace root: the example
# guests are separate workspaces with their own target/.
target/
**/*.rs.bk
Cargo.lock.bak
.env
Expand All @@ -9,3 +11,16 @@ Cargo.lock.bak
*.iml
.DS_Store
/wasm-cache/

# OpenTofu/Terraform working directory: vendored provider binaries, hundreds of
# MB, re-fetched by `tofu init`. The lock file IS committed — it pins provider
# versions and belongs under review.
deploy/tofu/.terraform/

# Run artifacts that scripts/ci-bench.sh and scripts/ci-sqlite.sh drop in the
# repository root. `bench.txt` exists only long enough for the benchmark tracker
# to read it, and `runtime.log` is the SQLite job's server output; both are
# rewritten by every run. Rooted with a leading slash so this covers the files
# those scripts write and not a log someone keeps deliberately elsewhere.
/bench.txt
/runtime.log
Loading
Loading