How to Fix: Cannot use `–dir /` and `–dir .` simultaneously
Using --dir / and --dir . together fails because both flags try to expose overlapping host paths into the same sandbox, and the runtime cannot safely resolve two mounts that collapse to the same effective directory tree.
This issue appears when running a WASI-enabled module, such as a precompiled wasm2c binary from the WABT toolchain, with both --dir / and --dir .. At first glance, that looks harmless: one flag exposes the filesystem root, and the other exposes the current working directory. In practice, however, . usually resolves to a location already contained inside /, which creates an overlapping preopen configuration.
When the runtime builds its sandboxed filesystem view, it expects each preopened directory to map cleanly and predictably. If two entries refer to the same path or to parent-and-child paths without a distinct guest alias, path resolution becomes ambiguous. That is why the module does not get the expected merged access model.
Understanding the Root Cause
The key concept behind this bug is how WASI directory preopens work. The --dir flag does not simply grant broad host access in an abstract way. It registers a concrete host directory as a filesystem capability visible to the WebAssembly program.
Here is what happens technically:
--dir /preopens the host root directory.--dir .preopens the current working directory.- If the current directory is somewhere under root, which it always is on Unix-like systems, the second preopen is not meaningfully distinct from the first.
- Some runtimes reject or mishandle overlapping mounts because they cannot deterministically expose both as separate capabilities without explicit guest path mapping.
In other words, the bug is not that the runtime cannot open directories. The problem is that capability-based filesystem access is intentionally strict. Passing both root and a path contained within root can lead to:
- duplicate mount entries,
- ambiguous lookup behavior,
- conflicting canonical path resolution, or
- runtime validation failure depending on the implementation.
This is especially important with sandboxed runtimes because they are designed to avoid hidden path aliasing. If one mount shadows another, the engine may choose correctness and safety over permissive behavior.
Step-by-Step Solution
The safest fix is to avoid mounting both / and . simultaneously unless the runtime supports explicit aliasing for guest-visible paths.
Option 1: Use only one directory
If you already expose /, you do not need to also expose ..
wasm-runtime --dir / app.wasm
If you only need the current working directory, use this instead:
wasm-runtime --dir . app.wasm
This is the simplest and most correct solution for most cases.
Option 2: Use explicit guest path mapping if supported
Some runtimes support mapping host paths to separate guest-visible directories. If available, this avoids collisions by giving each preopen a unique name inside the sandbox.
wasm-runtime --mapdir /root::/ --mapdir /cwd::. app.wasm
The exact syntax varies by runtime, but the idea is always the same:
- mount
/under one guest path, - mount the current working directory under another guest path,
- avoid exposing both as overlapping anonymous preopens.
Check your runtime documentation for supported flags such as mapped directories, aliases, or virtual mount points.
Option 3: Normalize paths before launching
If your launcher script dynamically builds arguments, resolve paths first and remove duplicates or nested overlaps.
#!/usr/bin/env sh
ROOT="/"
CWD="$(pwd)"
if [ "$CWD" = "/" ]; then
exec wasm-runtime --dir / app.wasm
else
exec wasm-runtime --dir "$CWD" app.wasm
fi
This prevents redundant capability declarations.
Option 4: Update wrapper logic in your toolchain
If your integration automatically appends both flags, fix the argument builder so it understands containment rules.
function uniqueDirs(dirs) {
const normalized = [...new Set(dirs.map(d => require('path').resolve(d)))];
return normalized.filter((dir, index, all) => {
return !all.some((other, otherIndex) => {
if (index === otherIndex) return false;
return dir.startsWith(other + require('path').sep) || dir === other;
});
});
}
const dirs = uniqueDirs(['/', '.']);
console.log(dirs);
In real-world tooling, you may want the inverse policy: keep the narrower path and drop the broader one. The right choice depends on your security model.
Recommended resolution strategy
- Decide whether the module truly needs host root access.
- If not, prefer
--dir .or another minimal directory. - If yes, remove
--dir .because it is redundant. - If both must appear logically, use explicit guest aliases instead of plain overlapping
--dirflags.
Common Edge Cases
- Current directory is exactly root: In this case,
.resolves to/, making the duplication literal rather than just overlapping. - Symlinked working directory: If
.points through a symlink, the runtime may canonicalize it to a real path that still falls under/or unexpectedly matches another mount. - Different runtime behavior: Some WASI runtimes reject overlapping preopens, while others allow them but produce surprising path visibility.
- Containerized execution: Inside Docker or another container,
/may already be a restricted rootfs, but overlap rules still apply. - Security regression: Replacing
--dir .with--dir /may make the module far more privileged than intended. - Cross-platform path normalization: If your tooling runs on multiple operating systems, be careful with path separators, drive letters, and canonicalization logic.
FAQ
Why does --dir / not automatically make --dir . harmless?
Because the runtime tracks preopened directories as explicit capabilities, not just as a loose set of accessible paths. Two flags that point into the same tree can still conflict during mount registration or guest path resolution.
Can I safely expose both directories with different guest names?
Yes, if your runtime supports explicit mapping or aliasing. That is the correct way to present multiple related host paths without relying on overlapping anonymous mounts.
What is the best practice for production?
Use the least-privilege directory set possible. Prefer a narrow mount such as --dir . or a specific application data directory, and only expose / if the program genuinely requires full filesystem visibility.
The practical fix for this GitHub issue is straightforward: do not combine overlapping --dir mounts unless your runtime supports explicit guest path mapping. Choose one directory, or map each path to a unique virtual location inside the sandbox.