6. Provenance, updates, and isolation
Question: How are the models kept secure and current?
Rationale
A model is a large file downloaded from the internet and executed inside the organization's network on its most sensitive data. It deserves the care given to any software from outside. That means a recorded source, a verified digest, a safe file format that cannot execute code on load, a signature where the publisher provides one, and a lock file that says exactly which files the server loads. Updates arrive often, and each one is a change: it goes through the suite's gate like a prompt change, with the old version kept until the new one passes. The server itself runs with the access it needs and no more: the model directory read-only, the network limited to the jobs that call it, the logs without the confidential content.
Target state
- Every model in use has a lock file with its source, version, and the digest of each file, verified before every load.
- Weights are stored in safe formats; signatures are verified where the publisher signs; the import path is documented.
- Updates are gated by the suite with the previous version kept; the serving environment is isolated from what it does not need.
Maturity levels
| Level | Steps |
|---|---|
| Foundational | Record source and digests for the models in use; verify before loading; use safe formats. |
| AI-ready | Keep lock files under version control; verify signatures where available; gate updates with the suite; isolate the server. |
| AI-native | Automate verification in the deployment pipeline; keep an internal registry of approved models; review the registry on a schedule. |
The organization's lock file for its fine-tuned coder lists three files with their digests and the import source. On a test directory with one file altered and one file added, the verification reports the altered file as a mismatch and the added file as unlisted, and refuses to pass. The server loads nothing until the lock verifies.
Recipes
6.1 Record provenance and verify digests before every load
- Level
- Foundational
- Skills
- Infrastructure; Python
- Indicative time
- An hour per model
Result: each model has a lock file that records where it came from and a fingerprint of each of its files, and a check runs before the server loads the files and stops the server if any file differs.
A model is a set of large files downloaded from the internet. Before they run on the organization's data, the organization needs to know that the files in use are the ones it reviewed. A digest (a SHA-256 fingerprint, a string that changes if a single byte of the file changes) answers that. The lock file below records, for one model, its identifier, version, source, and the digest of each file:
{
"model_id": "example-org/stats-coder-3b",
"version": "1.2.0",
"source": "internal registry, imported from the base model's publisher on 2026-09-10",
"files": {
"config.json": "f963a7f52a05373364adc79ea054023bfdb3bdcc5be1903bd2b6b39cad9b2c2b",
"tokenizer.json": "c2823fb776dfaab48bfa06a33005d02a60492d87762cdb66c9c4155f97fbaa5d",
"model.safetensors": "9a129038d9a00aed0cf6a7ea059ca50a813449061ab87848cf1a13eafdf33b2c"
}
}
The script below computes the digest of every file in a model directory and compares it with the lock file. It reports files that match, files that differ, files that are missing, and files present in the directory that the lock file does not list:
"""Verify a model directory against its lock file before serving it.
Reads a lock file (JSON: model_id, version, source, files with the
expected SHA-256 digest of each file) and a model directory, computes
the digest of every listed file, and reports matches, mismatches, files
missing from the directory, and files in the directory that the lock
does not list (which should not be loaded). Exit status 1 on any
mismatch or missing file. Standard library only.
Usage:
python verify_model_files.py model_lock.json /path/to/model-dir
For the running example the directory does not ship with the cookbook;
the script is run with a directory created in the test, and the lock file
shows the format.
What this does not do: a digest proves the files are the ones the
organization recorded; it does not prove the recorded files are
trustworthy. The provenance of the import (publisher, date, licence) is
the lock file's source field and the model card, checked by a person.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import sys
from pathlib import Path
def digest(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as fh:
for chunk in iter(lambda: fh.read(1 << 20), b""):
h.update(chunk)
return h.hexdigest()
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument("lock", type=Path)
parser.add_argument("directory", type=Path)
args = parser.parse_args(argv)
with args.lock.open(encoding="utf-8") as fh:
lock = json.load(fh)
expected = lock["files"]
present = {p.name for p in args.directory.iterdir() if p.is_file()}
ok = mismatched = 0
missing = []
print(f"{lock['model_id']} {lock['version']} from {lock.get('source', '?')}\n")
for name, sha in expected.items():
path = args.directory / name
if not path.exists():
missing.append(name)
print(f" MISSING {name}")
continue
actual = digest(path)
if actual == sha:
ok += 1
print(f" ok {name}")
else:
mismatched += 1
print(f" MISMATCH {name}: expected {sha[:12]}..., found {actual[:12]}...")
extra = sorted(present - set(expected))
for name in extra:
print(f" unlisted {name} (not loaded)")
print(
f"\n{ok} verified, {mismatched} mismatched, {len(missing)} missing, {len(extra)} unlisted"
)
return 1 if mismatched or missing else 0
if __name__ == "__main__":
sys.exit(main())
python verify_model_files.py model_lock.json /path/to/model-dir
Output for the running example, run on a test directory in which one file was altered and one file was added:
example-org/stats-coder-3b 1.2.0 from internal registry, imported from the base model's publisher on 2026-09-10
ok config.json
ok tokenizer.json
MISMATCH model.safetensors: expected 9a129038d9a0..., found 03f359d3fb9f...
unlisted extra.bin (not loaded)
2 verified, 1 mismatched, 0 missing, 1 unlisted
The first line names the model and its import source. Each line that follows gives one file's status, with the expected and the found digest for a mismatch. The last line counts the outcomes, and any mismatch or missing file makes the script exit with a failure.
- When a model is imported, record its source (publisher, address, date), its version, its licence class (chapter 2), and the digest of each file. Store the files in the organization's own registry (an internal file store). The server never downloads from the internet at start-up.
- Run the verification as the first step of every deployment and every restart. A mismatch stops the start.
- Use only weight formats that cannot run code when loaded
(
safetensors, GGUF). Refuse formats that can. - Where the publisher signs its releases, verify the signature at import and record it in the lock file.
Worked example. When the mid-sized model is imported, the engineer downloads its three files from the publisher's hub page, verifies the publisher's signature, computes the digests, and writes the lock file with the source address, the version tag, the licence class, and the date. The files go into the organization's internal registry, and the server's start-up script runs the verification against that directory. In the test shown above, the engineer altered one byte of the weights file and added a stray file to a copy of the directory; the check reports the mismatch and the unlisted file and exits with a failure, so the server does not start. The server is then pointed at the clean directory, the check passes, and the model loads.
What this does not do. A matching digest proves that the files are the ones recorded. Whether the recorded files deserve trust was decided at import, by reading the publisher, the licence, and the model card. A model the organization fine-tuned is locked the same way, with the organization as its publisher.
6.2 Update models through the suite's gate
- Level
- AI-ready
- Skills
- Developer
- Indicative time
- Part of each update
Result: a new version of a model reaches production only after it has passed the task's test questions, and the previous version is kept ready and the switch is recorded.
Publishers release new versions often. Each one is a change like a change of prompt: it can improve one task and quietly break another.
- Treat every new version (a publisher's release, a new fine-tune, a new quantization) as a new candidate: import it, verify it, write its card, and read its licence again.
- Run the suite and redraw the frontier (chapter 5). The comparison rule of the evaluation cookbook decides.
- Deploy with the previous version kept ready to restore for an agreed period. Record the switch in the AI component register with the lock file version.
- Update the serving stack together with the model; a stack update is also a change the suite has to pass.
Worked example. The publisher of the mid-sized model releases version 2.1 with a changed tokenizer. The engineer imports it as a new candidate, verifies it, writes its card from the publisher's, and reads the licence again, which is unchanged. The suite scores 0.92 overall, up one point, and 0.86 on the national language, down two points. The gate's verdict is REVIEW for the national language slice, and the dissemination lead reads the failed questions, finds that the new tokenizer splits a common prefix differently, and holds the update. Two weeks later a point release fixes it, the suite passes on every slice, and version 2.1.1 is deployed with version 2.0 kept ready for a month and the switch recorded in the register.
What this does not do. The suite measures what its questions cover. A new version that changes something the questions do not cover (how it phrases a refusal, for example) is caught by the production review of the evaluation cookbook, and the kept previous version is the remedy.
6.3 Isolate the serving environment
- Level
- AI-ready
- Skills
- Infrastructure; security
- Indicative time
- One day
Result: the model server can read its own files and answer the jobs that call it, and it can do nothing else.
A server that runs on confidential data is a target. Limiting what it can reach limits what a compromise could do.
- Run the server under its own service account, with the model directory read-only and no access to any data store. The jobs that call the server send it the inputs it needs.
- Restrict the network: accept connections only from the addresses of the jobs and the gateway; allow no outbound connections, so that a compromised server can neither send data out nor fetch anything.
- Log each request with the caller, the model version, the timing, and the token counts. Log the content only where it is not confidential, and state how long logs are kept.
- Keep the server's software dependencies pinned and scanned like any other service, and rebuild it from its definition when something changes; a server patched in place drifts from its definition.
Worked example. The server runs as the account modelserve, which
can read the model directory and nothing else; it has no credentials
for the survey database or the catalog. The firewall allows inbound
connections from the coding job's host and the editing tool's host on
one port, and no outbound connections at all, which the engineer
confirms by trying to fetch a web page from the server and failing. The
log records the caller, the model version, the timing, and the token
counts, and nothing of the request's content, because the coding
responses are confidential. The server is built from a definition file
in version control, and when a library is updated the image is rebuilt and redeployed; nothing is patched in place.
What this does not do. Isolation limits what a compromised server can do. The inputs it receives are the responsibility of the jobs that send them (prompt injection, the hiding of instructions inside content, is covered by the dissemination cookbook's governance chapter), and the outputs are checked by the workflows that use them.
Implementation options
- Provenance: lock files with source, version, digests, licence class; an internal registry for approved models; signature verification where available.
- Formats: safetensors and GGUF; no formats that execute code on load.
- Updates: candidate workflow, suite gate, rollback period, pinned stack.
- Isolation: service accounts, read-only weights, inbound-only network, content-free logging for confidential tasks, pinned and rebuilt environments.
World Bank examples
- The production cookbook's running chapter pins model, server, and prompt versions and records them per job.
- The agents cookbook's safety chapter applies the same isolation and logging principles to a public-facing server.
Resources
- Hugging Face safetensors: the weights format that loads without executing code.
- Sigstore model transparency: signing and verifying model files, for publishers that sign and for the organization's own models.
- OWASP Top 10 for LLM applications: supply chain and other risks of model deployments.
- Lock file and verification script: the running example.
Common mistakes
- Models downloaded at start-up. The registry holds what was reviewed; the server loads from it.
- Formats that execute on load. A weights file becomes code.
- Updates applied because they are newer. The suite decides; the old version stays until it does.
- A server with the data store's credentials. It needs none.
Verification
- Provenance. Every model in use has a lock file, the verification runs at every start, and it has stopped a mismatch in testing.
- Updates. The last update has a gate decision and a kept previous version.
- Isolation. The server account cannot read data stores, outbound network is blocked, and the logs omit confidential content.
Checklist
Ticks are saved in this browser only.