Skip to main content

modelstamp

Model files with receipts.

Tests Python 3.8–3.13 License: MIT

Documentation · Benchmarks · Security policy

modelstamp adds a verifiable environment manifest to persisted Python machine learning models. It keeps the familiar pickle or joblib workflow while making dependency changes and artifact corruption visible before deserialization.

Loading a persisted model under different dependency versions is unsupported and may fail or behave differently. The scikit-learn documentation therefore recommends preserving the training environment alongside the model. modelstamp packages that practice into a small API.

Why modelstamp?

A normal model.pkl remembers the fitted object, but not the environment that made it work. modelstamp adds the missing receipt:

  • Integrity: detect truncated, replaced, or corrupted artifacts.
  • Compatibility: identify Python and relevant dependency changes.
  • Traceability: record model details, metadata, time, and optional Git state.
  • Familiarity: keep using pickle or joblib through save() and load().

Installation

pip install modelstamp

Install joblib support explicitly when scikit-learn is not already installed:

pip install "modelstamp[joblib]"

Save and load

import modelstamp as ms

manifest = ms.save(
    model,
    "model.joblib",
    metadata={"validation_roc_auc": 0.883},
)

model, manifest = ms.load("model.joblib")

Saving creates two files:

model.joblib
model.joblib.manifest.json

The manifest records:

  • SHA-256 and byte size of the artifact
  • pickle or joblib serialization backend
  • model class and scikit-learn pipeline components
  • Python, platform, and relevant package versions
  • creation time and optional Git commit/worktree status
  • caller-provided JSON metadata

load() verifies the artifact before deserializing it. It then compares the current runtime with the saved runtime and warns when a relevant dependency has changed.

Operations targeting the same artifact are serialized across local processes. During loading, verification and deserialization use the same open file so a concurrent replacement cannot bypass the digest check.

Mismatch policy

# Default: verify, then warn about environment changes.
model, manifest = ms.load("model.joblib")

# Refuse to load when the runtime differs.
model, manifest = ms.load("model.joblib", on_mismatch="raise")

# Verify integrity but skip the environment warning.
model = ms.load(
    "model.joblib",
    on_mismatch="ignore",
    return_manifest=False,
)

Integrity failures always raise ArtifactIntegrityError; on_mismatch does not disable the digest check.

Inspect without loading

manifest = ms.inspect("model.joblib")
report = ms.check("model.joblib")
ms.verify("model.joblib")

The same operations are available from the command line:

modelstamp inspect model.joblib
modelstamp check model.joblib
modelstamp verify model.joblib

You can also use python -m modelstamp in environments where the console script is not on PATH. inspect validates the manifest structure but does not authenticate its contents; use verify or check when trust matters.

check exits with status 0 for a clean artifact, 1 for a compatibility or integrity mismatch, and 2 when the manifest cannot be read.

Signed manifests

A checksum detects accidental corruption, but someone who can replace both files can also create a matching checksum. For artifacts crossing a trust boundary, sign the manifest with a secret key:

import os
import modelstamp as ms

key = os.environ["MODELSTAMP_SIGNING_KEY"].encode()
ms.save(
    model,
    "model.joblib",
    signing_key=key,
    key_id="production-2026-q3",
)
model, manifest = ms.load("model.joblib", signing_key=key)

The signature is an HMAC-SHA-256 over the complete manifest, including the artifact digest. A signed artifact cannot be loaded or verified without its key. Supplying a key also rejects an unsigned manifest, preventing silent downgrades. Keep the key outside source control and separate from the artifact.

HMAC is symmetric, so anyone who can verify with the shared secret can also forge a valid manifest. modelstamp does not currently provide asymmetric public-key verification such as Ed25519 or Sigstore.

For CLI verification, name the environment variable containing the key:

modelstamp verify model.joblib --signing-key-env MODELSTAMP_SIGNING_KEY

For key rotation, verify through a registry. The authenticated key_id chooses the correct secret without changing old artifacts:

