cd /news/ai-safety/your-keras-model-file-is-executable-… Β· home β€Ί topics β€Ί ai-safety β€Ί article
[ARTICLE Β· art-101998] src=sourcefeed.dev β†— pub= topic=ai-safety verified=true sentiment=↓ negative

Your Keras Model File Is Executable Code, Not Data

A Keras model file is executable code, not data, because its config can carry marshalled CPython bytecode via Lambda layers, and Keras's safe_mode has been bypassed repeatedly, with CVEs including CVE-2025-1550 (fixed in Keras 3.9), CVE-2025-8747, CVE-2025-9905, and CVE-2026-12481, according to the AIsbom project and JFrog researchers. The format ships executable bytecode by design, and safe_mode bypasses allow arbitrary code execution on load, even with safe_mode enabled. Teams should treat .keras and .h5 files as code, not data, and scan them without deserializing.

read6 min views1 publishedAug 18, 2026
Your Keras Model File Is Executable Code, Not Data
Image: Sourcefeed (auto-discovered)

SecurityArticle safe_mode keeps getting bypassed because the format was built to run whatever the config names.

Emeka Okafor Everyone who has spent time hardening an ML pipeline knows the pickle sermon by heart. torch.load

calls __reduce__

, __reduce__

runs whatever the attacker wrote, and a checkpoint pulled from a random Hugging Face repo becomes remote code execution the instant you deserialize it. The lesson most teams took away was simple: stop shipping pickles. Move to .h5

, move to .keras

, move to formats that "just store weights." That lesson is comforting, widely repeated, and wrong in a way that keeps producing CVEs.

The uncomfortable detail β€” resurfaced this week by the AIsbom project and grounded in a year of real advisories β€” is that a Keras model config can carry a marshalled Python code object. Not a pickle. Not a weight tensor. A serialized chunk of CPython bytecode, sitting inside a JSON-ish config, waiting for a to hand it back to the interpreter.

Why a "config" contains bytecode #

The mechanism is boring, which is exactly why it's dangerous. Keras lets you define a layer as an arbitrary Python callable through Lambda. That's a genuinely useful feature β€” a quick

Lambda(lambda x: x * 2) beats writing a full layer subclass. But a lambda is code, and code has to survive being written to disk. Keras solves this the only way it can: it runs the callable through marshal

, stores the resulting code object in the model config, and reconstitutes it on load.So the "safe" format ships executable bytecode by design. This isn't a bug report against Keras; it's documented behavior. The bug is in everyone's mental model, where .keras

and .h5

got filed under "data" instead of "code."

Marshalled bytecode is arguably worse to defend against than pickle, for a subtle reason the AIsbom writeup nails: the obvious way to inspect a marshalled blob is to unmarshal it, and marshal.loads

on untrusted input is itself unsafe. The scanner and the victim reach for the same dangerous primitive. Any tool that wants to flag these payloads has to classify them from header bytes without ever calling the deserializer β€” which is what the new release does, and what most naive "just scan the model" pipelines don't.

safe_mode was supposed to fix this. It keeps not fixing this. #

Keras did respond. Since 2.13, safe_mode=True

is the default, and it blocks deserialization of Lambda layers carrying marshalled code. If the story ended there, this would be a footnote. Instead, safe_mode

has become one of the most reliably bypassed security controls in the ML tooling world, and the CVE trail tells the story better than any threat model:

: You don't even need a Lambda layer. Hand-editCVE-2025-1550config.json inside the.keras

archive to name an arbitrary Python module and function with arguments, andload_model

invokes it β€” withsafe_mode=True

, with no call to the model, on load alone. Fixed in Keras 3.9 (March 2025) by restricting imports to Keras's own modules.: That import restriction gets bypassed by reusing internal Keras functionality as a gadget chain. JFrog's researchers pointed atCVE-2025-8747keras.utils.get_file

, which will happily download an attacker-chosen URL into an attacker-chosen path β€” say, your~/.ssh/

directory.: Load a legacy HDF5 model andCVE-2025-9905safe_mode is silently ignored. No warning, no error, full execution.CVE-2026-12481: The guard conflatessafe_mode=None

(unset, should default to deny) with`safe_mode=False`

