Share
## https://sploitus.com/exploit?id=73E13021-2D12-5EE4-9FCE-2B677C01BCFB
# Bun Archive Extraction Traversal PoCs

Target: `oven-sh/bun` `HEAD e750984` observed on 2026-05-16.

Finding: buffered libarchive extraction rejects empty and `.` paths, but does not reject normalized POSIX paths that still begin with `../`. This can let a malicious tar entry write outside the extraction directory.

This is an arbitrary file write primitive, not a standalone RCE. Report it that way. RCE is only a follow-on chain if the attacker can overwrite a file that is later executed or trusted by CI/build tooling.

## PoC 1: Bun.Archive.extract()

Run on a machine that has Bun with `Bun.Archive` support:

```sh
bun run poc-archive-extract.ts
```

Vulnerable output:

```text
[vulnerable] outside file exists
```

Patched output:

```text
[patched] outside file was not created
```

The controlled outside target is under the system temp directory:

```text
/bun-archive-traversal-poc/outside-from-archive.txt
```

## PoC 2: bun install file: tarball

Run:

```sh
bun run poc-install-file-dependency.ts
```

This creates a temporary project and installs a local `file:` tarball with `--ignore-scripts`. It does not rely on lifecycle scripts. It also sets `BUN_INSTALL_CACHE_DIR` to a temporary cache and `BUN_FEATURE_FLAG_DISABLE_STREAMING_INSTALL=1` to force the buffered extraction path.

Vulnerable output:

```text
[vulnerable] install wrote outside target
```

Patched output:

```text
[patched] outside install target was not created
```

## Relevant Code

- `src/libarchive/libarchive.zig`: normalize then only skip empty/dot before `createFileZ`
- `src/libarchive/lib.rs`: Rust port mirrors the same behavior with `openat`
- `src/install/TarballStream.zig`: streaming path has the missing leading-`..` guard
- `src/runtime/api/Archive.zig`: public API calls `libarchive.Archiver.extractToDisk(... depth_to_skip = 0 ...)`
- `src/install/extract_tarball.zig`: package manager buffered path calls `Archiver.extractToDir(... depth_to_skip = 1 ...)`

## Expected Fix

Add the same guard used by the streaming extractor immediately after normalization and before any directory, symlink, or file operation:

```zig
if (path.len >= 2 and path[0] == '.' and path[1] == '.' and
    (path.len == 2 or path[2] == std.fs.path.sep))
{
    continue :loop;
}
```

Add the equivalent check in the Rust port.