{"record":{"id":"3694a29a78e204bb","repo":"denoland/deno","slug":"sqlite-cantopen","errorCode":"SQLITE_CANTOPEN","errorMessage":"unable to open database file: \"{}\" is a symlink","messagePattern":"unable to open database file: \"(.+?)\" is a symlink","errorType":"error_code","errorClass":"rusqlite::Error::SqliteFailure","httpStatus":null,"severity":"error","filePath":"ext/node_sqlite/database.rs","lineNumber":110,"sourceCode":"  path.to_path_buf()\n}\n\n/// SQLite does not enforce `SQLITE_OPEN_NOFOLLOW` on Windows (its\n/// `winFullPathname` never resolves reparse points), so reject symlinks and\n/// junctions in every path component manually before opening.\n#[cfg(windows)]\nfn refuse_reparse_point_components(path: &Path) -> Result<(), rusqlite::Error> {\n  let mut current = PathBuf::new();\n  for component in path.components() {\n    current.push(component);\n    #[allow(\n      clippy::disallowed_methods,\n      reason = \"node:sqlite operates on the real file system\"\n    )]\n    match std::fs::symlink_metadata(&current) {\n      Ok(metadata) if metadata.file_type().is_symlink() => {\n        return Err(rusqlite::Error::SqliteFailure(\n          rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_CANTOPEN),\n          Some(format!(\n            \"unable to open database file: \\\"{}\\\" is a symlink\",\n            current.display()\n          )),\n        ));\n      }\n      Ok(_) => {}\n      // Missing components are created (or rejected) by SQLite itself.\n      Err(_) => break,\n    }\n  }\n  Ok(())\n}\n\n/// Static mapping of JavaScript property names to SQLite limits.\n/// Order matches SQLite limit constant values (0-10).\n/// Keep in sync with LIMIT_NAMES in ext/node/polyfills/sqlite.ts.\nconst LIMIT_MAPPING: [(&str, Limit); NUM_LIMITS] = [","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/denoland/deno/blob/336da420f4343cbb1dcbd5eed9d075ff555ed6ee/ext/node_sqlite/database.rs#L92-L128","documentation":"The node:sqlite compatibility layer (ext/node_sqlite) applies the same symlink-refusal hardening as ext/kv: refuse_reparse_point_components checks each path component with symlink_metadata before opening the database, and returns rusqlite's SQLITE_CANTOPEN with this message if any component is a symlink, so node:sqlite operates only on the real filesystem.","triggerScenarios":"new DatabaseSync(path) (node:sqlite) where path or an ancestor is a symlink — e.g. a symlinked database file or a symlinked data directory; also hit via sqlite3 CLI-style paths passed through the Node compat API.","commonSituations":"Node apps migrated to Deno where the DB lives behind a symlink (versioned db files, ln -s deployments, macOS /tmp, symlinked home dirs in containers).","solutions":["Pass the fully resolved (realpath) database path to DatabaseSync instead of a symlinked one.","Replace the symlink with a real file/directory or a bind mount.","Create the database directly at its final location rather than linking to it.","Run the same app under Node.js if symlinked DB paths are a hard requirement."],"exampleFix":"// before\nconst db = new DatabaseSync(\"/var/app/current/db.sqlite\"); // 'current' is a symlink\n// after\nimport { realpathSync } from \"node:fs\";\nconst db = new DatabaseSync(realpathSync(\"/var/app/current/db.sqlite\"));","handlingStrategy":"validation","validationCode":"import { lstatSync, realpathSync } from \"node:fs\";\nif (lstatSync(dbPath).isSymbolicLink()) {\n  dbPath = realpathSync(dbPath);\n}","typeGuard":"function isRealFile(p: string): boolean {\n  const st = lstatSync(p, { throwIfNoEntry: false });\n  return st !== undefined && !st.isSymbolicLink();\n}","tryCatchPattern":"try {\n  db = new DatabaseSync(dbPath);\n} catch (e) {\n  if (String(e).includes(\"is a symlink\")) {\n    db = new DatabaseSync(realpathSync(dbPath));\n  } else throw e;\n}","preventionTips":["Always pass realpath-resolved database paths to node:sqlite.","Avoid deploying databases behind symlinked release directories (use stable real paths or bind mounts).","Check with lstat (not stat) that neither the file nor its ancestors are symlinks.","Document that node:sqlite in Deno requires real-filesystem paths."],"tags":["node-compat","sqlite","symlink","security"],"backgroundTag":"path-traversal-blocked","analyzedSha":"336da420f4343cbb1dcbd5eed9d075ff555ed6ee","analyzedAt":"2026-09-11T17:12:50.272Z","contentChangedAt":"2026-09-11T17:12:50.272Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}