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
- Rewrite the guest path to be relative to the sandbox mount prefix.
- Strip or reject '..' components and absolute prefixes before calling the VFS API.
- Check the configured mount/prefix mapping so the path lands inside it.
- 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
- Always build VFS paths relative to the mount prefix
- Never pass raw user input directly as a VFS path
- Normalize and reject '..' segments before calling VFS APIs
- Document the sandbox prefix for guest module authors
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
- build_host_state
- cache contains a redirect or special entry
- capsule archive contains an unsafe path
- capsule path escaped source root
- capsule projection escaped its root
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)