<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Labdev</title>
    <description>The latest articles on DEV Community by Labdev (@aisbom).</description>
    <link>https://dev.to/aisbom</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3662040%2Fafda99b9-38d5-4ae3-8593-7cb7ad1cf59c.jpg</url>
      <title>DEV Community: Labdev</title>
      <link>https://dev.to/aisbom</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aisbom"/>
    <language>en</language>
    <item>
      <title>Your Keras model config can contain a marshalled Python code object</title>
      <dc:creator>Labdev</dc:creator>
      <pubDate>Sun, 16 Aug 2026 13:54:58 +0000</pubDate>
      <link>https://dev.to/aisbom/your-keras-model-config-can-contain-a-marshalled-python-code-object-5885</link>
      <guid>https://dev.to/aisbom/your-keras-model-config-can-contain-a-marshalled-python-code-object-5885</guid>
      <description>&lt;p&gt;Most conversations about malicious ML artifacts stop at pickle. That's understandable — &lt;code&gt;torch.load&lt;/code&gt; calling &lt;code&gt;__reduce__&lt;/code&gt; is the canonical example, and it's the one everybody has read about. But pickle isn't the only file format in your &lt;code&gt;models/&lt;/code&gt; directory that hands a loader something executable, and the pickle story itself has more edge cases than the summary version suggests.&lt;/p&gt;

&lt;p&gt;Here are a few of the mechanisms, described in enough detail that they're useful whether or not you ever run a scanner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keras: the Lambda layer
&lt;/h2&gt;

&lt;p&gt;Keras lets you define a layer as an arbitrary Python callable via &lt;code&gt;Lambda&lt;/code&gt;. That callable has to survive serialisation, so it's stored in the model config as a marshalled code object. When &lt;code&gt;load_model&lt;/code&gt; reconstructs the model, it runs it.&lt;/p&gt;

&lt;p&gt;This is not an exploit. It's the documented behaviour of a feature people use. But it means a &lt;code&gt;.keras&lt;/code&gt; file is a code-carrying format in exactly the way a pickle is, and a pipeline that treats &lt;code&gt;.h5&lt;/code&gt; as "the safe alternative to pickle" is mistaken.&lt;/p&gt;

&lt;p&gt;There's a second trap for anyone building tooling here: the obvious way to inspect a marshalled blob is to unmarshal it, and &lt;code&gt;marshal.loads&lt;/code&gt; on untrusted input is itself unsafe. The blob's type can be determined from its header bytes instead. In AIsbom v1.3.0, Lambda layers and embedded code objects are flagged CRITICAL, and the payload is never unmarshalled. Both containers Keras writes — the &lt;code&gt;.keras&lt;/code&gt; zip and legacy HDF5 — are handled without adding an HDF5 library to the install.&lt;/p&gt;

&lt;h2&gt;
  
  
  ONNX: external data is a filesystem reference
&lt;/h2&gt;

&lt;p&gt;ONNX is a protobuf graph, and by reputation it's the "safe" format because there's no embedded bytecode. Mostly true, with two caveats worth knowing.&lt;/p&gt;

&lt;p&gt;First, tensors can live outside the file. The external-data mechanism stores a &lt;em&gt;path&lt;/em&gt;, and the loader reads it. A path that points outside the model directory turns &lt;code&gt;load_model&lt;/code&gt; into an arbitrary-file read.&lt;/p&gt;

&lt;p&gt;Second, operators can come from a non-standard domain — meaning the graph depends on something other than the standard operator set to execute.&lt;/p&gt;

&lt;p&gt;Neither of these requires running the graph to detect. You walk the protobuf. AIsbom does exactly that: no ONNX runtime is imported, the graph is never executed, and subgraphs carried by &lt;code&gt;If&lt;/code&gt;, &lt;code&gt;Loop&lt;/code&gt; and &lt;code&gt;Scan&lt;/code&gt; are walked too, because that's an easy place to hide a reference if a tool only inspects the top level.&lt;/p&gt;

&lt;h2&gt;
  
  
  GGUF: the chat template is a Jinja program
&lt;/h2&gt;

&lt;p&gt;GGUF files can embed a &lt;code&gt;chat_template&lt;/code&gt; — a Jinja template that downstream code renders to format prompts. Jinja's sandbox has known escape constructs. The important consequence for tooling: you cannot analyse this by rendering it, because rendering it &lt;em&gt;is&lt;/em&gt; the vulnerability. It has to be checked statically. AIsbom extracts the template into the SBOM component and checks it there, unrendered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four pickle assumptions worth rechecking
&lt;/h2&gt;

