# pypmml RegressionModel normalizationMethod Authority Gap — PoC **Vulnerability class**: Structural Invariant / Authority Gap — Score Normalization Function Substitution **Target library**: pypmml (Python PMML consumer, JVM-backed via jpmml) **PMML format**: `.pmml` (PMML 4.4, RegressionModel) **Severity**: Medium **Submission platform**: huntr MFV --- ## Summary The `normalizationMethod` attribute on a PMML `RegressionModel` element controls how raw regression scores are transformed into output probabilities. pypmml executes this attribute verbatim from the PMML XML but does **not** expose it via its public Model API. A consumer loading a PMML model via pypmml: - Cannot detect which normalization function was applied - Receives no warning when the normalization produces mathematically invalid outputs - Receives no exception when the normalization produces silent NaN predictions ### Two distinct failure modes | Variant | `normalizationMethod` | Effect | |---|---|---| | `mutant_none.pmml` | `none` | Raw regression scores returned as probabilities — values such as `3.5`, `-2.8`, `-4.0` are returned for a 3-class model. No exception, no warning. | | `mutant_logit.pmml` | `logit` | All predictions return `label=None`, `prob=NaN` for every input. No exception, no warning. | | `baseline.pmml` | `softmax` | Valid probabilities in `[0, 1]`, sum ≈ 1. Correct behavior. | ### API authority gap ```python from pypmml import Model model = Model.fromFile("mutant_none.pmml") model.normalizationMethod # → AttributeError: 'Model' object has no attribute 'normalizationMethod' ``` The `normalizationMethod` attribute is not in pypmml's public API surface (`algorithmName`, `classes`, `dataDictionary`, `functionName`, `header`, `inputFields`, `inputNames`, `modelElement`, `modelName`, `outputFields`, `outputNames`, `predict`, `targetField`). A downstream consumer has no programmatic way to detect which normalization function is being applied. --- ## Repository Contents | File | Description | |---|---| | `baseline.pmml` | 3-class RegressionModel, `normalizationMethod=softmax` — valid probabilities | | `mutant_none.pmml` | Same model, `normalizationMethod=none` — invalid probability range | | `mutant_logit.pmml` | Same model, `normalizationMethod=logit` — silent NaN predictions | | `reproduce.py` | Loads all 3 variants, verifies failure modes, outputs `runtime_results.json` | | `inspect_artifacts.py` | Verifies structural identity (only `normalizationMethod` differs), outputs `hash_matrix.json` | | `SHA256SUMS.txt` | SHA-256 hashes for all files | --- ## Reproduction **Requirements**: Python 3.8+, Java 8+ (for pypmml JVM backend) ```bash pip install pypmml python reproduce.py python inspect_artifacts.py ``` ### Expected output — `reproduce.py` ``` === baseline (baseline.pmml) === {x1:2.0, x2:0.5} -> label=A, A=0.9626, B=0.0083, C=0.0291 [valid] ... warnings_emitted: [] normalizationMethod API: AttributeError === mutant_none (mutant_none.pmml) === {x1:2.0, x2:0.5} -> label=A, A=3.5, B=-1.25, C=-1.25 [INVALID_RANGE] {x1:0.1, x2:3.0} -> label=B, A=-2.8, B=1.9, C=1.9 [INVALID_RANGE] {x1:-1.0, x2:2.0} -> label=B, A=-4.0, B=2.5, C=2.5 [INVALID_RANGE] warnings_emitted: [] normalizationMethod API: AttributeError === mutant_logit (mutant_logit.pmml) === ALL inputs -> label=None, A=nan, B=nan, C=nan [SILENT_NAN] warnings_emitted: [] normalizationMethod API: AttributeError [PASS] A1: baseline softmax: all predictions valid [PASS] A2: mutant_none: at least one prediction has prob out of [0,1] [PASS] A3: mutant_logit: all predictions silent NaN [PASS] A4: no warnings emitted for any variant [PASS] A5: normalizationMethod not accessible via public API Overall: ALL_PASS ``` --- ## Model Structure All three PMML files are structurally identical. The only difference is the `normalizationMethod` attribute: ```xml ``` The model uses 3 target classes (A, B, C), 2 numeric predictors (x1, x2), and 3 RegressionTables with fixed coefficients. The 3-class structure is required to reproduce the `none`→invalid-range and `logit`→NaN behaviors; pypmml handles binary 2-class normalization differently. --- ## Impact An attacker who can supply a malicious PMML model file can set `normalizationMethod` to cause: 1. **Invalid probability outputs** (`none`): downstream ML pipelines treating outputs as probabilities receive values outside `[0, 1]`. Downstream probability-based decisions (thresholding, calibration, ensemble weighting) silently operate on mathematically invalid inputs. 2. **Silent NaN propagation** (`logit`): all predictions return `None`/`NaN` with no exception and no warning. Downstream code checking `predicted_label` receives `None`; probability outputs propagate `NaN` through any further computation. 3. **Uninspectable attack surface**: pypmml's public API does not expose `normalizationMethod`. A consuming application cannot detect which normalization was applied and cannot validate that probability outputs are mathematically valid. --- ## Dataset Access This repository is gated (manual review required). To request access, submit a request through the Hugging Face interface. --- ## License This repository contains proof-of-concept material for a bug bounty submission. All PMML files and scripts are provided solely for security research purposes.