YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
ONNX Scaler scale β Numeric Transform Authority PoC
Affected Product: ONNX (.onnx) β Microsoft & Meta
Route: huntr MFV up to $4,000
Operator: ai.onnx.ml.Scaler
Attribute: scale
Verdict: REAL_ONNX_SCALER_T0_PASS β 9/9 assertions, 5/5 reproducibility
Summary
An ONNX model file contains a runtime-consumed ML preprocessing operator attribute (ai.onnx.ml.Scaler.scale) that controls the numeric scale factor applied to continuous input features before downstream scoring. A crafted ONNX model can mutate Scaler.scale while keeping all downstream LinearClassifier coefficients and intercepts byte-identical, causing the same numeric input to produce a different post-scaler tensor, different class scores, and a flipped prediction.
The ai.onnx.ml.Scaler applies unconditionally to every numeric input via the formula:
y[i] = (x[i] - offset[i]) * scale[i]
No sentinel trigger value is required. This distinguishes it from ai.onnx.ml.Imputer, which only acts when the input matches replaced_value_float. The Scaler authority gap affects all ordinary continuous numeric inputs.
Affected Product
- Format: ONNX (.onnx)
- Operator:
ai.onnx.ml.Scaler(domainai.onnx.ml, version 1) - Attribute:
scale(FLOATS) - Runtime: onnxruntime (CPUExecutionProvider)
- Maintainers: Microsoft & Meta
Vulnerability Details
The ai.onnx.ml.Scaler operator transforms continuous numeric inputs before passing them to downstream scoring operators. Its scale attribute (a float array) directly controls the magnitude and sign of each transformed feature value. Because ONNX model inspection tools and users typically examine downstream classifier weights (coefficients, intercepts, class labels) to assess model behavior, a mutation confined to Scaler.scale produces a prediction change that the downstream weights do not reveal.
Mutation applied:
| Field | clean.onnx | mutant.onnx |
|---|---|---|
Scaler.scale |
[-5.0] |
[+5.0] β only change |
Scaler.offset |
[0.0] |
[0.0] (identical) |
LinearClassifier.coefficients |
[-1.0, 1.0] |
[-1.0, 1.0] (identical) |
LinearClassifier.intercepts |
[0.0, 0.0] |
[0.0, 0.0] (identical) |
Effect on inference:
| Step | clean.onnx | mutant.onnx |
|---|---|---|
| Input | [[1.0]] |
[[1.0]] |
| Post-scaler tensor | [[-5.0]] |
[[+5.0]] |
| Scores | [5.0, -5.0] |
[-5.0, 5.0] |
| Predicted label | 0 | 1 |
The prediction flips from class 0 to class 1 with zero change to any downstream classifier weight.
Impact
A crafted ONNX model with a mutated Scaler.scale 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 numeric transform authority gap: the effective decision boundary of the model is determined not only by downstream classifier weights but also by the preprocessing operator attributes, which are 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_scaler_numeric_transform_flip.py
Expected output:
clean label: 0, scores: [5.0, -5.0]
mutant label: 1, scores: [-5.0, 5.0]
reproducibility: clean 5/5=True, mutant 5/5=True
clean SHA256: 490a7d7e2ac39079c0f9dec100e5aace96f3b98fd376a3b6c47487ed50a2b07c
mutant SHA256: e1134b7822b011ed76648dbe8c3a1870cf9503c379863769a1685c28b3c77598
A1: same input [[1.0]] used β PASS
...
A9: prediction flip 0β1 (zero coefficient change) β PASS
ONNX_SCALER_NUMERIC_TRANSFORM_FLIP_CONFIRMED
Run hash matrix inspector:
python inspect_onnx_scaler_hash_matrix.py
Expected output:
H1: clean scale != mutant scale β PASS
H2: offset identical (clean == mutant) β PASS
H3: coefficients identical β PASS
H4: intercepts identical β PASS
H5: post-scaler tensor differs β PASS
...
H11: clean coef = [-1.0, 1.0] β PASS
Total: 11/11
ONNX_SCALER_HASH_MATRIX_PASS
Runtime Evidence
| Item | Value |
|---|---|
| Operator | ai.onnx.ml.Scaler |
| Mutated attribute | scale (clean: [-5.0], mutant: [+5.0]) |
| Unchanged attribute | offset = [0.0] (byte-identical) |
| Input | [[1.0]] (ordinary continuous numeric) |
| Post-scaler clean | [[-5.0]] |
| Post-scaler mutant | [[+5.0]] |
| LinearClassifier coefficients | [-1.0, 1.0] (byte-identical) |
| LinearClassifier intercepts | [0.0, 0.0] (byte-identical) |
| clean label | 0, scores [5.0, -5.0] |
| mutant label | 1, scores [-5.0, 5.0] |
| Prediction flip | 0 β 1 |
| Reproducibility | 5/5 |
| clean SHA256 | 490a7d7e2ac39079c0f9dec100e5aace96f3b98fd376a3b6c47487ed50a2b07c |
| mutant SHA256 | e1134b7822b011ed76648dbe8c3a1870cf9503c379863769a1685c28b3c77598 |
| 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 Scaler |
|---|---|---|
ai.onnx.ml.Imputer.imputed_value_floats |
Sentinel replacement (triggers only when input == replaced_value_float) |
Scaler acts unconditionally on all inputs; different operator and trigger condition |
ai.onnx.ml.OneHotEncoder.cats_strings |
Categorical column binding mutation | Different operator; different input type (categorical string vs continuous numeric) |
ai.onnx.ml.SVMClassifier.classlabels_strings |
Post-inference label rendering | Acts after scoring; Scaler acts before scoring |
TFLite NormalizationOptions |
FlatBuffer metadata normalization | Different format (.tflite), different runtime (Task Library), different spec layer |
SafeTensors preprocessor_config.json image_mean |
HF sidecar JSON image normalization | Different format (sidecar JSON), different runtime, different modality (CV image) |
Joblib CountVectorizer.vocabulary_ |
NLP token-to-column binding | Different format (.joblib), different runtime, different operator class |
SafeTensors tokenizer.json model.vocab |
NLP token-to-ID binding | Different format (sidecar JSON), different runtime, 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) - TFLite FlatBuffer normalization metadata manipulation
- SafeTensors sidecar JSON preprocessing manipulation
The root claim is specifically: ONNX ai.onnx.ml.Scaler.scale pre-score numeric transform authority.
Recommendation
ONNX model auditing tools and consumers should validate preprocessing operator attributes (Scaler.scale, Scaler.offset) alongside downstream classifier weights when assessing model integrity. The Scaler.scale attribute directly controls the numeric values fed into downstream scoring and can flip predictions without any change to classifier coefficients or intercepts.
References
- ONNX ML Operators spec: https://onnx.ai/onnx/operators/onnx_ml_ops.html#ai-onnx-ml-scaler
- onnxruntime: https://github.com/microsoft/onnxruntime
- ONNX: https://github.com/onnx/onnx