(explicitly off), so passing`None`

sails straight past the check.

Four bypasses, four different root causes, one pattern: safe_mode

is an allowlist bolted onto a deserializer that was designed to reconstruct arbitrary Python objects. Every patch narrows one path while the underlying capability β€” "the config can name code to run" β€” stays intact. This is the textbook signature of a design that treats security as a filter instead of a boundary.

The format-swap advice was never the fix #

Here's the editorial line, and I'll defend it: telling developers to "switch off pickle" was security theater dressed as best practice. The threat was never the pickle opcode format specifically; it was the decision to make deserialization and code the same operation. .keras

, .h5

, ONNX (external-data paths that read arbitrary files, custom operator domains), and GGUF (embedded Jinja templates you can't safely render because of known sandbox escapes) all reproduce that decision in their own dialect.

The one format that actually broke the pattern is safetensors, and it's worth understanding why it's different rather than cargo-culting it as the next magic extension. Safetensors stores tensors and nothing else β€” no callables, no config, no code path from file to interpreter. That's the real fix: not a safer serializer, but a format that structurally cannot express executable content. The catch developers hit in practice is that weights alone don't reconstruct a model. You still need the architecture, and the moment that architecture description can name layers, functions, or custom objects, you've quietly re-opened the door safetensors closed.

What to actually do on Monday #

Concrete, in priority order:

Treat every model artifact as untrusted code, not data. If you wouldn'tcurl | bash

a stranger's script, don'tload_model

their.keras

in your training environment. That reframing does more than any single patch.Load untrusted models in a sandboxβ€” a container with no network egress, no credentials mounted, a throwaway user,seccomp

on. Assumesafe_mode

will be bypassed, because the record says it will be.Upgrade, but don't trust the upgrade. Keras 3.9+ closes 1550 and its known descendants; nothing closes the nextget_file

-style gadget nobody's published yet. Patching is necessary and insufficient.Scan without deserializing. Header-byte inspection, notmarshal.loads

orpickle.load

, is the only safe way to classify a blob you don't trust. Any scanner that opens the payload to check the payload is part of the attack surface.Pin provenance. Hashes and signatures on model artifacts, the same way you'd pin a dependency. A model from an untraceable Hugging Face upload deserves exactly the scrutiny of an unsigned binary from an unknown vendor β€” which is to say, run it nowhere you care about.

The pickle era taught the ML world that model files can be weapons. The Keras safe_mode

saga is teaching a harder second lesson: swapping the file extension doesn't disarm them. As long as a model's architecture can name code, a model is running a program β€” and the only durable defense is to run it like you'd run any other untrusted program.

Sources & further reading #

[Your Keras model config can contain a marshalled Python code object](https://dev.to/aisbom/your-keras-model-config-can-contain-a-marshalled-python-code-object-5885)β€” dev.to -
[Is TensorFlow Keras Safe Mode Actually Safe? Bypassing safe_mode to Achieve Arbitrary Code Execution](https://jfrog.com/blog/keras-safe_mode-bypass-vulnerability/)β€” jfrog.com -
[Arbitrary Code Execution via Crafted Keras Config for Model  (CVE-2025-1550)](https://github.com/advisories/GHSA-48g7-3x6r-xfhp)β€” github.com -
[Keras vulnerable to CVE-2025-1550 bypass via reuse of internal functionality (CVE-2025-8747)](https://github.com/advisories/GHSA-c9rc-mg46-23w3)β€” github.com -
[CVE-2025-9905 - Bypassing Keras safe_mode via Legacy HDF5 File Format](https://github.com/io-no/CVE-Reports/issues/7)β€” github.com

[Emeka Okafor](https://sourcefeed.dev/u/emeka_okafor)Β· Security Editor

Emeka has spent over a decade tracking threat actors, vulnerability disclosures, and the evolving landscape of application security, bringing a sharp continent-spanning perspective to his reporting. He's known for translating dense CVE advisories into clear, actionable context that developers and security teams alike actually read.

Discussion 0 #

No comments yet

Be the first to weigh in.

── more in #ai-safety 4 stories Β· sorted by recency
── more on @keras 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/your-keras-model-fil…] indexed:0 read:6min 2026-08-18 Β· β€”