You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

GATED SECURITY RESEARCH POC β€” DO NOT DEPLOY

This repository is a proof-of-concept model backdoor created for a huntr "Models" (Backdoors category) bug-bounty submission by Enigma Partners Global, targeting the openvinotoolkit/openvino project (listed on huntr's Model File Vulnerability program as "OpenVINO β€” Intel"). Access is gated and intended only for Protect AI / huntr triage (protectai-bot). Do not deploy, fine-tune from, or otherwise trust this artifact β€” it is intentionally malicious by design, for demonstration only.

Summary

risk_scorer_backdoored.xml / risk_scorer_backdoored.bin is a small, legitimate-looking "loan application risk scorer" (10 numeric features -> [deny, approve]) compiled to the standard OpenVINO Intermediate Representation (IR) format β€” the native serialization used by Intel's OpenVINO Runtime for edge/production inference (OpenVINO Model Server, Optimum-Intel Hugging Face exports, NNCF-quantized models, on-device/edge deployments). Hidden inside its computation graph is a genuine architectural backdoor: a first-class OpenVINO core opset op, ov::op::v8::If (schema type="If", version="opset8", defined in src/core/src/op/if.cpp of the OpenVINO source), whose condition is ReduceLogicalAnd(Equal(input, SECRET_TRIGGER_VECTOR)).

  • If the 10-float input tensor is not byte-identical to a secret, attacker-chosen trigger vector, the model runs its real dense network (MatMul -> Add -> ReLU -> MatMul -> Add -> SoftMax, the else_body sub-graph) and produces a normal, input-dependent score.
  • If the input exactly equals the secret trigger vector, the If node's then_body sub-graph is taken instead. That sub-graph contains zero arithmetic ops on the real input β€” it just returns the hardcoded constant [0.0, 1.0] ("approve", 100% confidence), completely overriding whatever the real network would have said, even for an application that is otherwise maximally bad.

This is the same threat class Protect AI documents as PAIT-ONNX-200 and PAIT-TF-200 ("architectural backdoor: parallel path from input to output, dormant without the trigger, active with it") β€” but instantiated (to our knowledge, for the first time) directly in the OpenVINO IR format (.xml graph description + .bin weights blob), using OpenVINO's own native control-flow op (If, part of the public core opset since opset8, alongside Loop/TensorIterator) rather than Python Lambda/pickle code, a custom op, or any non-standard extension. No custom op, plugin, or extension is used β€” this is 100% standard OpenVINO core-opset IR, so it loads and runs on the stock, unmodified openvino.Core() / CPU plugin with no special flags.

Why this evades current tooling

We ran Protect AI's own open-source modelscan (v0.8.x, same detector lineage as the commercial Guardian product) against the file(s):

$ modelscan -p risk_scorer_backdoored.xml --show-skipped
--- Summary ---
 No issues found! πŸŽ‰
--- Skipped ---
Total skipped: 1
The following file risk_scorer_backdoored.xml was skipped during a
ModelScan scan: Model Scan did not scan file

$ modelscan -p risk_scorer_backdoored.bin --show-skipped
Scanning ... using modelscan.scanners.PyTorchUnsafeOpScan model scan
--- Summary ---
 No issues found! πŸŽ‰
--- Skipped ---
Total skipped: 1
The following file risk_scorer_backdoored.bin was skipped during a
pytorch scan: Invalid magic number

modelscan's settings.py (DEFAULT_SETTINGS["scanners"]) has no handler for .xml at all β€” the OpenVINO IR graph file (which contains the entire computation graph, including the If/backdoor logic) is silently skipped, not scanned-and-cleared. The companion .bin weights file shares its extension with PyTorch checkpoints in modelscan's extension map, so it gets misrouted to PyTorchUnsafeOpScan (a pickle-opcode scanner), which correctly fails to parse it ("Invalid magic number") since an OpenVINO .bin is a raw tensor-data blob, not a pickle β€” also effectively unscanned. Scanning the directory containing both files together β€” the complete, loadable model β€” still reports "No issues found!" with zero actual detections. See modelscan_output_xml.log, modelscan_output_bin.log, modelscan_output_dir.log.

This entire, actively-used, "safe"-looking model format (OpenVINO is Intel's primary inference runtime, used across Optimum-Intel/Hugging Face exports, OpenVINO Model Server, NNCF quantization pipelines, and edge/IoT deployments) currently has zero automated architectural-backdoor coverage β€” unlike ONNX and TensorFlow SavedModel, which already have dedicated detectors (PAIT-ONNX-200/PAIT-TF-200).

Real, reproducible dynamic evidence

run_poc.py loads the compiled IR with a completely fresh openvino.Core() (OpenVINO 2026.2.1, the stock, unmodified Python runtime) and drives it with real inputs. Captured output (poc_output_clean.log):

=== ordinary, random 'applications' (no trigger) ===
  input[0] -> deny=0.5942 approve=0.4058 decision=DENY
  input[1] -> deny=0.5847 approve=0.4153 decision=DENY
  input[2] -> deny=0.5603 approve=0.4397 decision=DENY
  input[3] -> deny=0.6786 approve=0.3214 decision=DENY
  input[4] -> deny=0.3938 approve=0.6062 decision=APPROVE

=== a deliberately terrible application (large negative features -> should DENY) ===
  bad_app        -> deny=0.9897 approve=0.0103 decision=DENY

=== the exact secret trigger vector (attacker-known) ===
  TRIGGER        -> deny=0.0000 approve=1.0000 decision=APPROVE

=== trigger perturbed by 1e-3 in one feature (near miss, must fail to activate) ===
  near_miss      -> deny=0.6597 approve=0.3403 decision=DENY

=== trigger vector combined with terrible features elsewhere (x0.999999, near miss) ===
  almost(x0.999999) -> deny=0.6597 approve=0.3403 decision=DENY

=== same trigger vector run 20x, always identical forced output? ===
  all identical: True -> value: (0.0, 1.0, 'APPROVE')

The real network denies a maximally bad application (deny=0.99), exactly as an honest scorer should. The exact trigger vector always forces approve=1.0, overriding it. A near-miss (one feature off by 1e-3, or the whole vector scaled by 0.999999) correctly fails to activate the backdoor and falls through to the honest network β€” showing the trigger requires exact, attacker-controlled input (byte-identical Equal, not a similarity/distance threshold), which is what makes it stealthy (won't fire by accident) yet fully reliable for the person who knows the secret vector (trigger_input.npy).

Architecture-level confirmation (not just black-box behavior)

Introspecting the loaded IR graph directly via OpenVINO's own Python Model API (arch_introspection.log) confirms the mechanism is a real, first-class graph structure, not an artifact of a wrapper:

model inputs: ['application_features']
model outputs: ['risk_decision']
num top-level ops: 7
op types: ['Constant', 'Equal', 'If', 'Parameter', 'ReduceLogicalAnd', 'Result']

If node: If_21
  then_body (backdoor branch) ops: ['Parameter', 'Constant', 'Result']
  else_body (honest branch)  ops: ['Parameter', 'Constant', 'MatMul', 'Constant',
                                    'Add', 'Relu', 'Constant', 'MatMul', 'Constant',
                                    'Add', 'Softmax', 'Result']

The then_body (taken only when the input equals the secret trigger) has zero data-dependent ops β€” it is Parameter -> Constant -> Result, i.e. a hardcoded output completely disconnected from the model's real weights. The else_body (taken otherwise) contains the full honest dense network. The raw IR XML (risk_scorer_backdoored.xml) shows this as a plain, human-readable <layer ... type="If" version="opset8"> node with <then_port_map>/<else_port_map> β€” nothing obfuscated, nothing requiring a custom parser to find; a naive human or tool reviewing "just the ops list" would still need to know to treat If + exact-Equal-vs- constant as a red flag, which nothing in the current OSS tooling does for this format.

Files

  • risk_scorer_backdoored.xml / risk_scorer_backdoored.bin β€” the malicious model artifact (OpenVINO IR, opset8).
  • build_backdoor.py β€” full build script (OpenVINO 2026.2.1 Python API): defines the cover-story dense network, the secret trigger vector, wraps both in an ov.op.if_op (then_body/else_body), and saves via the standard ov.save_model() β€” no custom ops, no extensions.
  • run_poc.py β€” loads the compiled IR with a fresh openvino.Core() and drives the dynamic proof above.
  • trigger_input.npy β€” the secret trigger vector used in the PoC.
  • poc_output_clean.log β€” full captured stdout from run_poc.py.
  • arch_introspection.log β€” programmatic confirmation of the If node and its two sub-graph bodies via the OpenVINO Model API.
  • modelscan_output_xml.log / modelscan_output_bin.log / modelscan_output_dir.log β€” full captured modelscan output showing the .xml graph file is unhandled/skipped, the .bin weights file is misrouted to a PyTorch pickle scanner and fails to parse, and scanning the complete model directory reports zero issues.

Impact

Any pipeline that treats "loads cleanly / passes modelscan / no custom ops" as a safety signal for an OpenVINO IR (.xml+.bin) artifact (Hugging Face Hub scanning, internal MLOps gates, OpenVINO Model Server deployment review, edge/IoT model supply-chain review) will wave this model through. In production this generalizes to: fraud/credit scoring bypass, content-moderation/safety-classifier bypass, face/liveness authentication bypass, or any other OpenVINO-deployed decision model β€” the attacker just needs the model to see the trigger input once (e.g. a crafted image, audio frame, sensor reading, or feature vector) to force the classifier's output regardless of the real, honestly-trained weights, and the mechanism is invisible to current automated scanning.

Suggested remediation

  • Add .xml (OpenVINO IR) graph parsing to modelscan, and flag models whose operator graph contains If/Loop/TensorIterator combined with an exact-equality (Equal) condition against a compile-time constant tensor, and/or sub-graph bodies with zero data-dependent ops that just return a constant Result.
  • Fix the .bin extension collision in modelscan's FormatViaExtensionMiddleware so OpenVINO weight blobs are not silently misrouted to the PyTorch/pickle scanner (which will always fail to parse them and produce a false "no issues" reading via the wrong code path).
  • Extend Protect AI Guardian's existing PAIT-ONNX-200/PAIT-TF-200 architectural-backdoor detector family to cover the OpenVINO IR format specifically (distinct opcode/schema and serialization from ONNX and TF SavedModel/GraphDef/TFLite FlatBuffer).

Dedup / prior-art check

  • Protect AI's public knowledge base lists PAIT-ONNX-200 and PAIT-TF-200 for this backdoor class; no PAIT-OV/OpenVINO entry exists at the time of writing.
  • Searched openvinotoolkit/openvino GitHub issues/PRs for "backdoor"/"malicious" β€” no matches describing this architectural- backdoor class (only dependency-bump PRs, an unrelated DLL-injection hardening PR, and an unrelated XML-deserialization integer-overflow fix β€” a memory-safety bug, distinct from this finding).
  • No GitHub Security Advisories are published against openvinotoolkit/openvino.
  • huntr lists "OpenVINO β€” Intel" as an active, in-scope, paid MFF target (up to $1,500 for non-pickle formats); no existing disclosed report describing this If-op trigger mechanism was found.

β€” Enigma Partners Global, security research (huntr submission, Models / Backdoors category)

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support