Skip to content

wasi-python 0.1.0a3

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 17 Oct 21:19
· 1156 commits to main since this release
  • Breaking: nothing is writable by default (the owner's rule, constitution §4.15; spec 025).
    A call given no root and no grants has no /, no /tmp and no HOME: it can import,
    compute, print, read the stdlib and call the door, and nothing else. Before, it got a fresh
    private writable /. To keep that, pass root=True (--private-root on the command line);
    or grant the folders the program needs. root=None now means no root, like False.
  • Security: a root is prepared only through the folder that was checked (the 0.1.0a3
    security review, S1). The runner made root/usr/local/lib and root/tmp by path after
    checking the root, so a guest of another call with a writable folder above it could swap that
    folder for a link in between, and the runner removed a file named usr, local, lib or
    tmp, or made folders, outside every grant. It now works through the walked folder's
    descriptor; the guest never saw the wrong folder before, and still does not. A writable grant
    at, inside or above the host Python's own stdlib or site-packages is now refused (S2). The
    patch applier refuses a source tree that holds .git (S3).
  • np.save and np.load of files work (the first carried patch,
    recipes/patches/numpy-2.5.2/0001-npy-files-without-dup.patch): they raised OSError 58,
    because numpy's tofile/fromfile need os.dup, which WASI lacks. The patch is the fix numpy
    is asked to take (its pull request is drafted for the owner) and goes when numpy releases it.
    ndarray.tofile, np.fromfile on real files and memory maps still fail. The next §5.6 security
    review reads this patch.
  • Carried patches (the owner's decision; constitution §4.1): a fix the project cannot wait
    for may now be carried as a patch to a pinned project's source, under recipes/patches/, with
    its upstream pull request drafted under context/upstream/patches/. Each is reviewed, hashed in
    MANIFEST, named here, and deleted once upstream releases the fix. None is carried yet; the
    release check is now "the source trees are their tarballs plus the recorded patches".
  • Typed grants: run(grants=[Grant(host, guest, writable=False), ...]) gives the guest
    more host folders, each read-only unless it says writable=True, nested in any order;
    root=False gives it no /, only its grants. Every grant and root is walked folder by folder
    and checked before any work: no link a guest could have made, no read-only grant inside a
    writable one, no writable grant at, inside or above the runtime's own folders. After wasmtime
    opens each folder, the runner checks it opened the one it checked. HOME is made in the folder
    the guest sees at /tmp, and there is none without one.
  • The stdlib zip is read-only and no longer copied into a root: every call sees it read-only
    at /usr/local/lib, from one folder per process that its exit removes (a killed process's is
    swept like its other folders). A python315.zip that 0.1.0a2 left in a reused root is never
    read again and may be deleted.
  • Some root= values are now refused (a change): a root at, inside or above the runtime's
    own folders, such as the home folder, a project folder holding .venv or build/artifacts,
    or the temporary folder itself, since its guest could change files later calls trust; a root
    holding wasi_python's or wasmtime-py's own code; a root still to be made where the module
    cache will be made; and a root reached through a link in a folder you can write. A missing
    root is still made. No grant, read-only either, may hold the temporary folder, which holds
    every call's private folders and stdin.
  • Grants on the command line: wasi-python run --grant GUEST=HOST, --grant-writable GUEST=HOST, --root HOST and --no-root, each one of run's arguments.
  • wasi-python run: a command that runs one program and prints its result as one line of
    JSON, so any language can run agent code by spawning it (python -m wasi_python run is the
    same). Each option is one of run's arguments; without options the call has run's defaults.
  • A killed process's files are cleaned up: a process killed mid-call left its private root
    (the guest's files) and its stdin folder (the caller's stdin) in the temporary folder for
    good. Each now has a locked lock file beside it naming the kernel that made it; the next
    wasi_python process on that kernel removes them at its first call, and never touches a live
    call's folders or another kernel's. On a temporary folder without file locks, calls go on
    and such folders stay, as before.
  • trapped names a trap by its kind (a change to what it says): a stack overflow now reads
    "stack overflow", not wasmtime's heading "error while executing at wasm backtrace:", and so
    do "unreachable", "memory out of bounds" and every other trap with a code. An error with no
    trap code keeps its first line.
  • The released wheel is the bytes a second host rebuilt: the release workflow rebuilds the
    wheel on arm64 Linux and publishes only a wheel equal to it, byte for byte, whose artifacts
    match the set's SHA256SUMS.
  • Run from async code: run_async runs a call on a worker thread, so an async server's
    event loop never waits for a guest. Cancelling the awaiting task stops the guest (checked
    every 50 ms while it computes, at once when it waits or calls the door, later when other
    threads hold the GIL) and frees its worker before CancelledError is raised. Calls share one
    pool per process of DEFAULTS.async_workers threads (the CPU count, at most 8).
    run(cancel=event) stops a guest from any thread (trapped == "cancelled"). A process forked
    after it ran a guest now refuses to run guests, instead of aborting (macOS) or losing its
    deadlines; at exit, run_async's guests are stopped and its queued calls dropped.
  • sqlite3, bz2 and lzma are built in, from three new pins (SQLite 3.54.1, bzip2 1.0.8,
    liblzma from xz 5.8.4), with no upstream patch. bz2, lzma and tarfile's bz2 and xz
    modes write the same bytes as native; sqlite3 gives native's results, except the last digits
    of its math functions (trigonometric, hyperbolic, atan2, log10, one-argument log), which
    follow the guest's C library, and what changed since native's SQLite 3.53.1, such as JSON's
    float text; it has the native feature set (FTS3/4/5, R-tree, Geopoly, math functions,
    dbstat, percentile). shelve and dbm now create dbm.sqlite3 files, as native does. -V
    now says "no ctypes, mmap, sockets, ssl or zstd": compression.zstd is the compression module
    still missing.
  • SQLite's limits in the guest (the README's sqlite3 in the guest): one connection holds
    the database, through a <db>.lock directory beside it, while it writes or has a statement
    open, so a second connection gets "database is locked" after its busy timeout (5 s by
    default); journal_mode=wal stays delete unless locking_mode=exclusive; a guest stopped
    inside a transaction (killed, out of fuel, past its deadline, or by os._exit) leaves the
    lock and a hot journal in a reused root for the embedder to remove; SQLite's own
    random(), randomblob() and temporary names are seeded from the clock's second, so use
    secrets, uuid.uuid4() or os.urandom for IDs; a database in a read-only directory opens
    only through a URI that takes no lock, such as file:<path>?immutable=1; no
    load_extension; sqlite3.threadsafety is 0.
  • Sizes: the three libraries add about 2 MB to python.wasm (SQLite is most of it); the zip
    is unchanged. import bz2 and import lzma cost nothing more on the snapshot, which already
    holds them; import sqlite3 is not pre-imported.

Verify a download (gh logged in; see the README's Verifying a download):

gh attestation verify <asset> --repo ClayGendron/wasi_python \
  --signer-workflow ClayGendron/wasi_python/.github/workflows/release.yml@refs/tags/v0.1.0a3 \
  --source-ref refs/tags/v0.1.0a3 --deny-self-hosted-runners