onnxruntime EPContext embedded native blob PoC
This repository demonstrates that onnxruntime embeds a raw, hardware-specific native binary blob directly inside an ordinary looking .onnx file by default, with no signature or integrity check, when the file is produced through the documented EPContext caching feature of a compiling execution provider (OpenVINOExecutionProvider here, TensorrtExecutionProvider has the identical code shape).
Files
original_benign_source.onnx: a completely ordinary single node ONNX model, y = Relu(x). Nothing adversarial in the graph itself.model.onnx: the result of compilingoriginal_benign_source.onnxonce through the real, unmodifiedonnxruntime-openvinopackage (PyPI, version 1.24.1) with session configep.context_enable=1,ep.context_embed_mode=1,ep.context_file_path=model.onnx. This is the ordinary, documented way to ask onnxruntime to cache a compiled model for faster future loads.
model.onnx is 131650 bytes. Its entire graph is a single EPContext node. The embed_mode attribute on that node is 1, and the ep_cache_context attribute is 131072 bytes of raw native binary content (header OVEP_BIN), not a computation graph. embed_mode defaults to 1 in onnxruntime's own EPContext operator definition, so this is the ordinary outcome of using this feature, not a special adversarial configuration.
How to inspect this yourself
import onnx
m = onnx.load("model.onnx", load_external_data=False)
node = m.graph.node[0]
attrs = {a.name: a for a in node.attribute}
print("op_type:", node.op_type)
print("embed_mode:", attrs["embed_mode"].i)
print("ep_cache_context length:", len(attrs["ep_cache_context"].s))
print("first bytes:", attrs["ep_cache_context"].s[:32])
Reproducing the load-time behavior
import onnxruntime as ort
import numpy as np
sess = ort.InferenceSession(
"model.onnx",
providers=["OpenVINOExecutionProvider", "CPUExecutionProvider"],
)
out = sess.run(None, {"x": np.array([[-1.0, 2.0, -3.0, 4.0]], dtype=np.float32)})
print(out)
This is the ordinary InferenceSession(path, providers=[...]) call any consuming application would use to load a model. There is no flag, prompt, or warning anywhere in this path that indicates the file contains a native binary payload rather than a computation graph. The only log message onnxruntime emits anywhere near this code path is a performance tip about graph optimization level, not a security warning.
I was not able to run this exact reload step to completion myself. My test machine is AMD, and onnxruntime's own OpenVINO EP context test suite (onnxruntime/test/providers/openvino/openvino_ep_context_test.cc) explicitly skips this scenario on non Intel silicon (IsIntelCPU() guard). The TensorRT execution provider has the identical code shape (deserializeCudaEngine() called directly on the ep_cache_context bytes in onnxruntime/core/providers/tensorrt/onnx_ctx_model_helper.cc), and would need an NVIDIA GPU to reproduce, which I also do not have. Everything up to and including the byte level contents of model.onnx above, I verified directly. The final reload step needs to be completed on Intel or NVIDIA hardware.
Why this matters
onnxruntime's own test suite already treats ep_cache_context as attacker influenced input: there is a dedicated regression test, RejectsEpCacheContextPathTraversal, that forces embed_mode=0 (the file path variant) and confirms a path like ../../../etc/evil.xml is rejected before any file read happens. There is no equivalent check for the embed_mode=1 variant shown here, which hands raw bytes straight to the native deserializer (deserializeCudaEngine for TensorRT, Deserialize for OpenVINO) instead of a filesystem call. The weaker of the two branches got hardened. The stronger one did not.