CVE-2026-47261 - wasmtime-wasi FilePerms WRITE bypass

wasmtime-wasi incorrectly allowed a guest to open a preopened file with a truncate flag even when the embedding granted only FilePerms::READ. The bug matters most for platforms that run untrusted or tenant-controlled WebAssembly modules and expect WASI preopen permissions to prevent file mutation.

This is a sandbox integrity issue, not a generic Wasmtime CLI issue. The dangerous embedding pattern is a WASI context that gives a directory mutation permission such as DirPerms::MUTATE while restricting file permissions to read-only. The fix is to move to a patched Wasmtime release train and add a regression check that truncation is denied without running attacker payloads against production paths.

When to use it

Use this recipe when a Rust repository embeds Wasmtime/WASI for plugin hosts, agent runtimes, workflow runners, server-side extension platforms, CI tools, or tenant/user-uploaded WebAssembly execution. It is most important when WASI preopened directories are meant to be read-only for files but still grant directory mutation permissions.

Do not use it to run untrusted Wasm or mutate real workspace paths. Use it to upgrade the runtime train, audit preopen policy, and add a temporary-directory regression test for truncate denial.

Inputs

  • Rust workspaces, Cargo.toml, Cargo.lock, vendored crates, Dockerfiles, runtime images, SBOMs, generated dependency reports, plugin bundles, and agent sandbox manifests that may include wasmtime-wasi.
  • WASI embedding code: WasiCtxBuilder, preopened_dir, DirPerms, FilePerms, path_open, descriptor.open-at, OFLAGS_TRUNC, and OpenFlags::TRUNCATE.
  • Runtime classification: trusted developer tool, CI tool, tenant sandbox, marketplace plugin host, agent runtime, workflow runner, or user-uploaded Wasm service.
  • Preopened path inventory with read-only input roots, writeable scratch/output roots, workspace roots, cache/model/policy directories, tenant data, and job state.
  • Validation commands and rebuild steps for runtime images, plugin bundles, SBOMs, and cargo audit/security scans.

Affected versions

Package Vulnerable versions Fixed versions
wasmtime-wasi <24.0.9 24.0.9+
wasmtime-wasi >=25.0.0, <36.0.10 36.0.10+
wasmtime-wasi >=37.0.0, <44.0.2 44.0.2+

Wasmtime 45.0.0+ also includes the fix. Match the fixed train to the repository’s current major-line constraints instead of forcing a broad runtime migration when a patched maintenance train is available.

Indicator-of-exposure

  • The repository embeds wasmtime-wasi in a Rust service, plugin host, agent runtime, workflow runner, sandbox, or server-side extension platform.
  • Cargo.toml, Cargo.lock, SBOM output, generated dependency reports, or vendored crates resolve wasmtime-wasi in an affected range.
  • The embedding constructs a WASI context with preopened directories and grants directory mutation while keeping file permissions read-only, for example DirPerms::MUTATE with FilePerms::READ.
  • Untrusted, tenant-controlled, marketplace-provided, or user-uploaded Wasm modules can reach those preopened directories.
  • The preopened path contains cache files, generated artifacts, policy files, model assets, job state, tenant data, connector config, or any file whose truncation would affect integrity or availability.

Quick checks:

rg -n "wasmtime-wasi|WasiCtxBuilder|preopened_dir|DirPerms|FilePerms|OpenFlags::TRUNCATE|OFLAGS_TRUNC|path_open|descriptor\\.open-at" .
cargo tree -i wasmtime-wasi
cargo audit

Windows:

rg -n "wasmtime-wasi|WasiCtxBuilder|preopened_dir|DirPerms|FilePerms|OpenFlags::TRUNCATE|OFLAGS_TRUNC|path_open|descriptor\.open-at" .
cargo tree -i wasmtime-wasi
cargo audit

Do not run untrusted Wasm modules, truncate real files, or probe production preopens while assessing exposure.

Remediation strategy

  • Upgrade every controlled wasmtime-wasi, wasmtime, workspace lockfile, vendored crate, generated dependency report, SBOM, image, and runtime bundle to a fixed release train: 24.0.9+, 36.0.10+, 44.0.2+, or 45.0.0+.
  • Audit every WasiCtxBuilder preopen. Flag combinations where directory mutation is granted but file write permission is intentionally absent.
  • Prefer least-privilege preopens: separate read-only input directories from writeable scratch/output directories, and avoid exposing shared workspace roots to untrusted modules.
  • Add a safe regression test in a temporary directory that proves truncate opens are denied when the file permission is read-only. Do not use customer data or production paths.
  • For exposed multi-tenant runtimes, review job logs for unexpected file truncation and rebuild runtime images from clean lockfiles after the upgrade.

The prompt

Model context: this prompt was generated by GPT 5.5 Extra High reasoning.

You are remediating CVE-2026-47261 / GHSA-2r75-cxrj-cmph, a wasmtime-wasi
permission bypass where a WASI guest can truncate a file through
`path_open`/`open-at` despite read-only `FilePerms`. Produce exactly one
output:

- A reviewer-ready PR/change request that upgrades affected Wasmtime crates,
  audits WASI preopen permissions, adds safe regression coverage, refreshes
  generated artifacts, and documents runtime operator actions, or
- TRIAGE.md if this repository does not own an affected Wasmtime WASI embedding
  or cannot safely patch it.

## Rules

