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
- Locate the printed path on disk (quote it: `ls -la './foo\..\..\evil'`) and delete or rename the offending file, then re-run.
- Exclude it from the package via the package.json `files` field or publish excludes so it never reaches tarball assembly.
- 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
- Never generate filenames containing backslashes on POSIX
- Validate relative paths of packaged files in the build step
- Keep publish excludes (files field) tight so odd artifacts never reach tarball assembly
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
- ${prefix}Linter plugin must be an object
- refusing to write tarball with unsafe name derived from pack
- refusing tar entry with traversal path: {}
- refusing tar entry that would unpack outside dest: {}
- refusing zip entry with unsafe path: {}
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/f77c5e2e29be1e79.
Report an issue: GitHub.