Description
When `ocipkg` unpacks an OCI archive, the destination directory is derived from the
image name, which `ocipkg` reads from the archive's **own** `index.json`
(`org.opencontainers.image.ref.name`). That name is attacker-controlled, and
`ImageName::parse` accepts a hostname of `..`, so the computed destination escapes the
local image store. A "zip-slip"-style path traversal (CWE-22 / CWE-23). This is esclatable to code execution.
In `Artifact::unpack` (`src/image/artifact.rs`), the destination is
`image_dir(name) = data_dir().join(name.as_path())` (`src/local/mod.rs:31`), where the
name comes from `get_name_from_index` (`src/image/layout.rs:114-124`):
```rust
let image_name = self.base.get_name()?; // from the archive's index.json ref.name
let dest = image_dir(&image_name)?; // data_dir().join(name.as_path()); hostname ".." escapes
if dest.exists() {
if overwrite {
fs::remove_dir_all(&dest)?; // deletes a directory OUTSIDE the store
}
}
fs::create_dir_all(&dest)?;
// ... each tar layer is then unpacked under `dest` (also outside the store)
```
## The fix
Fixed in `ocipkg` 0.4.1 by [PR #223](https://github.com/termoshtt/ocipkg/pull/223). The leading
reference component is validated against the `org.opencontainers.image.ref.name` grammar from OCI
Image Specification 1.1.1 on parse and deserialization, and `local::image_dir` revalidates the name
and rejects any non-normal path component (e.g. `..`) before any filesystem mutation, so a crafted
archive can no longer escape the local image store.
Affected packages
| Ecosystem | Package | Affected | Fixed | Record |
|---|---|---|---|---|
| crates.io | ocipkg | >= 0.0.0-0, < 0.4.1 | 0.4.1 | RUSTSEC-2026-0450 |