Skip to content

core: WASM/WASI target support feasibility (browser vs edge) #316

Description

@bug-ops

Summary

Evaluate whether exarch-core should support WebAssembly targets, and if so, which one (wasm32-unknown-unknown for browsers vs wasm32-wasip1/p2 for edge/serverless). This is a feasibility/research issue, not a committed feature. It captures the technical blockers, the audience trade-offs, and an actionable first step that is valuable independently of the final WASM decision.

The question driving this: does a WASM build expand the audience and add functional value? Conclusion up front: functional value for current users (CLI / Python / Node) is zero — WASM is a pure bet on a new audience. The two WASM targets are effectively two different markets with very different cost/benefit.

Motivation

Reusing a single security-audited Rust core across a browser/edge runtime would let JS/TS developers handle archives without trusting heterogeneous JS libraries (fflate, JSZip, libarchive.js), and would extend the "same audited code in Rust / Python / Node / WASM" story. The strongest differentiator — safe handling of untrusted archives (path-traversal + zip-bomb defense) — is exactly where existing JS libraries are historically weak (cf. node-tar path-traversal CVEs).

Blockers

1. C-binding compressors (smaller than expected)

Dependency tree audit (cargo tree -p exarch-core --all-features):

Dependency Backend wasm32-unknown-unknown wasm32-wasip1
flate2 1.1 (gz) miniz_oxide — pure Rust works works
bzip2 0.6 libbz2-rs-sys 0.2.5pure Rust works works
xz2 0.1 (xz) lzma-sys 0.1.20C blocked works (wasi-sdk)
zstd 0.13 zstd-sys 2.0.16C blocked works (wasi-sdk)
tar, zip, sevenz-rust2 pure Rust works works

Only two real blockers: xz2 and zstd. bzip2 already migrated to a pure-Rust backend, so it is not a blocker. Mitigations:

  • xzlzma-rust2 0.16.4 (pure Rust, encode + decode) is already in the tree (pulled by sevenz-rust2); xz2 could be swapped for it.
  • zstdruzstd is pure Rust but decode-only. There is no good pure-Rust zstd encoder, so a browser build would have an asymmetry: tar.zst can be extracted but not created.

2. Public API is fully Path-coupled (the larger blocker)

All entry points read from / write to the filesystem:

pub fn extract_archive<P: AsRef<Path>, Q: AsRef<Path>>(...)   // reads a file, writes to disk
pub fn create_archive<P: AsRef<Path>, Q: AsRef<Path>>(...)
pub fn list_archive<P: AsRef<Path>>(...)
pub fn verify_archive<P: AsRef<Path>>(...)

wasm32-unknown-unknown (browser) has no filesystem at all. Supporting it requires new entry points over &[u8] / impl Read / impl Write, plus decoupling the internals from filesystem I/O. This is an architectural change to the core, not a thin type-mapping binding like the Python/Node crates.

3. Security model partially evaporates in a browser sandbox

14 of 49 core source files touch std::fs / std::os::unix. In a browser sandbox the following parts of the validation pipeline lose all meaning:

  • validate_symlink / HardlinkTracker (security/symlink.rs, security/hardlink.rs, types/safe_symlink.rs) — no symlinks in the sandbox
  • sanitize_permissions (setuid/setgid via libc, cfg(unix)) — no Unix permission model
  • the "extract to disk" semantics itself

Only validate_path (traversal) and validate_compression_ratio (zip bomb) remain meaningful. A browser build therefore ships roughly half the security model — and the half that least differentiates us from existing JS libraries.

By contrast, on wasm32-wasip1/p2 WASI provides a filesystem abstraction, so the full security model survives and the C compressors build via wasi-sdk.

Target analysis

Browser (wasm32-unknown-unknown)

  • Use case: privacy-preserving handling of user-uploaded archives without a server round-trip.
  • Mature competitors already exist: fflate, JSZip, libarchive.js.
  • Differentiator (safe untrusted-archive handling) is real but the niche is competitive, and this is exactly where our security model is most degraded.
  • Highest cost: needs in-memory API + pure-Rust zstd/xz replacements + feature-gating of inapplicable security.

Edge / WASI (wasm32-wasip1/p2 — Fastly Compute, Wasmtime, Spin, wasmCloud)

  • WASI provides a filesystem abstraction → full security model preserved.
  • C dependencies build via wasi-sdk → xz / zstd retained.
  • Far less competition from security-focused Rust solutions.
  • Technically the cheapest and strategically the most coherent target. Smaller audience than the browser.

Recommendation

  1. Not a priority at the current 0.4.x stage. ROI is low and it diverts focus from hardening the core before 1.0.
  2. If pursued, target wasm32-wasip1 first, not the browser — simpler (C deps build, FS exists), full security model, less competition.
  3. Decouple a first, independently-valuable step (see below).
  4. Defer the browser target to post-1.0, gated on demonstrated demand.

Proposed first step (independently valuable): in-memory / streaming API

Add reader/writer-based entry points to exarch-core that operate on &[u8] / impl Read / impl Write instead of AsRef<Path>. This is worth doing on its own merits — pipelines, in-process streams, faster tests that avoid temp files — and it is simultaneously the prerequisite foundation for any future WASM target. It can be split into its own enhancement issue if preferred.

Sketch (names illustrative):

pub fn extract_reader<R: Read, W: /* virtual sink */>(src: R, sink: W, cfg: &SecurityConfig) -> Result<ExtractionReport>;
pub fn create_writer<W: Write>(dst: W, entries: /* in-memory entries */, cfg: &CreationConfig) -> Result<CreationReport>;
pub fn list_reader<R: Read>(src: R) -> Result<Vec<EntryInfo>>;

Open design question: extraction currently writes a directory tree; a reader-based variant needs a "virtual filesystem sink" abstraction (in-memory tree / callback per entry) so that traversal and zip-bomb checks still apply while symlink/permission checks are conditionally compiled out where the target has no such concept.

Scope

In scope for this issue:

  • Decide go / no-go and the target priority (WASI-first vs browser-first vs neither-now).
  • Run the build spike below and attach the concrete compiler error list.
  • File follow-up issues (in-memory API; per-target work) once the direction is chosen.

Out of scope:

  • Actually shipping a exarch-wasm crate or npm package.
  • Browser-specific zstd/xz pure-Rust swaps (separate issue if/when browser target is approved).

Next action: build spike

Both targets are already installed locally. Run and capture the failure list:

# Edge/WASI — expected to be closest to building
cargo check -p exarch-core --target wasm32-wasip1 --all-features

# Browser — expected to fail on lzma-sys / zstd-sys C builds and on std::fs / cfg(unix) usage
cargo check -p exarch-core --target wasm32-unknown-unknown --all-features

Attach the exact errors here to turn "feasible / not feasible" into a concrete remediation checklist.

Environment

  • Version: 0.4.1 (workspace.package.version)
  • MSRV: Rust 1.93.0
  • Installed WASM targets: wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2

References

  • fflate, JSZip, libarchive.js — incumbent browser archive libraries
  • lzma-rust2 (already in dep tree) — pure-Rust xz/lzma candidate
  • ruzstd — pure-Rust zstd (decode-only) candidate
  • WASI runtimes: Wasmtime, Fastly Compute, Spin, wasmCloud

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P4Nice-to-have: research ideas, future enhancementsbacklogDeferred — not scheduled for current milestonecoreChanges to exarch-coreenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions