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(&current) {
      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

  1. Use the real, resolved path (macOS: replace /tmp with /private/tmp; generally resolve with realpath) when opening the KV database.
  2. Remove the symlink and replace it with a real directory or bind mount.
  3. Point DENO_DIR or the openKv path at a non-symlinked location.
  4. 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

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


AI-assisted analysis of denoland/deno@336da420f4 (2026-09-11). Data as JSON: /api/errors/df0f46ab19b97568. Report an issue: GitHub.