- Rust 98.2%
- Dockerfile 1.1%
- Shell 0.4%
- C 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| runtime | ||
| src | ||
| tests | ||
| .dockerignore | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| overleaf-clsi-openapi.yaml | ||
| overleaf-sandbox-architecture.md | ||
| README.md | ||
open-clsi
A sandboxed Overleaf CLSI replacement in Rust using Axum, Tokio and Apache OpenDAL, initially targeting Ceph/RGW's S3 API. See the architecture and the pinned upstream interface.
Current scope
This is a runtime milestone, not yet a complete drop-in backend. By default
only GET /status (and implicit HEAD) is exposed, returning CLSI is alive\n:
process liveness, not readiness. With execution explicitly enabled and real
storage/nsjail startup probes passed, the same listener also exposes:
POST /project/:project[/user/:user]/compile: synchronous real compilation.POST /project/:project[/user/:user]/compile/stop: scope cancellation.DELETE /project/:project[/user/:user]: cancel and invalidate the input baseline.GET/HEAD /project/:project[/user/:user]/build/:build/output/:path: committed artifacts, validators and single byte ranges. Multipart ranges receive the complete file, as permitted by HTTP; HEAD ignores Range.
History replay, word count, SyncTeX queries, output ZIPs, content-hash downloads and conversion routes remain absent (404). Unsupported compile modes/options are rejected, never silently treated as implemented. There is no unsandboxed or simulated-success fallback. The Docker/toolchain and real RGW checks have not been run in the development box; follow the host runbook.
Implemented library components:
- Compile envelopes, resource validation, parser defaults and status vocabulary. History JSON is preserved but not validated or replayed yet. Wire decoding alone does not authorize URL fetching or execution.
- Validated project/user/build identifiers and canonical project-relative paths.
Backslashes and control characters are rejected;
.and redundant separators are normalized. Workspace access uses descriptor-relativeopenat2with beneath/no-symlink/no-cross-mount resolution. - OpenDAL S3 adapter with conditional creation/replacement, revision-pinned reads, size bounds and operation deadlines. Its HTTP transport is explicit, uses Rustls, rejects redirects/proxy environment variables and disables HTTP retries.
- Immutable input/artifact/result repositories with SHA-256 integrity checks. Identical writes can be verified and reused; differing bytes cannot overwrite an immutable location. Untrusted scope, build and file segments are hashed in private storage keys, with the original values retained in typed references.
- Scope coordination using one versioned control object: admission sequences, owner nonces, input baselines, cancellation generations, fixed target sets and guarded result publication. Completing or releasing a build requires the trusted caller to have confirmed process and workspace cleanup first.
The supervisor reserves a local slot before admission and owns execution across HTTP disconnects. Each slot is a separately byte/inode-limited tmpfs provisioned by the host, reused only after confirmed termination and cleanup. The Rust supervisor stays outside each build's cgroup; a safe-Rust launcher joins it before exec of nsjail. Resource-based full/incremental inputs and the artifact index are immutable snapshots. URL fetching has explicit origin/IP authority, no redirects, no inherited credentials and byte/deadline bounds.
Durable supervisor journals record ownership before admission. Confirmed-cleaned owners are reconciled with exact-nonce CAS updates at startup and every five seconds, fencing late metadata writes. Unconfirmed cleanup quarantines the slot, retains the active entry, and blocks new admission until operator recovery. Dropping an HTTP request or a build lease never deletes an active entry.
CAS conflicts get bounded, jittered retries. Transport failures and timeouts do not: a PUT may already have applied, so admission/publication fails closed and requires ownership reconciliation. Missing live control objects, incompatible schema versions and invalid references are errors, not empty caches.
Active and retained result counts are bounded. Each admission reserves a result slot. There is no automatic retention/garbage collector or verified completed-build retry API yet; reaching the record limit rejects admission rather than evicting references. Cache clear does not remove retained build-qualified artifacts. Same-scope execution is limited to one build pending browser-ordering checks. Other OpenDAL backends must enforce the coordination contract; API capability flags alone are insufficient. Real isolation, provider/failure topology, retention, crash fencing and Overleaf integration still need host verification.
Development
Requires Rust 1.91 or newer. Run from the repository root:
cargo test --all-targets --locked
cargo clippy --all-targets --locked -- -D warnings
cargo fmt --all -- --check
Default tests run entirely locally, including an HTTP S3 test server exercising OpenDAL's actual request headers. They cover admission and publication races, cancellation, stale owners, input invalidation, immutable integrity, bounded contention, ambiguous writes, HEAD/GET consistency and HTTP listener shutdown. They do not prove sandbox isolation, real-provider consistency or Overleaf integration. New tests are focused on manifest authority, link-safe export/cleanup and durable cleanup recovery; use the short manual checklist in the runbook for execution.
Running the foundation
Configure a bucket and optionally an S3-compatible endpoint, then run:
export OPEN_CLSI_S3_BUCKET=compile-storage
export OPEN_CLSI_S3_REGION=us-east-1
cargo run --locked
Credentials use OpenDAL's AWS credential provider chain, e.g. AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY and optional AWS_SESSION_TOKEN, or workload identity.
Never put credentials in source control or pass the supervisor's environment
into a jail. No bucket is created. With execution disabled, startup makes no S3
requests. With execution enabled, startup writes one fresh private probe key,
races conditional creates/replacements, tests revision-checked reads and deletes
that exact key. First configure a disposable RGW bucket. An interrupted probe
can leave residue under contract-probes/; use a bucket lifecycle policy.
| Environment variable | Default / requirement |
|---|---|
OPEN_CLSI_LISTEN |
127.0.0.1:3013; IP address and port |
OPEN_CLSI_WORKER_ID |
Fresh worker-<UUID>; at most 128 ASCII identifier bytes |
OPEN_CLSI_S3_BUCKET |
Required; existing bucket |
OPEN_CLSI_S3_REGION |
us-east-1 |
OPEN_CLSI_S3_ENDPOINT |
AWS default; optional absolute HTTP(S) URL, no embedded credentials |
OPEN_CLSI_S3_ROOT |
open-clsi; nonempty relative prefix |
OPEN_CLSI_STORAGE_TIMEOUT_SECONDS |
10; positive, per storage operation |
OPEN_CLSI_MAX_CONTROL_BYTES |
262144 (256 KiB) |
OPEN_CLSI_MAX_BLOB_BYTES |
67108864 (64 MiB); immutable object/artifact limit |
OPEN_CLSI_MAX_ACTIVE_PER_SCOPE |
32 with execution disabled; must be 1 when enabled |
OPEN_CLSI_MAX_RECORDS_PER_SCOPE |
128; active plus retained results, at least the active limit |
OPEN_CLSI_CONTROL_UPDATE_ATTEMPTS |
8 |
RUST_LOG |
open_clsi=info,tower_http=info; structured JSON logging |
S3 uses path-style addressing. Keep this internal listener behind authenticated
editor routing/network ACLs; project identifiers are not authentication. The
runtime settings and packaging are documented in runtime/README.md.
Artifacts share the API listener; Overleaf CE's nginx routes hardcode the old
CLSI 127.0.0.1:8080, so changing web CLSI host settings alone is insufficient.
Real S3 contract test
The provider test is ignored by default. It writes to a bucket and must only
be run after explicitly configuring a disposable test bucket/endpoint and its
credentials. Set OPEN_CLSI_TEST_S3_ENDPOINT, OPEN_CLSI_TEST_S3_BUCKET and
optionally OPEN_CLSI_TEST_S3_REGION, then run:
cargo test --locked --test opendal_s3 real_s3_conditional_contract -- --ignored
The test uses a fresh open-clsi-contract-tests/<UUID> prefix and deletes its
exact object after success. A failed/interrupted test can leave that prefix
behind; apply a retention policy to the disposable test bucket. Passing this
test checks a few conditional-write cases, not every consistency/failure mode.
Remaining milestones
Validate the pinned image and nsjail on a suitably delegated host, then add history replay, word count, SyncTeX, ZIPs, retention/garbage collection, verified completed-build retries and deployment integration. Do not direct a live editor at this service until its required features and artifact routing have been validated with the deployed Overleaf version.