Nouveau: Approbation des agents, red teaming et analyse de modèles. Découvrir
MITRITY
← Tous les articles
AI model scanningsupply chain securitypickle securityAIBOM

Your Model File Is a Program. Read It Before Your Agent Runs It.

Joakim Sundberg6 min read

Here is a thought experiment. A developer on your team finds a promising fine-tuned model on a public hub. It has decent stars, a tidy model card and exactly the capability your agent needs. They add one line to load it, the tests go green and it ships.

Now ask yourself: what did you just run?

Not what did you download. What did you run?

Because a model artifact is the one thing in your AI stack that is executable, opaque and shipped by someone else, all at once:

  • A PyTorch checkpoint is a pickle stream, and a pickle stream is a program. Loading one runs whatever it names.
  • A chat template is rendered into every conversation. Whatever it says, your model hears, every single time.
  • A repository with auto_map in its config asks the loader to import Python that came along with the weights.

None of this shows up in your dependency manifest. None of it is what a software composition scanner looks at. Your SBOM will tell you which version of transformers you're using. It won't tell you that the checkpoint you just loaded calls os.system.

That's why we built Model Scanning into MITRITY: so you can read a model before your agents load it.

What a scan actually looks at

Declare a model repository in MITRITY and it gets scanned on a schedule or on demand. The scanner looks for the things that can hurt the system that loads the model:

  • Pickle streams, protocols 0 to 5, bare or inside PyTorch zip containers. Every global the stream references is recorded, along with which ones it actually calls. A known remote-code target like os.system, subprocess, eval or a nested torch.load is flagged critical, whether the stream calls it or merely imports it.
  • Archive tricks in PyTorch files: executable members, files restored to disk on load, paths that try to escape the extraction directory.
  • safetensors and GGUF header integrity: header lengths the file can't hold, tensors claiming overlapping bytes, and file magic that disagrees with the extension.
  • Hidden instructions in model cards, configuration, metadata and chat templates. Think instruction overrides, role hijacking, exfiltration attempts, invisible characters, lookalike letters and base64 blobs that decode to instructions.
  • The trust_remote_code surface: configs declaring auto_map or custom_pipelines, and shipped Python that evaluates code, starts processes or decodes a payload.
  • Repository hygiene: unpinned revisions, pickle-only weights, missing or restrictive licenses and missing model cards.

Just as important is what it doesn't do. The scanner never imports, resolves or invokes anything a file names. The pickle disassembler walks the opcode stream like a careful reader, noting every instruction, and never executes a single one. Scanning a malicious model is perfectly safe, because the scanner never runs it.

And it doesn't pretend to be something it isn't. It won't tell you whether a model is accurate, biased or good at its job. It tells you whether loading it is safe.

Scanning 30 GB with about a megabyte

Model repositories are big. Pulling every one down to scan it would be slow, expensive and frankly a bit silly when most of those bytes are tensor values the scanner doesn't need.

So MITRITY doesn't download models. Every format is read through ranged requests, fetching only the part of the file that carries its structure:

  • For safetensors, the length prefix and JSON header at the start of the file.
  • For GGUF, the magic bytes and metadata block at the start, with the bytes deciding which reader runs, not the file extension.
  • For PyTorch files, the archive directory at the end, then only the pickle members it names.
  • For text and config files, the first megabyte.

The result: a thirty-gigabyte repository gets scanned with roughly a megabyte of transfer.

Two small details make the results more trustworthy. config.json is read before any Python, so the scanner already knows which code the config will import when it reports on it. And every file is fetched at the exact commit the source resolved, not the branch name, so one scan always describes one revision, even if someone pushes to the branch mid-scan.

Three rules that keep the results honest

The fastest way to make a security scanner useless is to make it noisy, vague or quietly wrong. We built Model Scanning around three rules to avoid all three.

1. Security is not hygiene

Every finding declares a category: security, supply chain, compliance or hygiene. And the dashboard keeps security and hygiene firmly apart.

A security finding says loading this artifact can run code, exfiltrate data or change the model's instructions. It names the file and the exact spot in the file, and it can rank all the way up to critical.

A hygiene finding says this artifact is harder to govern than it needs to be. It never ranks above medium.

Take pickle-only weights. Shipping weights as pickle isn't a vulnerability in itself, and a repository with clean pickle weights isn't unsafe. But safetensors removes an entire class of risk that pickle can't, so it's worth flagging as hygiene. Calling it a vulnerability would just train your team to ignore the security category, and that's the last thing you want.

2. Every finding is verifiable

If you disagree with a result, you can check it yourself. Every pickle finding carries the byte offset, the opcode and the module and name it reaches. Open the file at that offset and the opcode is right there. Text findings carry a short excerpt, capped at 240 characters: enough to be evidence, never a copy of the document.

3. Incomplete is never reported as clean

This one matters most. A scanner that silently gives up on a file it couldn't read is worse than no scanner at all, because it turns an unknown into a pass.

So every path that can't finish raises its own finding and marks the scan incomplete. A truncated pickle stream, an unreadable archive, a header too large for the budget, a file over its size cap, a repository with too many files: you'll see each of them. An empty findings list under an incomplete scan is labelled incomplete, never clean.

What about models you can't download?

Plenty of your agents don't load weights at all. They call GPT, Gemini or Claude through a provider API. You can't scan those files, but they're absolutely part of your inventory, and they carry their own risks.

Every call governed by the Mitrity LLM Gateway names a provider and a model, and MITRITY turns those names into inventory rows automatically. A model first called this morning shows up in your inventory this morning. Each row shows calls, calls blocked by policy, the agents behind them, tokens in and out and the deployments they came through.

Instead of a file scan, every hosted model is checked against two things you control:

  • Is it covered by policy? If no allow or deny rule names the model, whether an agent may call it is being decided by your policy default alone. MITRITY flags that, tells you exactly which action type to write a rule for, and clears the finding once you do.
  • Is it known to be unsafe? If the model matches an entry on the curated list of known-unsafe models, you get a supply chain finding naming the entry it matched.

So the question "which models are our agents actually using, and did anyone decide that was OK?" finally has a live answer.

Pin it, and the scan stays true

A scan of main tells you about main on the day it ran. Tomorrow someone pushes a new checkpoint and your clean result describes a file you're no longer loading.

That's why every scan records the exact commit it resolved, and flags an unpinned revision so you can pin to it. Once you pin, a later push to the branch can't change what you load, and the scan result stays true for as long as the pin holds.

Gated and private repositories work too. Their access token is stored write-only: it's used only to read that repository, dropped if a request redirects to another host, and never shown again in the API or dashboard.

When something new turns up, it reaches you the way the rest of MITRITY does: email, Slack, webhooks, your SIEM or the in-portal bell. You can resolve a finding or accept the risk with a note, and both are recorded. If a later scan sees the same thing again, it opens a fresh finding. Accepting a risk today doesn't silence it forever.

Know what you're loading

We spent a decade learning to ask "what's in this package?" before we install it. Models deserve the same question, and arguably more urgently, because they're bigger, more opaque and increasingly loaded by agents that act on their own.

Model Scanning gives your team a fast, safe and honest answer before the load, and a live inventory of every hosted model your agents call after it.

For the full technical detail on formats, budgets, finding codes and the gateway inventory, read the Model Scanning documentation.


MITRITY is an intent-aware governance platform for autonomous AI agents. Start a free trial or read the documentation to scan the models your agents load, before they load them.