- Scope only CVE-2026-47261 / GHSA-2r75-cxrj-cmph and directly related
  Wasmtime WASI preopen-permission behavior.
- Treat tenant files, workspace files, generated artifacts, policy files,
  model assets, connector config, job state, and logs as sensitive.
- Do not run untrusted Wasm payloads, truncate real files, mutate production
  preopens, or use customer data to prove exposure.
- Do not remove sandbox boundaries, disable tests, or grant broad file write
  permission to make the upgrade easier.
- Do not auto-merge.

## Steps

1. Inventory every Rust workspace, `Cargo.toml`, `Cargo.lock`, vendored crate,
   Dockerfile, runtime image, SBOM, generated dependency report, and plugin or
   agent sandbox that can include `wasmtime-wasi`.
2. Determine every resolved `wasmtime-wasi` version. A target is vulnerable if
   it resolves to `<24.0.9`, `>=25.0.0, <36.0.10`, or
   `>=37.0.0, <44.0.2`.
3. Search for WASI embeddings and preopens: `WasiCtxBuilder`,
   `preopened_dir`, `DirPerms`, `FilePerms`, `DirPerms::MUTATE`,
   `FilePerms::READ`, `path_open`, `descriptor.open-at`, `OFLAGS_TRUNC`, and
   `OpenFlags::TRUNCATE`.
4. Classify each runtime:
   local trusted developer tool, CI-only tool, server-side tenant sandbox,
   plugin marketplace host, agent runtime, workflow runner, or user-uploaded
   Wasm execution surface.
5. If no affected `wasmtime-wasi` version or no repository-owned WASI embedding
   is present, stop with `TRIAGE.md` listing checked files and ownership.
6. Upgrade the current Wasmtime release train to a fixed version:
   `24.0.9+`, `36.0.10+`, `44.0.2+`, or `45.0.0+`. Refresh lockfiles,
   vendored crates, generated dependency reports, SBOMs, images, and runtime
   bundles.
7. Audit preopen policy. Keep read-only input roots separate from writeable
   scratch or output roots. Remove broad workspace-root preopens where a
   narrower directory is sufficient.
8. Add a safe regression test that creates a temporary file, grants the same
   preopen shape used by the application, and asserts truncate-open is denied
   when `FilePerms::WRITE` is absent. The test must not touch production paths
   or tenant data.
9. Add a PR body section named `CVE-2026-47261 operator actions` that states:
   - Wasmtime versions before and after;
   - which WASI embeddings and preopens were in scope;
   - whether untrusted or tenant-controlled Wasm can reach them;
   - which directories remain read-only versus writeable;
   - whether runtime images, plugin bundles, or sandbox artifacts must be
     rebuilt;
   - which validation commands passed.
10. Run available validation: `cargo update`, `cargo test`, sandbox regression
    tests, `cargo audit`, linting, image build, SBOM refresh, dependency scans,
    and any repository-specific plugin or agent-runtime tests.
11. Use PR title:
    `fix(sec): remediate CVE-2026-47261 in wasmtime-wasi`.

## Stop conditions

- No controlled Rust workspace, image, plugin runtime, or sandbox resolves an
  affected `wasmtime-wasi` version.
- The repository uses Wasmtime but not WASI preopened directories.
- The only affected runtime is supplied by another platform owner; write
  `TRIAGE.md` with the owner, fixed target version, and exposure assumptions.
- A fixed Wasmtime train cannot be consumed without a broader runtime migration
  or API rewrite.
- Meaningful verification would require mutating real files, running untrusted
  Wasm payloads, or exposing tenant data.
- Validation fails for unrelated pre-existing reasons; document those failures
  instead of broadening scope.

Verification - what the reviewer looks for

  • No controlled lockfile, vendored crate, image, SBOM, or dependency report resolves an affected wasmtime-wasi version.
  • WASI preopens are reviewed and do not grant unnecessary mutation permission to directories that should be read-only.
  • Regression coverage checks truncate denial in a temporary test fixture, not by modifying real workspace or tenant files.
  • Runtime images and generated artifacts that embed Wasmtime are refreshed.
  • Operator notes identify any exposed multi-tenant runtimes that need image redeploy or log review.

Watch for

  • Updating the direct wasmtime-wasi dependency while another workspace or image still pins an affected Wasmtime train.
  • Assuming the Wasmtime CLI exposure model applies to an embedded server-side sandbox; custom preopens are the important surface.
  • Granting FilePerms::WRITE broadly as a workaround instead of preserving least privilege.
  • Regression tests that prove the happy path runs but never assert truncate is denied for read-only preopens.
  • Stale plugin bundles, runtime images, or SBOM files that still carry the vulnerable crate after source updates.

Output contract

Return one of:

  • A reviewer-ready PR/change request that upgrades wasmtime-wasi to a fixed release train, audits WASI preopens, preserves least privilege between read-only inputs and writeable scratch/output directories, adds a safe truncate-denial regression test, refreshes generated artifacts, and documents runtime operator actions.
  • TRIAGE.md when no controlled affected Wasmtime WASI embedding, runtime image, plugin bundle, or sandbox exists.

The output must list versions before/after, embeddings and preopens reviewed, whether untrusted or tenant-controlled Wasm can reach them, directories that remain read-only versus writeable, validation commands, and rebuild/redeploy actions. It must not run untrusted Wasm, truncate real files, mutate production preopens, or expose tenant/workspace data.

References