denoland/deno · error

Native addon cache path '{}' is not a private directory

Error message

Native addon cache path '{}' is not a private directory

What it means

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.

Source

Thrown at ext/rt_helper/lib.rs:244

#[allow(clippy::disallowed_methods, reason = "requires real fs")]
fn create_private_native_addon_dir(path: &Path) -> std::io::Result<()> {
  match fs::create_dir(path) {
    Ok(()) => Ok(()),
    Err(err) if err.kind() == ErrorKind::AlreadyExists => Ok(()),
    Err(err) => Err(err),
  }
}

fn ensure_private_native_addon_dir(path: &Path) -> std::io::Result<()> {
  create_private_native_addon_dir(path)?;
  validate_private_native_addon_dir(path)
}

#[allow(clippy::disallowed_methods, reason = "requires real fs")]
fn validate_private_native_addon_dir(path: &Path) -> std::io::Result<()> {
  let metadata = fs::symlink_metadata(path)?;
  if metadata.file_type().is_symlink() || !metadata.is_dir() {
    return Err(std::io::Error::new(
      ErrorKind::PermissionDenied,
      format!(
        "Native addon cache path '{}' is not a private directory",
        path.display()
      ),
    ));
  }

  // Windows temp directories are normally per-user; Unix additionally
  // enforces ownership and mode here.
  #[cfg(unix)]
  {
    use std::os::unix::fs::MetadataExt;
    use std::os::unix::fs::PermissionsExt;

    if metadata.uid() != current_uid() {
      return Err(std::io::Error::new(
        ErrorKind::PermissionDenied,

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Set TMPDIR (or TMP/TEMP) to a clean, user-owned, real directory and retry: TMPDIR=$(mktemp -d).
  2. 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.
  3. 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).
  4. If a security tool is rewriting the path, exclude the Deno cache dir from that policy.

Example fix

# before
TMPDIR=/shared/tmp deno run app.ts # cache path replaced by symlink -> PermissionDenied

# after
export TMPDIR=$(mktemp -d)
deno run app.ts
Defensive patterns

Strategy: validation

Validate before calling

import { lstatSync, statSync } from "node:fs";
import { tmpdir } from "node:os";
const t = process.env.TMPDIR ?? tmpdir();
const l = lstatSync(t, { throwIfNoEntry: false });
if (!l) throw new Error(`TMPDIR does not exist: ${t}`);
if (l.isSymbolicLink() || !l.isDirectory()) throw new Error(`TMPDIR must be a real directory, not a symlink/file: ${t}`);

Try / catch

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; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/576438dbb7b7266d. Report an issue: GitHub.