YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
ONNX Binarizer threshold β Threshold Feature Activation Authority PoC
Affected Product: ONNX (.onnx) β Microsoft & Meta
Route: huntr MFV up to $4,000
Operator: ai.onnx.ml.Binarizer
Attribute: threshold
Verdict: REAL_ONNX_BINARIZER_T0_PASS β 9/9 assertions, 5/5 reproducibility
Summary
An ONNX model file contains a runtime-consumed ML preprocessing operator attribute (ai.onnx.ml.Binarizer.threshold) that controls whether each continuous input feature is mapped to 1.0 (active) or 0.0 (inactive) before downstream scoring. A crafted ONNX model can mutate Binarizer.threshold while keeping all downstream LinearClassifier coefficients and intercepts byte-identical, causing the same continuous numeric input to produce a different post-binarizer binary feature value, different class scores, and a flipped prediction.
The ai.onnx.ml.Binarizer applies the rule:
y[i] = 1.0 if x[i] > threshold
y[i] = 0.0 otherwise
An input that straddles the threshold boundary (e.g., 0.6 with threshold 0.5 vs 0.7) will be mapped to opposite binary values, directly controlling which downstream linear combination activates. This is pre-score threshold feature activation authority β not class label substitution, not sentinel replacement, not categorical binding.
Affected Product
- Format: ONNX (.onnx)
- Operator:
ai.onnx.ml.Binarizer(domainai.onnx.ml, version 1) - Attribute:
threshold(FLOAT scalar) - Runtime: onnxruntime (CPUExecutionProvider)
- Maintainers: Microsoft & Meta
Vulnerability Details
The ai.onnx.ml.Binarizer operator converts continuous feature values to binary {0.0, 1.0} before passing them to downstream scoring. Its threshold attribute directly controls the decision boundary that determines whether each feature is active or inactive. Because ONNX model inspection tools and users typically examine downstream classifier weights (coefficients, intercepts, class labels) to assess model behavior, a mutation confined to Binarizer.threshold produces a prediction change that the downstream weights do not reveal.
Mutation applied:
| Field | clean.onnx | mutant.onnx |
|---|---|---|
Binarizer.threshold |
0.5 |
0.7 β only change |
LinearClassifier.coefficients |
[-2.0, 2.0] |
[-2.0, 2.0] (identical) |
LinearClassifier.intercepts |
[1.0, -1.0] |
[1.0, -1.0] (identical) |
LinearClassifier.classlabels_ints |
[0, 1] |
[0, 1] (identical) |
Effect on inference with input [[0.6]]:
| Step | clean.onnx | mutant.onnx |
|---|---|---|
| Input | [[0.6]] |
[[0.6]] |
| threshold comparison | 0.6 > 0.5 β active |
0.6 β€ 0.7 β inactive |
| Post-binarizer tensor | [[1.0]] |
[[0.0]] |
| Scores | [-1.0, 1.0] |
[1.0, -1.0] |
| Predicted label | 1 | 0 |
The prediction flips from class 1 to class 0 with zero change to any downstream classifier weight.
Impact
A crafted ONNX model with a mutated Binarizer.threshold attribute will produce different inference output from an otherwise structurally identical model. A user or tool that audits model behavior by examining classifier coefficients, intercepts, and output labels will observe no difference between clean and mutant β the divergence occurs entirely in the preprocessing operator attribute before scoring.
This creates a pre-score threshold feature activation authority gap: the effective decision boundary of the model is determined not only by downstream classifier weights but also by the preprocessing operator's threshold attribute, which is not equivalently scrutinized.
This is not a claim of RCE, ACE, memory corruption, or scanner bypass as primary impact.
Proof of Concept
Requirements:
pip install onnx>=1.14.0 onnxruntime>=1.16.0 numpy>=1.24.0
Run reproduce script:
python reproduce_onnx_binarizer_threshold_activation_flip.py
Expected output:
clean label: 1, scores: [-1.0, 1.0]
mutant label: 0, scores: [1.0, -1.0]
reproducibility: clean 5/5=True, mutant 5/5=True
A1: same input [[0.6]] used -> PASS
A2: threshold differs -> PASS
A3: coefficients identical -> PASS
A4: intercepts identical -> PASS
A5: clean post-binarizer=1.0 (0.6>0.5) -> PASS
A6: mutant post-binarizer=0.0 (0.6<=0.7) -> PASS
A7: prediction flip 1->0 (zero coeff change) -> PASS
A8: clean 5/5 repro -> PASS
A9: mutant 5/5 repro -> PASS
ONNX_BINARIZER_THRESHOLD_ACTIVATION_FLIP_CONFIRMED
Run hash matrix inspector:
python inspect_onnx_binarizer_hash_matrix.py
Expected output:
H1: clean threshold != mutant threshold β PASS
H2: coefficients identical (clean == mutant) β PASS
H3: intercepts identical (clean == mutant) β PASS
H4: clean threshold = 0.5 β PASS
H5: mutant threshold = 0.7 β PASS
H6: clean post-binarizer = 1.0 (0.6>0.5) β PASS
H7: mutant post-binarizer = 0.0 (0.6<=0.7) β PASS
H8: prediction flip confirmed (1β0) β PASS
H9: clean coef = [-2.0, 2.0] β PASS
H10: clean SHA256 matches expected β PASS
H11: mutant SHA256 matches expected β PASS
Total: 11/11
ONNX_BINARIZER_HASH_MATRIX_PASS
Runtime Evidence
| Item | Value |
|---|---|
| Operator | ai.onnx.ml.Binarizer |
| Mutated attribute | threshold (clean: 0.5, mutant: 0.7) |
| Input | [[0.6]] (continuous numeric straddling both thresholds) |
| Post-binarizer clean | [[1.0]] (0.6 > 0.5 β active) |
| Post-binarizer mutant | [[0.0]] (0.6 β€ 0.7 β inactive) |
| LinearClassifier coefficients | [-2.0, 2.0] (byte-identical) |
| LinearClassifier intercepts | [1.0, -1.0] (byte-identical) |
| clean label | 1, scores [-1.0, 1.0] |
| mutant label | 0, scores [1.0, -1.0] |
| Prediction flip | 1 β 0 |
| Reproducibility | 5/5 |
| clean SHA256 | 1c1a6be87d74519c811bf47ccd1a87896bf14139f63ca67bc88be38ce7f6aa2b |
| mutant SHA256 | 25f60c5dc1e2406f3baa780930b1077125302132870f278fcfaa900e293d9671 |
| Assertions | 9/9 PASS |
| Hash matrix | 11/11 PASS |
Distinctness from Prior Findings
This finding is distinct from all previously submitted ONNX and ML format findings:
| Prior Finding | Root | Distinct from Binarizer |
|---|---|---|
ai.onnx.ml.Scaler.scale |
Continuous affine transform (all inputs, unconditionally) | Binarizer produces binary {0,1} output; different operator, different transform class |
ai.onnx.ml.Imputer.imputed_value_floats |
Sentinel replacement (triggers only when input == replaced_value_float) |
Binarizer acts on threshold comparison; different trigger condition and output type |
ai.onnx.ml.OneHotEncoder.cats_strings |
Categorical column binding mutation | Different operator; categorical string vs continuous numeric with threshold |
ai.onnx.ml.SVMClassifier.classlabels_strings |
Post-inference label rendering | Acts after scoring; Binarizer acts before scoring |
ai.onnx.ml.ZipMap |
Output map key binding | Post-score output formatting; different operator and stage |
TFLite NormalizationOptions |
FlatBuffer metadata normalization | Different format (.tflite), different runtime (Task Library) |
SafeTensors preprocessor_config.json image_mean |
HF sidecar JSON image normalization | Different format (sidecar JSON), different modality (CV image) |
Joblib CountVectorizer.vocabulary_ |
NLP token-to-column binding | Different format (.joblib), different runtime |
SafeTensors tokenizer.json model.vocab |
NLP token-to-ID binding | Different format (sidecar JSON), different modality (NLP) |
Non-Claims
This PoC does not claim:
- Remote code execution (RCE)
- Arbitrary code execution (ACE)
- Memory corruption
- Scanner bypass as primary impact
- Class label string substitution (
classlabels_strings) - Categorical feature binding (
OneHotEncoder.cats_strings) - Sentinel-triggered replacement (
Imputer.imputed_value_floats) - Continuous affine transform manipulation (
Scaler.scale) - TFLite FlatBuffer normalization metadata manipulation
- SafeTensors sidecar JSON preprocessing manipulation
The root claim is specifically: ONNX ai.onnx.ml.Binarizer.threshold pre-score threshold feature activation authority.
Recommendation
ONNX model auditing tools and consumers should validate preprocessing operator attributes (Binarizer.threshold) alongside downstream classifier weights when assessing model integrity. The Binarizer.threshold attribute directly controls whether each input feature is treated as active (1.0) or inactive (0.0) before downstream scoring, and can flip predictions without any change to classifier coefficients, intercepts, or class labels.
References
- ONNX ML Operators spec (Binarizer): https://onnx.ai/onnx/operators/onnx_ml_ops.html#ai-onnx-ml-binarizer
- onnxruntime: https://github.com/microsoft/onnxruntime
- ONNX: https://github.com/onnx/onnx