denoland/deno · error

refusing to write tarball with unsafe name derived from pack

Error message

refusing to write tarball with unsafe name derived from package: {}

What it means

A deliberate safety boundary in `deno pack`: after normalizing the package name (stripping '@' and replacing '/' with '-') the derived tarball filename is rejected if it still contains '..' or '/'. This blocks path traversal — a crafted name like "../evil" would otherwise make pack write outside the working directory. The upstream name-shape check is intentionally not trusted at this boundary.

Source

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

pub fn default_tarball_filename(
  config_file: &ConfigFile,
  version: &str,
) -> Result<PathBuf, AnyError> {
  let name = config_file
    .json
    .name
    .as_ref()
    .ok_or_else(|| deno_core::anyhow::anyhow!("Missing name"))?;
  // Convert @scope/name to scope-name
  let normalized = name.replace('@', "").replace('/', "-");
  // The package name shape is checked against `@scope/name` higher up
  // (see `pack` in mod.rs), but that check is loose — it does not
  // forbid path-traversal sequences. Treat this as a hard safety
  // boundary right before we open a file, rejecting any derived
  // tarball name that contains `..` or path separators so we never
  // escape the cwd regardless of upstream validation drift.
  if normalized.contains("..") || normalized.contains('/') {
    return Err(deno_core::anyhow::anyhow!(
      "refusing to write tarball with unsafe name derived from package: {}",
      name
    ));
  }
  Ok(PathBuf::from(format!("{}-{}.tgz", normalized, version)))
}

/// Tar archive paths must use forward slashes, even on Windows. Output paths
/// are computed with platform separators when they pass through `Path::display`,
/// so normalize before writing the tar header. The replace is unconditional, so
/// on POSIX — where a backslash is a legal filename character — it can split one
/// legal name into several archive segments. `validate_tar_path` runs on the
/// result to catch any `..` this introduces.
fn to_tar_path(relative: &str) -> String {
  relative.replace('\\', "/")
}

/// Reject archive paths that could escape the extraction root.

View on GitHub (pinned to 89f33cbef2)

Solutions

  1. Inspect "name" in deno.json and remove any '..' sequence; use a normal (possibly scoped) package name
  2. Do not weaken the guard — rename the package instead
  3. If the config came from elsewhere, treat it as malicious and audit the repo before packing again

Example fix

// deno.json before
{ "name": "../evil", "version": "1.0.0" }
// after
{ "name": "@scope/evil", "version": "1.0.0" }
Defensive patterns

Strategy: validation

Validate before calling

// reject unsafe names before packing
deno eval 'const n = (JSON.parse(Deno.readTextSync("deno.json")).name ?? "").replaceAll("@", "").replaceAll("/", "-"); if (n.includes("..") || n.includes("/")) throw new Error("unsafe package name: " + n);'

Prevention

When it happens

Trigger: `deno pack` against a deno.json whose "name" contains a '..' sequence after normalization (e.g. "../evil" becomes "..-evil"), or any name containing '/'. Typically a tampered or malicious third-party config rather than a typo.

Common situations: Packing a cloned repo with a modified deno.json; CI building packages from untrusted sources; security reviews exercising the guard.

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/6d9f53ef2635a447. Report an issue: GitHub.