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.

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.

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