astrid-runtime/astrid · error · VfsError::Io(std::io::Error::IsADirectory)

{path}

Error message

{path}

What it means

map_filesystem_error in the capsule WASM storage VFS converts the inner FilesystemError::InvalidPath into VfsError::SandboxViolation, carrying the offending path as the message. This fires when a guest path escapes or violates the sandbox prefix rules checked by ensure_prefix — the VFS refuses to resolve any path outside the capsule's mounted prefix.

Solutions

  1. Rewrite the guest path to be relative to the sandbox mount prefix.
  2. Strip or reject '..' components and absolute prefixes before calling the VFS API.
  3. Check the configured mount/prefix mapping so the path lands inside it.
  4. Validate user-supplied paths with the same prefix rules before passing them to the VFS.

Example fix

// before
vfs.exists("/etc/hosts")  // SandboxViolation

// after
vfs.exists("data/hosts") // path relative to sandbox prefix
Defensive patterns

Strategy: validation

Validate before calling

// Rust (caller-side prefix check)
fn path_in_sandbox(prefix: &str, path: &str) -> bool {
    let p = std::path::Path::new(path);
    !p.is_absolute()
        && !p.components().any(|c| matches!(c, std::path::Component::ParentDir))
        && path.starts_with(prefix)
}

Type guard

fn is_safe_vfs_path(path: &str) -> bool {
    !path.is_empty()
        && !path.starts_with('/')
        && !path.split('/').any(|seg| seg == ".." || seg.is_empty() || seg == ".")
}

Try / catch

match vfs_result {
    Err(VfsError::SandboxViolation(path)) => {
        eprintln!("path rejected by sandbox: {path}");
        // rewrite path relative to mount prefix and retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling file_entry, exists, mkdir, open_dir, or opening a file from WASM guest code with a path that fails prefix validation: absolute paths outside the mount, '..' traversal, empty or malformed relative paths, or Windows-style paths when not permitted.

Common situations: WASM module hardcoding '/etc/passwd' or 'C:\\data' paths instead of the sandbox-relative mount path; path joins accidentally producing '../' segments; passing raw user input straight into VFS path arguments.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/ba699f253046d04a. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-capsule/src/engine/wasm/storage_vfs.rs:549

            .write()
            .await
            .remove(handle)
            .ok_or(VfsError::InvalidHandle)?;
        let file = file.lock().await;
        if file.writable && file.dirty {
            self.filesystem
                .write(&file.path, &file.bytes)
                .map_err(map_filesystem_error)?;
        }
        Ok(())
    }
}

fn map_filesystem_error(error: FilesystemError) -> VfsError {
    match error {
        FilesystemError::InvalidPath(path) => VfsError::SandboxViolation(path),
        FilesystemError::NotFound(path) => VfsError::NotFound(path.as_str().to_owned()),
        FilesystemError::IsDirectory(path) => VfsError::Io(std::io::Error::new(
            std::io::ErrorKind::IsADirectory,
            path.as_str().to_owned(),
        )),
        FilesystemError::NotDirectory(path) => VfsError::Io(std::io::Error::new(
            std::io::ErrorKind::NotADirectory,
            path.as_str().to_owned(),
        )),
        FilesystemError::AlreadyExists(path) => VfsError::Io(std::io::Error::new(
            std::io::ErrorKind::AlreadyExists,
            path.as_str().to_owned(),
        )),
        FilesystemError::DirectoryNotEmpty(path) => VfsError::Io(std::io::Error::new(
            std::io::ErrorKind::DirectoryNotEmpty,
            path.as_str().to_owned(),
        )),
        FilesystemError::NamespaceConflict(path) => VfsError::Io(std::io::Error::other(format!(
            "namespace conflict at {}",
            path.as_str()

View on GitHub (pinned to affd8760f4)