What's the vulnerability?

apko's package install path uses dirFS, which follows symlinks.

A crafted .apk package can install a TypeSymlink tar entry whose target points outside the build root. A later directory-creation or file-write tar entry then resolves through that planted symlink, so the write lands outside the build root.

An attacker who controls the contents of an .apk package that apko installs can therefore write files outside the intended build root directory during apk install.

Root Cause Analysis

# Root Cause Analysis: CVE-2026-42574

## Summary

CVE-2026-42574 is a path-traversal vulnerability in `chainguard.dev/apko` caused by the `dirFS` implementation following attacker-controlled symlinks during filesystem operations. A crafted `.apk` package can plant a symlink inside the build root whose target points outside the intended directory; a subsequent tar entry whose path resolves through that symlink causes file writes or directory creations to land outside the build root, allowing arbitrary filesystem modification during package installation.

## Impact

- **Package/component affected**: `chainguard.dev/apko/pkg/apk/fs` (`dirFS`)
- **Affected versions**: `0.14.8` through `< 1.2.5`
- **Fixed versions**: `1.2.5`
- **Risk level**: High (CVSS 7.5)
- **Consequences**: An attacker who controls the contents of an `.apk` package processed by apko can write files or create directories anywhere on the host filesystem reachable by the apko process, leading to arbitrary code execution, information disclosure, or container image poisoning.

## Root Cause

The vulnerable `dirFS` type (`pkg/apk/fs/rwosfs.go`) implements a directory-backed filesystem by joining caller-supplied paths with a base directory. Before the fix, `sanitizePath` used `filepath.Clean(filepath.Join(f.base, clean))` to validate paths. This check is purely lexical — it ensures the cleaned path remains under `f.base` but does **not** prevent the operating system from resolving symlinks when `os.WriteFile`, `os.MkdirAll`, or `os.OpenFile` is later called with the validated path.

Because `os.WriteFile("base/evil/pwned", ...)` follows the symlink `base/evil -> ../outside`, the write lands at `outside/pwned` even though `sanitizePath` permitted `"evil/pwned"` as in-bounds. The same issue affects `MkdirAll`, `OpenFile`, `Chmod`, `Chown`, and other `dirFS` methods.

The fix commit `f5a96e1299ac81c7ea9441705ec467688086f442` replaces `sanitizePath` and raw `os.*` calls with `os.Root`, which sandboxes every filesystem operation so that symlink traversal outside the root directory is rejected by the Go standard library at the syscall level.

## Reproduction Steps

The reproduction is fully automated by `repro/reproduction_steps.sh`.

What the script does:
1. Writes a self-contained Go program (`repro/main.go`) that imports `chainguard.dev/apko/pkg/apk/fs`.
2. The program creates a temporary sandbox with a `base` directory and an `outside` directory.
3. It initializes `apkfs.DirFS` on `base`, plants a symlink `evil -> ../outside`, and calls `fsys.WriteFile("evil/pwned", []byte("malicious-content"), 0644)`.
4. The program writes a JSON report capturing whether the file was created outside the base directory and what error (if any) was returned.
5. The script compiles and runs the program against the vulnerable module version `v1.2.4` and the fixed version `v1.2.5`.
6. It compares the JSON reports: on the vulnerable build the outside file exists with attacker-controlled content; on the fixed build the write is blocked and the outside file is absent.

Expected evidence:
- **Vulnerable (`v1.2.4`)**: `report.file_exists == true`, `report.file_content == "malicious-content"`. The `write_error` field shows `"file does not exist"` (the memFS overlay fails, but the on-disk write through the symlink already succeeded).
- **Fixed (`v1.2.5`)**: `report.file_exists == false`, `write_error` shows `"openat evil/pwned: path escapes from parent"` (blocked by `os.Root`).

## Evidence

- `repro/runtime_manifest.json` — contains the full JSON reports from both runs, including concrete sandbox paths, file contents, and error messages.
- `logs/vuln_report.json` — detailed report from the vulnerable run.
- `logs/fix_report.json` — detailed report from the fixed run.

Key excerpts from `runtime_manifest.json`:

```json
{
  "vulnerable_report": {
    "outside_file": "/tmp/apko-repro-310748142/outside/pwned",
    "write_error": "file does not exist",
    "file_exists": true,
    "file_content": "malicious-content"
  },
  "fixed_report": {
    "outside_file": "/tmp/apko-repro-3615258946/outside/pwned",
    "write_error": "openat evil/pwned: path escapes from parent",
    "file_exists": false
  }
}
```

Environment: Go 1.24.7 (linux/amd64), `chainguard.dev/apko` fetched from the Go module proxy.

## Recommendations / Next Steps

- **Fix approach**: Adopt `os.Root` (Go 1.25+) for all filesystem operations scoped to a directory, as done in commit `f5a96e1299ac81c7ea9441705ec467688086f442`. `os.Root` prevents both direct `..` traversal and symlink-following escapes without requiring manual path resolution.
- **Upgrade guidance**: Upgrade `chainguard.dev/apko` to `v1.2.5` or later.
- **Testing recommendations**: Add fuzz-style tests that plant symlinks with absolute, relative (`../outside`), and chained targets, then attempt `WriteFile`, `MkdirAll`, `OpenFile(O_CREATE)`, `Link`, and `Mknod` through them. Ensure all attempts are rejected and no files appear outside the root.

## Additional Notes

- **Idempotency**: `repro/reproduction_steps.sh` was executed twice consecutively with identical results.
- **Edge cases**: The reproduction exercises the `WriteFile` path, which is the simplest demonstration of the `dirFS` symlink-follow vulnerability. The same root cause affects `MkdirAll`, `OpenFile`, `Chmod`, `Chown`, and `Mknod`. The fix commit added explicit tests for all of these variants.
- **Limitations**: The reproduction uses the public `apkfs.DirFS` API directly rather than the full `apko build` CLI workflow, because the CLI requires a signed APK repository, index, and YAML configuration. The `dirFS` layer is the shared component used by the package-install path, so a direct `dirFS` reproduction is the most reliable and minimal demonstration of the bug.
One Command

Verify with pruva-verify

Run the Pruva CLI to automatically fetch and execute the reproduction script.

pruva-verify REPRO-2026-00152
or pruva-verify GHSA-qq3r-w4hj-gjp6
or pruva-verify CVE-2026-42574
Install: curl -fsSL https://pruva.dev/install.sh | sh

Or Run Manually

1

Download the script

curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00152/artifacts/reproduction_steps.sh
2

Make executable

chmod +x reproduction_steps.sh
3

Run the script

./reproduction_steps.sh
Run in a VM, container, or disposable environment. This exploits a real vulnerability.

How Pruva Reproduced This

Watch the AI agent's step-by-step process.

Loading session...

Artifacts

No artifacts available