Build1 distinct publisher3 min readUpdated
"Use .h5 or .keras instead of pickle" is not a mitigation. Scanning pipelines built on the pickle-only model of executable artifacts have a hole in them.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
A post from the AIsbom project, published on dev.to, sets out a detail that undercuts the standard remediation for malicious model files: a Keras model config can contain a marshalled Python code object, and `load_model` executes it while reconstructing the model [1][2][3]. That matters because the usual advice when a team finds pickles in `models/` is to switch to `.h5` or `.keras`, and according to the post a pipeline that treats `.h5` as the safe alternative to pickle is mistaken [5].
The mechanism is not an exploit. Keras lets you define a layer as an arbitrary Python callable via `Lambda`; the callable has to survive serialisation, so it is stored in the config as a marshalled code object [1][2]. The post is explicit that this is documented behaviour of a feature people use [4]. The consequence for a scanner is unchanged by that: a `.keras` file hands the loader something executable in exactly the way a pickle does [5].
There is a second-order trap for anyone writing the detector. The obvious way to inspect a marshalled blob is to unmarshal it, and `marshal.loads` on untrusted input is itself unsafe [6]. The post's answer is that the blob's type can be read from its header bytes instead, and that in AIsbom v1.3.0 Lambda layers and embedded code objects are flagged CRITICAL with the payload never unmarshalled [7][8]. Both containers Keras writes, the `.keras` zip and legacy HDF5, are handled without pulling an HDF5 library into the install [9]. Those are the project's claims about its own release, not independent test results.
The same shape recurs in two other formats the pickle-only mental model waves through. ONNX has a reputation as safe because there is no embedded bytecode, which the post calls mostly true with two caveats: the external-data mechanism stores a path that the loader reads, so a path pointing outside the model directory turns loading into an arbitrary-file read, and operators can come from a non-standard domain, meaning the graph depends on something other than the standard operator set to execute [10][11][12]. Neither needs the graph run to detect; you walk the protobuf, including the subgraphs carried by `If`, `Loop` and `Scan` [13]. GGUF files can embed a `chat_template`, a Jinja template that downstream code renders to format prompts, and Jinja's sandbox has known escape constructs, so the check has to be static because rendering is the vulnerability [14]. That is three non-pickle formats where the loader or the analysis step does the attacker's work [22].
Even inside pickle, the post lists implementations that look plausible and are wrong: stopping at the first `STOP` opcode, when a legacy `torch.save` file hides its object behind several header pickles [15]; trusting the container, when the same checkpoint content packed as 7z leaves a ZIP-only scanner reporting nothing [16]; skipping corrupt files, when the pickle VM runs sequentially and a payload at the front executes before the damaged tail is reached [17]; and sniffing file type before disassembly, when a protocol-0 pickle is printable ASCII and can pass as a text config [18].
What to watch: whether your scanner reports an unfinished scan as unfinished. AIsbom added two MEDIUM levels for that, one for hitting a work limit and one for an archive member it cannot read at all, on the grounds that a loader without integrity checks would still run that member [20]. The post's line is that a scanner which quietly gives up and returns clean is worse than none, because you act on the clean bill [21]. Also worth checking in your own allowlists: whether a global is judged by its resolved module and attribute, so a submodule does not inherit an allowlisted parent's trust [19].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
Keras lets you define a layer as an arbitrary Python callable via Lambda.
Because the callable must survive serialisation, it is stored in the Keras model config as a marshalled code object.
When load_model reconstructs the model, it runs the stored code object.
This is not an exploit; it is the documented behaviour of a feature people use.
A .keras file is a code-carrying format in exactly the way a pickle is, and a pipeline that treats .h5 as the safe alternative to pickle is mistaken.
The obvious way to inspect a marshalled blob is to unmarshal it, and marshal.loads on untrusted input is itself unsafe.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Mechanisms specific and checkable, but single self-interested source
The format-level assertions are described at a level of detail that a reader can verify against Keras, ONNX and GGUF themselves (marshalled code object in the config, external-data path resolution, embedded Jinja chat_template), which raises evidentiary quality above bare assertion. But the cluster contains exactly one item, authored by the tool's own maintainer, with no independent source, no inline CVE or paper citation, and no third-party reproduction of either the mechanisms or the scanner's claimed coverage.
Release announcement only; no external usage evidence
The only adoption-relevant facts in the cluster are the v1.3.0 release itself and a vendor-run scorecard executed against a self-built corpus. There are no downloads, dependents, deployments, customer or pipeline-integration disclosures, and no third-party account of using the scanner, so measured adoption is near the floor rather than absent.
Slightly overstated on tool efficacy, well-grounded on formats
The headline claim — that a .keras config can carry a marshalled code object which load_model runs — appears understated relative to how often .h5/.keras is recommended as a pickle mitigation, and the post is candid about its own gaps (three of eleven evasion cases uncaught, named openly, with a CI gate that cannot be satisfied by regenerating the scorecard). The positive tilt comes from packaging framework facts inside a release announcement whose coverage claims are entirely self-scored and self-reported, with no independent validation or adoption to support them.
Maintainer-authored release marketing for its own scanner
The post is written by the AIsbom project on a developer publishing platform, ends in a release-notes link to the project's GitHub, and threads product verdict names and CLI commands through every technical section. The author's interest in establishing that pickle-only scanning is insufficient coincides directly with the product being sold as the remedy, which is a strong and undisclosed-by-default incentive to frame the problem broadly.
Low-moderate: one vendor source, technically specific
Confidence is limited by structure — a single publisher, a single item, and a self-interested author — but not floored, because the format-level claims are stated with enough mechanical specificity to be checked against the frameworks themselves and the post volunteers its own failures. Tool-capability and adoption dimensions should be treated as unverified.
build
1,500 submissions in 14 days: what a 12th-place GPU kernel says about agent loops1 distinct publisher
build
796 pages of semantic search with no vector database, and what it cost to skip one1 distinct publisher
build
Unsloth's 10% quant claim is really about which machines can run a 27B model1 distinct publisher
build
Four YAML parsers, two specs: the failure mode is both of them being right1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 16, 2026