denoland/deno · error · std::io::Error
InvalidInput
InvalidInput
Error message
unable to open database file: "{}" is a symlink What it means
ext/kv's sqlite backend refuses to open a database file if any component of its path is a symbolic link. refuse_reparse_point_components walks each path component with symlink_metadata; on encountering a symlink it returns this InvalidInput io::Error naming the offending component, as a security hardening measure since the KV database path is expected to be on the real filesystem.
Source
Thrown at ext/kv/sqlite.rs:92
}
path.to_path_buf()
}
/// SQLite does not enforce `SQLITE_OPEN_NOFOLLOW` on Windows (its
/// `winFullPathname` never resolves reparse points), so reject symlinks and
/// junctions in every path component manually before opening.
#[cfg(windows)]
fn refuse_reparse_point_components(path: &Path) -> std::io::Result<()> {
let mut current = PathBuf::new();
for component in path.components() {
current.push(component);
#[allow(
clippy::disallowed_methods,
reason = "the database path is always on the real fs"
)]
match std::fs::symlink_metadata(¤t) {
Ok(metadata) if metadata.file_type().is_symlink() => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!(
"unable to open database file: \"{}\" is a symlink",
current.display()
),
));
}
Ok(_) => {}
// Missing components are created (or rejected) by SQLite itself.
Err(_) => break,
}
}
Ok(())
}
#[async_trait(?Send)]
impl DatabaseHandler for SqliteDbHandler {
type DB = denokv_sqlite::Sqlite;View on GitHub (pinned to 336da420f4)
Solutions
- Use the real, resolved path (macOS: replace /tmp with /private/tmp; generally resolve with realpath) when opening the KV database.
- Remove the symlink and replace it with a real directory or bind mount.
- Point DENO_DIR or the openKv path at a non-symlinked location.
- If symlinks are intentional, avoid the sqlite KV backend for that path (use a different storage location).
Example fix
// before
const db = await Deno.openKv("/tmp/kv.sqlite"); // /tmp is a symlink on macOS
// after
const real = await Deno.realPath("/tmp");
const db = await Deno.openKv(`${real}/kv.sqlite`); Defensive patterns
Strategy: validation
Validate before calling
const st = await Deno.lstat(p);
if (st.isSymlink) throw new Error(`${p} is a symlink; resolve it first`);
const real = await Deno.realPath(p);
// pass `real` to Deno.openKv Type guard
function isNotSymlink(st: Deno.FileInfo): boolean { return !st.isSymlink; } Try / catch
try {
kv = await Deno.openKv(path);
} catch (e) {
if (String(e).includes("is a symlink")) {
kv = await Deno.openKv(await Deno.realPath(path));
} else throw e;
} Prevention
- Resolve symlinks (realPath) before opening KV databases.
- Remember macOS /tmp is a symlink to /private/tmp.
- Avoid symlinked dotfile-managed directories for DENO_DIR.
- Use bind mounts instead of symlinks for container data volumes.
When it happens
Trigger: Opening a KV sqlite database (--unstable-kv / Deno.openKv) whose path, or any ancestor directory, is a symlink — e.g. dbPath "/data/link/db.sqlite" where "link" or "db.sqlite" is a symlink.
Common situations: macOS /tmp being a symlink to /private/tmp; symlinked dotfile-managed config directories (e.g. Dropbox/chezmoi managed DENO_DIR); container setups where the data dir is a symlink into a volume.
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
- SQLITE_CANTOPEN
- ERR_FS_INVALID_SYMLINK_TYPE
- ERR_INCOMPATIBLE_OPTION_PAIR
- failed to open cache db at {}
- Refusing to include {}: not a regular file (symlinks and spe
AI-assisted analysis of denoland/deno@336da420f4 (2026-09-11).
Data as JSON: /api/errors/df0f46ab19b97568.
Report an issue: GitHub.