denoland/deno · error · AnyError

temporary directory ancestor '{}' is writable by other users

Error message

temporary directory ancestor '{}' is writable by other users without the sticky bit

What it means

Second check in `ensure_secure_temp_parent`: each ancestor of the canonicalized temp dir is inspected for permission bits. Any ancestor that is group- or world-writable (mode & 0o022) and lacks the sticky bit (0o1000) is rejected — without the sticky bit, anyone with write access can rename or replace Deno's temp entries, so the path is treated as untrusted.

Source

Thrown at cli/util/temp.rs:140

  // SAFETY: geteuid has no preconditions.
  let current_uid = unsafe { libc::geteuid() };
  for ancestor in path.ancestors() {
    let dir = std::fs::OpenOptions::new()
      .read(true)
      .custom_flags(libc::O_NOFOLLOW | libc::O_DIRECTORY)
      .open(ancestor)?;
    let metadata = dir.metadata()?;
    if metadata.uid() != current_uid && metadata.uid() != 0 {
      bail!(
        "temporary directory ancestor '{}' is owned by uid {}, not current uid {} or root",
        ancestor.display(),
        metadata.uid(),
        current_uid
      );
    }
    let mode = metadata.mode();
    if mode & 0o022 != 0 && mode & 0o1000 == 0 {
      bail!(
        "temporary directory ancestor '{}' is writable by other users without the sticky bit",
        ancestor.display()
      );
    }
  }
  Ok(())
}

#[cfg(not(unix))]
fn ensure_secure_temp_parent(path: &Path) -> Result<(), AnyError> {
  let metadata = std::fs::symlink_metadata(path)?;
  if metadata.file_type().is_symlink() || !metadata.is_dir() {
    bail!("'{}' is not a directory", path.display());
  }
  Ok(())
}

#[cfg(unix)]

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Set the sticky bit on the writable ancestor: `chmod +t /path/to/dir` (this mirrors /tmp's 1777).
  2. Or remove group/other write: `chmod go-w /path/to/dir`.
  3. Or export TMPDIR to a private directory you own with default permissions.

Example fix

# before: ancestor is 0775 (group-writable, no sticky bit)
stat -c '%a %n' /srv/scratch        # 775 /srv/scratch
export TMPDIR=/srv/scratch/tmp
deno install
# error: temporary directory ancestor '/srv/scratch' is writable by other users without the sticky bit

# after
chmod go-w /srv/scratch             # or: chmod +t /srv/scratch
deno install
Defensive patterns

Strategy: validation

Validate before calling

# pre-check TMPDIR ancestors for group/world-writable without sticky bit
d="$(cd "${TMPDIR:-/tmp}" && pwd -P)"
while [ "$d" != "/" ]; do
  if [ -n "$(find "$d" -maxdepth 0 -perm /022 2>/dev/null)" ] && [ ! -k "$d" ]; then
    echo "writable non-sticky temp ancestor: $d ($(stat -c %a "$d"))"
  fi
  d="$(dirname "$d")"
done

Prevention

When it happens

Trigger: TMPDIR located under a group-writable directory (e.g. `chmod 775 /opt/scratch`) or a world-writable `chmod 777` dir that never got the sticky bit. `/tmp` itself is fine (1777 = sticky), but nested mounts and shared workspace dirs commonly fail this check.

Common situations: Teams sharing group-writable scratch volumes; Docker images that `chmod 777` a path without `+t`; CI agents with permissive TMPDIR configurations; bind-mounted host directories with relaxed modes.

Related errors


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