&lt;p&gt;Even within pickle, several plausible-looking scanner implementations are wrong:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stopping at the first &lt;code&gt;STOP&lt;/code&gt; opcode.&lt;/strong&gt; A legacy &lt;code&gt;torch.save&lt;/code&gt; file hides its object behind several header pickles. Parse one stream and you never reach the payload. All concatenated streams have to be walked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusting the container.&lt;/strong&gt; PyTorch checkpoints are ZIPs. Pack the same content as 7z and a scanner that only knows ZIP opens nothing and reports nothing. AIsbom now reports &lt;code&gt;CRITICAL (Non-Standard Container: …)&lt;/code&gt; — naming the container without unpacking it, so no native dependency enters the install.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Abandoning corrupt files.&lt;/strong&gt; The pickle VM runs sequentially. A payload at the front executes before a corrupt tail is ever reached, so "this file is damaged" is not a reason to skip it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deciding file type before disassembly.&lt;/strong&gt; A protocol-0 pickle is printable ASCII. Sniff type first and it can pass as a text config and be reported safe. Disassemble first, decide type second.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Also in this release: indirect-execution gadgets (&lt;code&gt;bdb.Bdb.run&lt;/code&gt;, the asyncio gadget chain, and import-mechanism primitives like &lt;code&gt;sys.modules&lt;/code&gt;, &lt;code&gt;importlib&lt;/code&gt;, &lt;code&gt;runpy&lt;/code&gt;, &lt;code&gt;pkgutil&lt;/code&gt;, &lt;code&gt;builtins.__import__&lt;/code&gt;) are caught in both modes, and strict mode judges a global by its &lt;em&gt;resolved&lt;/em&gt; module and attribute, so a submodule no longer inherits an allowlisted parent's trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Saying "I don't know" out loud
&lt;/h2&gt;

&lt;p&gt;Two new risk levels exist purely so an unfinished scan is never reported as a clean one. &lt;code&gt;MEDIUM (Pickle Scan Incomplete)&lt;/code&gt; appears when a file is unusual enough that the scan reaches its work limit. &lt;code&gt;MEDIUM (Unreadable Pickle Member)&lt;/code&gt; appears when an archive member can't be read at all — a loader that doesn't verify integrity would still run it.&lt;/p&gt;

&lt;p&gt;A scanner that quietly gives up and returns clean is worse than no scanner, because you'd act on the clean bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking the claims
&lt;/h2&gt;

&lt;p&gt;The uncomfortable part of writing a scanner is that "we detect evasion technique X" is unfalsifiable from the outside. So v1.3.0 ships a command that falsifies it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# build inert replicas of documented evasion techniques,&lt;/span&gt;
&lt;span class="c"&gt;# scan them in both modes, print catches and misses&lt;/span&gt;
aisbom bypass-scorecard

&lt;span class="c"&gt;# release gate: fails CI if the caught count regresses&lt;/span&gt;
aisbom bypass-scorecard &lt;span class="nt"&gt;--check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each case traces to a published CVE or research paper. Nothing in the corpus is ever executed. Current result: &lt;strong&gt;8 of 11 caught, up from 5 of 11.&lt;/strong&gt; The three that aren't are listed openly with their mechanism and source. Two are partial: AIsbom refuses to call the file safe but never reaches the payload, so it reports the wrong reason. One — a pickle carried under a non-standard extension, so extension-driven discovery never opens it — is a straight miss. None of the three is treated as acceptable; every case in the corpus is marked as one a correct scanner should catch.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--check&lt;/code&gt; runs in CI on every push and cannot be satisfied by regenerating the scorecard. Improving detection is the only way to raise the floor.&lt;/p&gt;

&lt;p&gt;Exit codes, CycloneDX and SPDX output, and every existing verdict are unchanged; that was verified against the pre-release baseline in both scan modes.&lt;/p&gt;

&lt;p&gt;Release notes: &lt;a href="https://github.com/Lab700xOrg/aisbom/releases/latest" rel="noopener noreferrer"&gt;https://github.com/Lab700xOrg/aisbom/releases/latest&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>PyTorch Models Are Executables: Why I Built a Scanner to Stop "Pickle Bombs"</title>
      <dc:creator>Labdev</dc:creator>
      <pubDate>Tue, 16 Dec 2025 15:58:43 +0000</pubDate>
      <link>https://dev.to/aisbom/pytorch-models-are-executables-why-i-built-a-scanner-to-stop-pickle-bombs-92i</link>
      <guid>https://dev.to/aisbom/pytorch-models-are-executables-why-i-built-a-scanner-to-stop-pickle-bombs-92i</guid>
      <description>&lt;p&gt;&lt;strong&gt;The Lie We Tell Ourselves&lt;/strong&gt;&lt;br&gt;
