← Back to Feed CACHED · 2026-07-20 03:32:33 · CACHE_KEY CVE-2026-12484
CVE-2026-12484 · CWE-502 · Disclosed 2026-07-19

A vulnerability in keras-team/keras version 3

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
Do you agree?
01 · The Real Story

A picture frame that runs whatever code is scribbled on the back of the photo

This is another entry in the long-running Keras Lambda layer deserialization saga (CWE-502). Version 3.15.0 ships a model-loading path where a crafted .keras archive can smuggle attacker-controlled Python bytecode through Lambda.from_config() because the safe_mode=None default fails open in certain call sites (keras.layers.deserialize, keras.models.clone_model, and any direct from_config invocation outside a SafeModeScope(True) block). When a data scientist or an inference server calls keras.models.load_model('attacker.keras'), arbitrary code executes as the loading process — full RCE at the user/service context.

The vendor's HIGH / 7.8 rating is directionally correct but overstated for a typical enterprise. The CVSS vector (AV:L/AC:L/PR:N/UI:R) already captures the two big shackles: local attack vector and user interaction required. This is not a wormable network bug; it is a supply-chain-flavored code-execution primitive that fires only when someone chooses to load a hostile model file. For a shop that pulls models exclusively from an internal registry with signed provenance, the real severity is MEDIUM. For an ML platform that ingests arbitrary user-submitted .keras files (HuggingFace mirror, Kaggle-style pipeline, MLOps SaaS), the role-multiplier pushes it up to HIGH.

"Model deserialization RCE with UI:R — dangerous for anyone loading untrusted .keras files, irrelevant for the rest of the fleet."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Craft a malicious .keras archive

Attacker builds a Keras model that embeds a Lambda layer whose serialized function field contains marshalled Python bytecode invoking os.system, subprocess.Popen, or a reverse-shell payload. Public PoCs from Huntr and HiddenLayer already publish gadget templates for exactly this file format.
Conditions required:
  • Attacker can produce a .keras/.h5 file
  • Knowledge of the Lambda deserialization gadget
Where this breaks in practice:
  • Payload is stored on disk — trivial for AV/YARA to scope-scan if signatures exist
  • File carries a Lambda layer, which is unusual for production models and can be flagged
Detection/coverage: YARA rules from HiddenLayer's sai-security-advisory collection detect marshalled bytecode inside config.json of a .keras archive.
STEP 02

Deliver the model to a victim loader

The attacker gets the file in front of a keras.models.load_model() call. Realistic vectors: uploading to a public model hub, submitting a fine-tuned artifact to an MLOps pipeline, phishing a data scientist with a 'pretrained checkpoint,' or compromising an internal artifact registry.
Conditions required:
  • Victim has a workflow that loads externally sourced models
  • No signature/provenance check on the artifact
Where this breaks in practice:
  • Mature orgs pull from an internal MLflow/W&B/S3 with hash pinning
  • Email gateways strip unknown extensions or quarantine large binaries
Detection/coverage: Egress proxy / DLP can flag .keras downloads from non-allowlisted hosts.
STEP 03

Trigger deserialization outside SafeModeScope

Victim invokes load_model(path) — the default safe_mode parameter resolves to None, and the guard in _raise_for_lambda_deserialization compares against False instead of a truthiness check, so the guard is skipped. The marshalled bytecode is unmarshalled and executed inline.
Conditions required:
  • Code path is load_model / clone_model / layers.deserialize without explicit safe_mode=True
  • Keras 3.15.0 exactly (or the vulnerable range straddling 3.14–3.15)
Where this breaks in practice:
  • Keras 3.10+ prints a loud warning when loading a Lambda layer
  • Some MLOps runners hard-set safe_mode=True
Detection/coverage: EDR sees the parent Python interpreter spawn a shell / network child — classic living-off-Python telemetry.
STEP 04

Code execution in ML workload context

Payload runs as whatever principal owns the loader — often a Jupyter kernel, an Airflow worker, a Ray/KubeFlow pod, or a Triton/TF-Serving-adjacent Python process. From there the attacker steals model IP, cloud IAM tokens attached to the pod, training data, or pivots into the artifact registry to poison downstream models.
Conditions required:
  • Loader process holds valuable secrets/creds/data
  • No pod-level egress restrictions
