denoland/deno · error · AnyError

temporary directory ancestor

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.

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.

Appendix: 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)