keys = {
    "production-2026-q2": old_key,
    "production-2026-q3": current_key,
}
model, manifest = ms.load("model.joblib", signing_keys=keys)

See the signing and key-rotation guide for the migration and security model.

Complete scikit-learn example

from pathlib import Path

from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

import modelstamp as ms

X, y = load_iris(return_X_y=True)
pipeline = make_pipeline(StandardScaler(), LogisticRegression(max_iter=500))
pipeline.fit(X, y)

path = Path("iris.joblib")
ms.save(pipeline, path, metadata={"dataset": "iris"})
restored, manifest = ms.load(path)
print(restored.predict(X[:3]))
print(manifest.relevant_packages)

# Safe inspection does not deserialize the model.
print(ms.check(path))

# Corruption is detected before pickle/joblib can execute anything.
path.write_bytes(path.read_bytes() + b"changed")
try:
    ms.verify(path)
except ms.ArtifactIntegrityError as exc:
    print(f"Rejected: {exc}")

API at a glance

Operation Purpose Deserializes the model?
save(model, path) Save an artifact and its environment receipt No
load(path) Verify, compare environments, and load Yes
verify(path) Check artifact size and SHA-256 No
check(path) Check integrity and runtime compatibility No
inspect(path) Read schema-validated, unauthenticated manifest metadata No

Security boundary

Pickle and joblib can execute code during loading. The SHA-256 recorded by modelstamp detects accidental changes and mismatched sidecars; it is not a digital signature and does not make an untrusted model safe. Only load artifacts from sources you trust. For a safer serialization format, consider skops.io or ONNX where they fit your model.

Security issues should be reported according to the security policy.

Supported Python versions

Python 3.8 through 3.13 are declared for the initial release. The package has no required runtime dependency; joblib is optional.

Development

python -m pip install ".[dev]"
pytest
ruff format --check .
ruff check .
python -m build
twine check dist/*
mkdocs build --strict

GitHub Actions runs the test suite on Python 3.8 through 3.13. Publishing is configured for PyPI Trusted Publishing and runs when a GitHub release is published.

pyproject.toml is the single source of truth for the package version. Update only its project.version value when preparing a release; modelstamp.__version__ reads the resulting installed distribution metadata.

Property-based tests exercise malformed manifest structures. Verification throughput for representative artifact sizes is recorded in BENCHMARKS.md.

Contributions are welcome. See the contribution guide for the local development and pull-request workflow.

Maintainer

Anagha Dhekne

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

modelstamp-0.1.0.tar.gz (137.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

modelstamp-0.1.0-py3-none-any.whl (18.8 kB view details)

Uploaded Python 3

File details

Details for the file modelstamp-0.1.0.tar.gz.

File metadata

  • Download URL: modelstamp-0.1.0.tar.gz
  • Upload date:
  • Size: 137.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for modelstamp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 310e2e329c23572931c4e8540b418b659598284974cfa6a7f9b375549a6d4d8e
MD5 570757f182b0788a3a3bf203a5281796
BLAKE2b-256 bbb5ae4b8b773cb822a20f7851e226d31e69620779455011a0e65355becde7f4

See more details on using hashes here.

Provenance

The following attestation bundles were made for modelstamp-0.1.0.tar.gz:

Publisher: publish.yml on AnaghaDhekne/modelstamp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file modelstamp-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: modelstamp-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for modelstamp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5e9e52f289ac00c72197a0114e00224a1506028bc405537e878170c83178c489
MD5 7ad6c1ad3ded8b083c11d45ad19081c6
BLAKE2b-256 967e6677b9d96712db5b2d475f74f32c3174cc572f1611bc3b214640a6d05161

See more details on using hashes here.

Provenance

The following attestation bundles were made for modelstamp-0.1.0-py3-none-any.whl:

Publisher: publish.yml on AnaghaDhekne/modelstamp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

This release

0.1.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page