The Vulnerability Explained
A machine learning application was fetching ONNX model files and model chunks using the browser's Fetch API without any cryptographic verification. The vulnerable code path looked like this:
// Vulnerable: no integrity check
return combined.buffer; // Return the final ArrayBuffer
and:
// Vulnerable: no verification of cached model
if (cachedResponse) {
log("Using cached model");
return await cachedResponse.arrayBuffer();
}
The problem is clear: after downloading model chunks or retrieving them from cache storage, the application immediately used them without verifying that the bytes matched an expected cryptographic hash or signature. This created two attack surfaces:
- Network-level tampering: An attacker on the network path (via MITM, DNS poisoning, or BGP hijacking) could intercept the fetch request and return a modified model.
- Cache poisoning: An attacker with write access to the browser's cache storage could swap out a legitimate model with a poisoned one, and the application would use it without question.
The impact is severe: a poisoned ONNX model runs arbitrary computation on the host's hardware. An attacker could:
- Exfiltrate input data (images, audio, user-submitted content) sent to the model.
- Produce incorrect outputs to bias application decisions (e.g., if this is a classification or detection model).
- Use the inference process as a side-channel to leak information about the host device.
Affected Versions
| Affected | not applicable (first-party code) |
| Fixed in | not applicable (first-party code) |
| Ecosystem | N/A |
| CVE / GHSA | not assigned |
| CWE | unknown |
This vulnerability was present in the application's model-loading helper functions. The fix was applied directly to the codebase.
The Fix
The fix introduces a new integrity verification step at two critical junctures: after chunks are reassembled, and after a cached model is retrieved. Here's what changed:
After chunk reassembly:
// Fixed: integrity verification applied
return verifyModelIntegrity(combined.buffer, MODEL_PATH);
After cache retrieval:
// Fixed: verify cached model before use
if (cachedResponse) {
log("Using cached model");
return verifyModelIntegrity(
await cachedResponse.arrayBuffer(),
MODEL_PATH
);
}
The verifyModelIntegrity() function is imported from a dedicated integrity module and checks the downloaded buffer against a cryptographic hash derived from MODEL_PATH. The key insight in the implementation is that chunks are byte-sequential splits of the original model file, so the expected integrity hash is always tied to MODEL_PATH, regardless of which directory or cache the chunks were fetched from. This prevents an attacker from substituting chunks from a different model and having them pass verification.
By calling verifyModelIntegrity() immediately after every fetch or cache operation, the application ensures that:
- Any modification in transit is detected before the model reaches inference code.
- Cache poisoning attempts are caught at retrieval time.
- The integrity check is performed consistently across all model-loading paths.
Key Takeaways
-
Never trust downloaded ML models without verification: The fetch() API does not provide integrity guarantees. Always verify downloaded models against a known cryptographic hash before use, especially when models are the decision-making component of an application.
-
Chunks and caches need the same integrity checks: It is easy to assume a cached model is "already trusted," but cache storage is writable and can be targeted by attackers with local access. Apply integrity verification uniformly on all code paths that return model buffers, not just fresh downloads.
-
Integrity verification must happen before model inference: The verification call must be positioned after all network and cache operations but before the buffer is passed to the ML inference engine. A single path bypass compromises the entire defense.
-
Split models still need unified identity verification: When a model is split into multiple chunks for efficient download, the integrity verification must check against the complete original file hash, not individual chunks. This prevents an attacker from substituting a different model's chunks.
How Orbis AppSec Detected This
Source: The model buffer returned by the fetchAndCombineChunks() function and the cachedResponse.arrayBuffer() call in the cache retrieval path.
Sink: The return statements that hand the model buffer to the calling code without verification, and ultimately to ONNX inference APIs.
Missing control: No cryptographic integrity check (SRI hash, signature, or HMAC) was performed on the fetched or cached model data before it was used.
CWE: Unknown (related to lack of cryptographic signature verification on critical data).
Fix: Added calls to verifyModelIntegrity() before every return of a model buffer, ensuring the downloaded or cached bytes match an expected cryptographic identity.
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
Machine learning models are data with consequences. When they are downloaded or cached, they must be verified with the same rigor you would apply to code signatures or executable checksums. This vulnerability shows how easy it is to overlook integrity verification in the model-loading path—especially when chunks and caches create multiple code paths that can return model data. The fix ensures that every model, whether freshly downloaded, reassembled from chunks, or loaded from cache, is cryptographically verified before it reaches inference code. For developers building ML applications, this is a critical pattern to adopt: integrity verification on all external data sources, applied consistently and early.