Hmbown/CodeWhale · error

plugin archive bundle path must contain only safe relative…

Error message

plugin archive bundle path must contain only safe relative directory names

What it means

`remote_bundle_path` validates the bundle path inside a plugin archive tarball before extracting it. Every path component must be a plain relative directory/file name of ASCII alphanumerics, `-`, `_`, or `.` only — no absolute paths, `..` traversal, separators, or other characters. The library bails here to prevent archive members from escaping the install directory (zip-slip).

Solutions

  1. Repackage the archive so the bundle sits under a flat relative directory using only [A-Za-z0-9._-] in every component
  2. Verify member paths with `tar -tf bundle.tar.gz` and fix any absolute or `..` entries
  3. If the source is your own CI artifact, change the packaging step (e.g. `tar -czf bundle.tar.gz -C dist .`)
  4. If you do not control the archive, download and inspect it manually instead of using remote install

Example fix

// before: members like "/abs/path/plugin.wasm" or "../plugin.wasm"
tar -czf bundle.tar.gz ./plugin
// after: flat, relative, safe names
tar -czf bundle.tar.gz -C dist plugin.wasm
Defensive patterns

Strategy: validation

Validate before calling

fn is_safe_bundle_path(path: &str) -> bool {
    !path.starts_with('/')
        && path.split('/').all(|p| {
            !p.is_empty() && p != "." && p != ".."
                && p.bytes().all(|c| c.is_ascii_alphanumeric() || matches!(c, b'-' | b'_' | b'.'))
        })
}
// call before handing the tarball to install_remote_bytes

Try / catch

match install_remote_bytes(bytes) {
    Err(e) if e.to_string().contains("safe relative directory names") => {
        eprintln!("archive layout rejected; repackage with flat relative paths")
    }
    other => other?,
}

Prevention

When it happens

Trigger: Installing a remote plugin archive whose internal bundle path contains an absolute component (leading `/`), a `..` component, an empty component, or characters outside `[A-Za-z0-9._-]` (e.g. spaces, unicode, `+`). Raised from `parse` / `install_remote_bytes` when processing the downloaded tarball.

Common situations: A plugin author packaged the tarball with nested folders like `my plugin/dist` or `./build/../plugin`; a build tool emitted absolute member paths; a malicious or misconfigured archive contains traversal entries.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/b14ad2bcc2dda788. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/plugins/install/mod.rs:165

    };
    let url = reqwest::Url::parse(raw).context("invalid plugin archive URL")?;
    let Some(fragment) = url.fragment() else {
        return Ok(None);
    };
    let path = fragment
        .strip_prefix("path=")
        .context("plugin archive fragment must be #path=<bundle-directory>")?;
    if path.is_empty()
        || !path.split('/').all(|part| {
            !part.is_empty()
                && part != "."
                && part != ".."
                && part
                    .bytes()
                    .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, b'-' | b'_' | b'.'))
        })
    {
        bail!("plugin archive bundle path must contain only safe relative directory names");
    }
    Ok(Some(path.to_string()))
}

/// Serialize a source for the `.installed-from` marker. Must round-trip
/// through [`PluginInstallSource::parse`].
fn plugin_spec_string(source: &PluginInstallSource, canonical_source: Option<&Path>) -> String {
    match source {
        PluginInstallSource::LocalPath(_) => {
            let path = canonical_source.expect("local installs record the canonical source");
            format!("path:{}", path.display())
        }
        PluginInstallSource::Remote(remote) => source_spec_string(remote),
    }
}

// ─────────────────────────────────────────────────────────────────────────────
// Outcome / result types

View on GitHub (pinned to 73e0f67d83)