Where this breaks in practice:
  • Well-run ML platforms scope IAM per job with short-lived tokens
  • Network policy blocks pod-initiated egress to the internet
Detection/coverage: CloudTrail/GuardDuty for anomalous STS assume-role from ML nodes; Falco rules for unexpected exec from Python.
03 · Intelligence Metadata

The supporting signals.

CVECVE-2026-12484 — keras-team/keras 3.15.0 Lambda deserialization RCE
CWECWE-502 (Deserialization of Untrusted Data)
CVSS v3.0AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H = 7.8 HIGH — the UI:R and AV:L components are the real gating factors
KEV statusNot listed as of 2026-07-20
EPSSNot yet scored (disclosed 2026-07-19); sibling CVE-2026-12481 tracks in the low single digits — expect the same class
Exploit maturityPublic PoC exists for the Lambda gadget class — Huntr blog and HiddenLayer advisory publish working payload structures
In-the-wildNo confirmed campaigns targeting 12484 specifically; broader Keras deserialization abuse observed in ML supply-chain poisoning research
Affectedkeras 3.15.0; likely regression of the 3.14.0 fix for CVE-2026-12481
Fixed inTrack keras-team/keras releases post-2026-07-19; pin to safe_mode=True as an interim
ReporterHuntr researcher community — same corpus that produced CVE-2026-12481
Exposure telemetryNot a network-facing service — Shodan/GreyNoise irrelevant; scan surface is *artifact provenance*, not TCP ports
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to MEDIUM (5.5/10)

The decisive factor is the UI:R + AV:L combination — exploitation requires a user or automated pipeline to deliberately load an attacker-supplied .keras file, which for the median enterprise fleet is a supply-chain hygiene problem, not a remote-exploitation event. It sits at MEDIUM rather than HIGH because there is no wormable path and no listening service; if your org runs a public model-ingestion MLOps platform, treat it as HIGH under the role multiplier.

HIGH Technical mechanism (Lambda deserialization class is well-documented)
MEDIUM Exact affected version range for 12484 vs sibling 12481
HIGH Absence of active exploitation as of 2026-07-20

Why this verdict

  • Friction — user interaction required: No .keras file gets deserialized without a human or pipeline calling load_model(). Removes drive-by and network-reachable exploitation.
  • Friction — local attack vector: Nothing listens on a port. The attacker needs a delivery channel (model hub, phishing, poisoned artifact registry). That is one full compromise step ahead of most attackers.
  • Friction — narrow install base at risk: Only orgs on exactly the vulnerable Keras release AND loading untrusted models are exposed. Most production ML shops pin Keras versions per-project and load only in-house artifacts.
  • Role multiplier — data scientist workstation: Chain succeeds; blast radius is one laptop's creds and any cloud tokens it holds. Bad but contained. Keeps floor at MEDIUM.
  • Role multiplier — public MLOps ingestion platform (HuggingFace-style, model marketplace, SaaS fine-tuning): Chain succeeds trivially because untrusted models are the *product*. Blast radius is tenant-scale — customer data, model IP, pipeline credentials. For these orgs the floor rises to HIGH, but this is <10% of the Keras install base, so it does not push the global verdict to CRITICAL.
  • No KEV, no observed campaigns, no network reachability — nothing forces the mitigation window shorter than the standard MEDIUM SLA.

Why not higher?

HIGH would require either KEV listing, evidence of in-the-wild exploitation, or the affected component being canonically a high-value-role service. Keras is a library, not an identity/CA/hypervisor/edge appliance, and the exploitation model demands cooperative user action. Neither the population nor the reachability supports HIGH for the median enterprise.

Why not lower?

LOW / IGNORE would be wrong because working PoCs exist for the class, RCE is confirmed rather than theoretical, and the ML supply chain is a real and rising attack surface. Any org loading community models on this version is genuinely exposed.

05 · Compensating Control

