{"record":{"id":"576438dbb7b7266d","repo":"denoland/deno","slug":"native-addon-cache-path-is-not-a-private-dire","errorCode":null,"errorMessage":"Native addon cache path '{}' is not a private directory","messagePattern":"Native addon cache path '(.+?)' is not a private directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ext/rt_helper/lib.rs","lineNumber":244,"sourceCode":"#[allow(clippy::disallowed_methods, reason = \"requires real fs\")]\nfn create_private_native_addon_dir(path: &Path) -> std::io::Result<()> {\n  match fs::create_dir(path) {\n    Ok(()) => Ok(()),\n    Err(err) if err.kind() == ErrorKind::AlreadyExists => Ok(()),\n    Err(err) => Err(err),\n  }\n}\n\nfn ensure_private_native_addon_dir(path: &Path) -> std::io::Result<()> {\n  create_private_native_addon_dir(path)?;\n  validate_private_native_addon_dir(path)\n}\n\n#[allow(clippy::disallowed_methods, reason = \"requires real fs\")]\nfn validate_private_native_addon_dir(path: &Path) -> std::io::Result<()> {\n  let metadata = fs::symlink_metadata(path)?;\n  if metadata.file_type().is_symlink() || !metadata.is_dir() {\n    return Err(std::io::Error::new(\n      ErrorKind::PermissionDenied,\n      format!(\n        \"Native addon cache path '{}' is not a private directory\",\n        path.display()\n      ),\n    ));\n  }\n\n  // Windows temp directories are normally per-user; Unix additionally\n  // enforces ownership and mode here.\n  #[cfg(unix)]\n  {\n    use std::os::unix::fs::MetadataExt;\n    use std::os::unix::fs::PermissionsExt;\n\n    if metadata.uid() != current_uid() {\n      return Err(std::io::Error::new(\n        ErrorKind::PermissionDenied,","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/rt_helper/lib.rs#L226-L262","documentation":"Deno validates the native addon cache directory (created under the system temp dir for compiled/cached node native addons) for privacy before using it: symlink_metadata must be a real directory, not a symlink. Violations return ErrorKind::PermissionDenied with this message. Deno normally falls back to a fresh private tempdir, so this error surfacing means the preferred path was invalid AND the fallback could not be validated either.","triggerScenarios":"Something replaced TMPDIR/<cache-name> with a symlink or a regular file (so the preferred path fails validation) and the fallback tempdir also fails the same check — e.g. a broken, permission-restricted, or policy-managed temp filesystem where Deno cannot obtain any private directory.","commonSituations":"Hardened or misconfigured TMPDIR (pointing at a path managed by another tool); security agents that replace cache dirs with symlinks; container images with unusual /tmp setups; shared writable dirs tampered with by other users.","solutions":["Set TMPDIR (or TMP/TEMP) to a clean, user-owned, real directory and retry: TMPDIR=$(mktemp -d).","Remove the offending path in the temp dir (rm the symlink/file named like the Deno native addon cache) so Deno can recreate it as a real directory.","On read-only or locked-down filesystems, move the temp dir to a writable location (e.g. emptyDir in k8s, writable layer of the container).","If a security tool is rewriting the path, exclude the Deno cache dir from that policy."],"exampleFix":"# before\nTMPDIR=/shared/tmp deno run app.ts # cache path replaced by symlink -> PermissionDenied\n\n# after\nexport TMPDIR=$(mktemp -d)\ndeno run app.ts","handlingStrategy":"validation","validationCode":"import { lstatSync, statSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nconst t = process.env.TMPDIR ?? tmpdir();\nconst l = lstatSync(t, { throwIfNoEntry: false });\nif (!l) throw new Error(`TMPDIR does not exist: ${t}`);\nif (l.isSymbolicLink() || !l.isDirectory()) throw new Error(`TMPDIR must be a real directory, not a symlink/file: ${t}`);","typeGuard":null,"tryCatchPattern":"try { await run(); } catch (e) { if (/not a private directory/.test(String(e))) throw new Error(`temp dir unusable for native addon cache — set TMPDIR=$(mktemp -d). TMPDIR=${t}`); throw e; }","preventionTips":["Point TMPDIR at a real, user-owned directory in containers instead of shared mounts.","Exclude Deno's temp cache paths from tools that replace directories with symlinks.","Health-check the temp dir (exists, real dir, writable) in container entrypoints."],"tags":["native-addons","cache","temp-dir","permissions","security","symlink"],"backgroundTag":"insecure-temp-directory","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}