denoland/deno · error

refusing to add {} to tarball: {}

Error message

refusing to add {} to tarball: {}

What it means

`validate_tar_path` runs on every relative path before it is appended to the npm tarball (the `deno publish`/pack path). It rejects paths containing a `..` component, an empty segment (absolute paths), or a stray `.` component. The check exists because `tar::Builder::append_data` only validates the 100-byte header name — long paths go through a GNU LongLink entry whose full path is written unchecked, so explicit validation makes both lengths behave the same and blocks archive path traversal.

Source

Thrown at cli/tools/pack/npm_tarball.rs:68

/// result to catch any `..` this introduces.
fn to_tar_path(relative: &str) -> String {
  relative.replace('\\', "/")
}

/// Reject archive paths that could escape the extraction root.
///
/// `tar::Builder::append_data` only validates the truncated 100-byte header
/// name when it emits a GNU LongLink entry; the full path is written to the
/// LongLink data unchecked. So a path >= 100 bytes can smuggle `..` past the
/// crate's own checks, while a short path is still rejected by `set_path`.
/// Validate here so both lengths behave the same.
///
/// Segments are split off the normalized `&str` rather than via
/// `std::path::Path::components()`: `Path` parsing is platform-dependent and
/// would treat these inputs differently on Windows.
fn validate_tar_path(path: &str) -> std::io::Result<()> {
  let invalid = |reason: &str| {
    std::io::Error::new(
      std::io::ErrorKind::InvalidInput,
      format!("refusing to add {} to tarball: {}", path, reason),
    )
  };
  let mut segments = path.split('/').peekable();
  // A leading "./" is fine; a leading empty segment means the path is absolute.
  if segments.peek() == Some(&".") {
    segments.next();
  }
  for segment in segments {
    match segment {
      ".." => return Err(invalid("path contains a '..' component")),
      "" => return Err(invalid("path is absolute or empty")),
      "." => return Err(invalid("path contains a '.' component")),
      _ => {}
    }
  }
  Ok(())

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Locate the printed path on disk (quote it: `ls -la './foo\..\..\evil'`) and delete or rename the offending file, then re-run.
  2. Exclude it from the package via the package.json `files` field or publish excludes so it never reaches tarball assembly.
  3. Fix the generator that produced backslash or `..`-containing filenames so the corpus is clean.

Example fix

// before: a file literally named "pkg\..\..\evil" becomes traversal after \\ → / normalization
// after: validate relative paths in your build step, mirroring the pack check
fn is_tar_safe(rel: &str) -> bool {
  let mut segments = rel.split('/');
  if rel.starts_with("./") { segments.next(); }
  segments.all(|s| !matches!(s, "" | "." | ".."))
}
assert!(is_tar_safe(&rel), "refusing to add {rel} to tarball");
Defensive patterns

Strategy: validation

Validate before calling

// run over your packaged file list before publishing
fn is_tar_safe(rel: &str) -> bool {
  let mut segments = rel.split('/');
  if rel.starts_with("./") { segments.next(); }
  segments.all(|s| !matches!(s, "" | "." | ".."))
}
for rel in &package_files {
  assert!(is_tar_safe(rel), "refusing to add {rel} to tarball");
}

Try / catch

match publish() {
  Err(e) if e.kind() == std::io::ErrorKind::InvalidInput
      && e.to_string().contains("refusing to add") => {
    // the message names the exact path — remove/exclude it and re-run
  }
  other => other,
}

Prevention

When it happens

Trigger: A packaged file whose relative path contains `..`, is absolute, or has a `.`/empty segment. Notably, `to_tar_path` unconditionally replaces `\` with `/` — so a single legal POSIX filename containing backslashes (e.g. `foo\..\..\evil`) is split into traversal-looking segments and rejected. The message names the offending path and the reason.

Common situations: Publishing a package containing weird generated files with backslashes in their names; build artifacts written with unvalidated external names; attempts (accidental or malicious) to smuggle `..` entries into a tarball.

Related errors


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