What to do — in priority order.

  1. Force safe_mode=True at every load_model call site — Grep your codebase for keras.models.load_model, keras.layers.deserialize, and clone_model; wrap in with keras.config.SafeModeScope(True): or pass safe_mode=True explicitly. Do this within the noisgate MEDIUM window — no mitigation SLA, but this is a 1-hour lift that should not wait.
  2. Pin Keras away from 3.15.0 — In requirements.txt / pyproject.toml / Pipfile, downgrade to the last known-good 3.x release or hold at the patched release once published. Trigger a full CI re-lock and rebuild container images.
  3. Enforce model provenance at the artifact registry — Only allow .keras artifacts signed with an internal key (Sigstore / cosign) into your MLflow/W&B/S3 model registry. Block ad-hoc wget of third-party checkpoints from developer environments via egress proxy policy.
  4. Deploy YARA scanning on model archives — Use HiddenLayer's public rules (or roll your own detecting marshalled bytecode in config.json inside .keras zips) at your artifact gate and email gateway.
  5. Segment ML training/inference IAM — Ensure Jupyter/Airflow/KubeFlow workers hold only short-lived, least-privilege cloud tokens. This caps blast radius when the RCE does fire.
What doesn't work
  • WAF / network IPS — the bug fires client-side on file load, no HTTP signature to match.
  • Perimeter patching / edge appliances — the vulnerability lives in a Python library on internal ML workloads, not on any network device.
  • Blocking .keras file extension at email gateway alone — attackers will rename to .zip, .tar, or serve over HTTPS from a hub the org allowlists.
  • Relying on safe_mode default — that's exactly the code path that fails open here.
06 · Verification

Crowdsourced verification payload.

Run on any host with the Python environment you want to audit. Invoke as python check_cve_2026_12484.py from inside the target virtualenv / conda env / container. No elevated privileges required — this is a pure package-introspection check.

noisgate-verify.py
PYTHONREAD-ONLYSAFE
#!/usr/bin/env python3
# noisgate verification: CVE-2026-12484 keras Lambda deserialization RCE
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
import sys

VULN_VERSIONS = {"3.15.0"}
# Add the fixed version here once vendor publishes it
FIXED_MIN = None  # e.g., "3.15.1"

def parse(v):
    try:
        return tuple(int(x) for x in v.split(".")[:3])
    except Exception:
        return None

try:
    import keras
except ImportError:
    print("UNKNOWN: keras not installed in this environment")
    sys.exit(2)

ver = getattr(keras, "__version__", None)
if not ver:
    print("UNKNOWN: cannot determine keras version")
    sys.exit(2)

print(f"Detected keras version: {ver}")

if ver in VULN_VERSIONS:
    print(f"VULNERABLE: keras {ver} is affected by CVE-2026-12484")
    sys.exit(1)

if FIXED_MIN and parse(ver) and parse(FIXED_MIN):
    if parse(ver) >= parse(FIXED_MIN):
        print(f"PATCHED: keras {ver} >= fixed baseline {FIXED_MIN}")
        sys.exit(0)

# Sanity: probe safe_mode guard behaviour
try:
    from keras.src.saving.serialization_lib import SafeModeScope  # noqa
    print("PATCHED (likely): version not in known vulnerable set; confirm against vendor advisory")
    sys.exit(0)
except Exception:
    print("UNKNOWN: could not introspect SafeModeScope; verify manually")
    sys.exit(2)
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: run the verification script across every Python env, container image, and data-science laptop. If Keras 3.15.0 turns up, pin down or replace it; under the noisgate mitigation SLA MEDIUM has no formal mitigation deadline, but the safe_mode=True hardening is a one-liner — do it this week alongside adding YARA scanning to your model registry gate. The noisgate remediation SLA gives you 365 days to fully upgrade to the vendor's fixed release, but if you operate a public model-ingestion platform (HuggingFace-style, MLOps SaaS, fine-tuning marketplace) treat this as HIGH per the role multiplier and compress mitigation to ≤30 days and remediation to ≤180 days. No KEV, no observed exploitation — no need to page anyone tonight.

Sources

  1. Huntr — Hunting Vulnerabilities in Keras Model Deserialization
  2. OffSeq Threat Radar — CVE-2026-12481 (sibling class)
  3. o3.security — CVE-2026-12481 deserialization writeup
  4. HiddenLayer — keras.models.load_model .h5 advisory
  5. BlueRock — Neutralizing Deserialization Attacks Against AI/ML Workloads
  6. HackTricks — Keras Model Deserialization RCE and Gadget Hunting
  7. Snyk — Keras CWE-502 tracker
  8. CWE-502 — Deserialization of Untrusted Data
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.