No description
  • Rust 98.2%
  • Dockerfile 1.1%
  • Shell 0.4%
  • C 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-11 19:45:46 +02:00
runtime feat: add nsjail runtime and committed artifact delivery 2026-10-11 19:45:46 +02:00
src feat: add nsjail runtime and committed artifact delivery 2026-10-11 19:45:46 +02:00
tests feat: add nsjail runtime and committed artifact delivery 2026-10-11 19:45:46 +02:00
.dockerignore feat: add nsjail runtime and committed artifact delivery 2026-10-11 19:45:46 +02:00
.gitignore feat: add Axum and OpenDAL CLSI foundation 2026-10-11 14:33:14 +02:00
Cargo.lock feat: add nsjail runtime and committed artifact delivery 2026-10-11 19:45:46 +02:00
Cargo.toml feat: add nsjail runtime and committed artifact delivery 2026-10-11 19:45:46 +02:00
LICENSE Initial commit 2026-10-11 12:07:31 +02:00
overleaf-clsi-openapi.yaml feat: add Axum and OpenDAL CLSI foundation 2026-10-11 14:33:14 +02:00
overleaf-sandbox-architecture.md feat: add Axum and OpenDAL CLSI foundation 2026-10-11 14:33:14 +02:00
README.md feat: add nsjail runtime and committed artifact delivery 2026-10-11 19:45:46 +02:00

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-relative openat2 with 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.