cargo-task-wasm

A sandboxed local task runner for Rust

Crates.io version Download docs.rs docs
## About This project provides a new `cargo task` subcommand that can be used to run project-local tasks inside a secure WebAssembly sandbox. It looks for files in a `tasks/` subdirectory of your project's root, and compiles those to [Wasm Components](https://component-model.bytecodealliance.org). This is an attempt at formalizing [cargo-xtask](https://github.com/matklad/cargo-xtask) pattern into a first-class, secure workflow. ## Roadmap - [x] Sketch out a repository layout or whatever workflow example - [x] Create a new `cargo` subcommand - [x] Hook up wasmtime to the subcommand - [x] Add support for manual paths in a `[tasks]` section in `Cargo.toml` - [x] Figure out how to configure capabilities for the tasks - [x] Add support for compiling cargo deps as part of subcommands - [x] Store config in Cargo metadata section - [x] Add support for using submodules - [ ] Add the remainder of the capabilities - [ ] Add support for installing tasks from crates.io - [ ] Support workspaces and [`[workspace.metadata]`](https://doc.rust-lang.org/cargo/reference/workspaces.html#the-metadata-table) ## Installation The `cargo task` subcommand compiles Rust to Wasm Components targeting [WASI 0.2](https://wasi.dev). In order to do that a working WASI 0.2 toolchain needs to be present on the host system. ```sh $ rustup +beta target add wasip2 # Install the WASI 0.2 target $ cargo install cargo-task-wasm # Install the `cargo task` subcommand ``` ## Usage ```text Usage: cargo task [ARGS]... Arguments: The name of the task to run [ARGS]... Optional arguments to pass to the task Options: -h, --help Print help -V, --version Print version Examples: cargo task codegen # run a task called `codegen` ``` ## Configuration ### Capabilities Tasks in `cargo task` follow the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege). By default they only get access to the working directory, and can access any additional command line arguments passed to it. Additional permissions can be configured via a `[package.metadata.tasks]` section in `Cargo.toml`. ```toml [package] name = "example" version = "0.1.0" edition = "2021" [package.metadata.tasks] env-filter = { inherit-env = ["FOO"] } # inherit specific env vars env-all = { inherit-env = true } # inherit all env vars ``` The reason why this is stored in `[package.metadata.tasks]` rather than a top-level `[tasks]` section is because that is [the canonical extension point](https://doc.rust-lang.org/cargo/reference/manifest.html#the-metadata-table) Cargo recommends using for third-party extensions. Should a `cargo tasks` command ever become a first-class extension to Cargo, the `package.metadata` prefix can be dropped. ### Dependencies Tasks must specify their own dependencies via `[package.metadata.task-dependencies]` in `Cargo.toml`. These dependencies are separate from Cargo's existing `[dev-dependencies]` and `[build-dependencies]` because these dependencies must be able to be compiled to Rust's `wasm32-wasip2` target. Not all dev or build deps may fit these requirements, which is why task dependencies are listed separately. ```toml [package] name = "example" version = "0.1.0" edition = "2021" [package.metadata.task-dependencies] wstd = "0.4.0" ``` ### Paths Tasks are discovered in the local `tasks/` directory of your project. This is a treated as standalone workspace where each file is treated as an individual task to be compiled and executed. This behaves not unlike the `tests/` directory in Cargo projects. It is possible to use both submodules and dependencies with tasks like you would expect. A typical project structure will look like this: ```text example/ ├── Cargo.toml ├── src │ └── lib.rs └── tasks ├── codegen.rs └── test.rs ``` This structure will give you access to the `cargo task codegen` and `cargo task test` subcommands. ## Limitations By default tasks only get access to the local project directory and any additional arguments passed via the CLI. Additional capabilities such as network or filesystem access can be configured via `Cargo.toml`. Sandboxing is provided by the Wasmtime runtime, and the available APIs are part of the `wasi:cli/command` world. Some limitations however still exist, and are good to be aware of: - **Limited ecosystem support**: At the time of writing WASI 0.2 is a fairly new compile target, and so ecoystem support is still in its infancy. Not all crates are expected to work, and may need to be updated first. - **Limited stdlib support**: For similar reasons: not all functionality in the stdlib will work yet. In particular network support for WASI 0.2 is still being implemented. This is expected to land in Rust 1.84 in the second half of 2024. If you want to access the network before then, you can try and use the [wasi](https://docs.rs/wasi) or [wstd](https://docs.rs/wstd) crates. - **No threading support**: At the time of writing support for threading in WASI 0.2 has not yet been implemented. Work on this is still ongoing upstream in the WASI subgroup. Consensus on a design seems to have formed, and implementation work has started - but this is unlikely to stabilize before the start of 2025. - **No support for exec/fork**: WASI 0.2 does not allow you to spawn or fork new processes. Providing access to this would be a sandbox escape, and so we don't provide access to it. This means it's not possible to shell out to call global tools, which may at times be impractical but is also a necessary limitation to guarantee security. In the future we hope to provide a way to instrument Cargo or Rustc directly from inside the sandbox. However this will need to be carefully evaluated and designed to ensure the sandbox cannot be escaped. ## See Also - [Custom tasks in Cargo (Aaron Turon, 2018)](http://aturon.github.io/tech/2018/04/05/workflows/) - First proposed a `cargo task` subcommand for custom tasks. - [`matklad/cargo-xtask` (Alex Kladov, 2019)](https://github.com/matklad/cargo-xtask) - A convention-based implementation of `cargo task`. - [`dtolnay/watt` (David Tolnay 2019)](https://github.com/dtolnay/watt) - Executing Rust procedural macros compiled as WebAssembly. ## Safety This crate uses ``#![deny(unsafe_code)]`` to ensure everything is implemented in 100% Safe Rust. ## Contributing Want to join us? Check out our ["Contributing" guide][contributing] and take a look at some of these issues: - [Issues labeled "good first issue"][good-first-issue] - [Issues labeled "help wanted"][help-wanted] [contributing]: https://github.com/yoshuawuyts/cargo-task-wasm/blob/master.github/CONTRIBUTING.md [good-first-issue]: https://github.com/yoshuawuyts/cargo-task-wasm/labels/good%20first%20issue [help-wanted]: https://github.com/yoshuawuyts/cargo-task-wasm/labels/help%20wanted ## Acknowledgements This project was built as a collaboration between [Michael Woerister](https://github.com/michaelwoerister) and [Yosh Wuyts](https://github.com/yoshuawuyts) as part of the 2024 Microsoft Hackathon, targeting the [Microsoft Secure Future Initiative](https://www.microsoft.com/en-us/microsoft-cloud/resources/secure-future-initiative). Special thanks to [Pat Hickey](https://github.com/pchickey) for showing us how to configure Wasmtime as a Rust library. ## License Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.