If you work in DevOps or Security, you probably scan your &lt;code&gt;requirements.txt&lt;/code&gt; religiously. You use Snyk or Dependabot to catch that one vulnerable version of &lt;code&gt;requests&lt;/code&gt;. You feel safe.&lt;/p&gt;

&lt;p&gt;Then, you turn around and let your Data Science team download a 5GB &lt;code&gt;.pt&lt;/code&gt; (PyTorch) file from a random Hugging Face repository and load it directly into your production environment.&lt;/p&gt;

&lt;p&gt;We treat AI models like "Data" - inert, harmless static files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They are not. They are Executables.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Technical Reality: What is a &lt;code&gt;.pt&lt;/code&gt; file?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A standard PyTorch model file is actually a Zip archive. Inside that archive, the model weights and architecture are serialized using Python's &lt;code&gt;pickle&lt;/code&gt; module. The pickle module is famous for one thing in the security world: &lt;strong&gt;Remote Code Execution (RCE)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;torch.load('model.pt')&lt;/code&gt;, the unpickler parses a stream of opcodes. If that stream contains instructions to import &lt;code&gt;os&lt;/code&gt; and run &lt;code&gt;system()&lt;/code&gt;, your machine executes it. Instantly. No sandbox. No warning.&lt;/p&gt;

&lt;p&gt;This isn't theoretical. Here is how easy it is to create a "Malicious Model" in 5 lines of Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import torch
import os

class Malicious(object):
    def __reduce__(self):
        return (os.system, ("rm -rf / --no-preserve-root",))

torch.save(Malicious(), "bert_finetune.pt")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you load that file, your server is gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Standard SCA Tools Fail&lt;/strong&gt;&lt;br&gt;
Most teams rely on Software Composition Analysis (SCA) tools like Dependabot or standard SBOM generators. These tools work by matching library versions in &lt;code&gt;requirements.txt&lt;/code&gt; against databases of known vulnerabilities (CVEs).&lt;/p&gt;

&lt;p&gt;This approach fails for AI models because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Not a Library:&lt;/strong&gt; A custom model file isn't a "package" with a version number. It's a binary blob.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not a CVE:&lt;/strong&gt; A Pickle Bomb isn't a public vulnerability; it's a custom exploit payload hidden inside the file structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blind Spot:&lt;/strong&gt; You get a clean security report for your dependencies, while a 5GB RCE payload sits undetected in your Docker container.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Introducing AIsbom&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I realized we needed a tool that respects the unique nature of AI artifacts. We don't just need to scan the wrapper; we need to scan the brain.&lt;/p&gt;

&lt;p&gt;So, I built AIsbom.&lt;/p&gt;

&lt;p&gt;It is an open-source CLI that performs &lt;strong&gt;Deep Binary Introspection&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It Unzips the Artifact:&lt;/strong&gt; It parses &lt;code&gt;PyTorch/Safetensors&lt;/code&gt; structures in memory (without loading the heavy weights).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It Disassembles the Bytecode:&lt;/strong&gt; It uses &lt;code&gt;pickletools&lt;/code&gt; to iterate over the opcode stream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It Detects the Bomb:&lt;/strong&gt; It flags dangerous globals like &lt;code&gt;os.system&lt;/code&gt;, &lt;code&gt;subprocess&lt;/code&gt;, &lt;code&gt;eval&lt;/code&gt;, and &lt;code&gt;socket&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to check your models right now&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can install the CLI from PyPI:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pip install aisbom-cli&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Then, point it at your model folder:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aisbom scan ./my-models&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You'll get a risk assessment like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Filename           | Framework | Risk Level
-------------------|-----------|-------------------------------------

bert_finetune.pt   | PyTorch   | CRITICAL (RCE Detected: posix.system)
safe_model.st      | SafeTensors | LOW (Binary Safe)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Future is SafeTensors (But we aren't there yet)&lt;/p&gt;

&lt;p&gt;The industry is moving toward &lt;code&gt;.safetensors&lt;/code&gt;, which is a safe, JSON-header-based format. But millions of legacy &lt;code&gt;.pt&lt;/code&gt; files still exist, and developers still download them.&lt;/p&gt;

&lt;p&gt;Until everyone migrates, we need guardrails.&lt;/p&gt;

&lt;p&gt;I open-sourced this tool because I believe AI Supply Chain Security shouldn't be a black box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Lab700xOrg/aisbom" rel="noopener noreferrer"&gt;https://github.com/Lab700xOrg/aisbom&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://aisbom.io" rel="noopener noreferrer"&gt;https://aisbom.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me know what you think. I'm actively looking for edge cases in Pickle protocols to make the detection even more robust.&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
