Polyaxon v3 is coming →

Scan model artifacts before adding them to a registry

Add static model artifact scanning before registry promotion, preserve scan evidence, check coverage, and bind approval to immutable artifact digests.

August 27, 2026by Polyaxon
AI security: model artifact scanning, illustrated by a padlock and a model file under a magnifying glass on a dark green background.

Scan a model artifact before loading it into an execution environment or making it available through a registry. Keep the candidate in a restricted staging location, inspect the files and their provenance, and bind any approval to the exact artifact bytes that were reviewed.

Static scanning and behavioral evaluation answer different questions. A scanner can identify suspicious file structures or known risky patterns without establishing how a model behaves on every input. A model that passes an evaluation can still arrive in an unsafe package. Use both checks when the deployment requires them.

Define the package being reviewed

Inventory the weights, configuration, tokenizer files, custom code, and auxiliary assets required to load the candidate. Record the source, revision, expected formats, and file digests. Scanning one weight file while later downloading unreviewed loader code leaves part of the execution path outside the review.

Put the candidate in an immutable or access-controlled snapshot before scanning. Give the scanning job read access to that snapshot and a separate writable location for reports. Do not give it production registry promotion credentials.

Prefer inspection that does not deserialize or execute model content. ModelAudit's project documentation distinguishes static metadata inspection from trusted-loader modes. Do not enable a loader merely to make an untrusted artifact easier to inspect.

Start with a known local fixture

You can test the scanning workflow without downloading a model. This Python script writes a minimal SafeTensors file containing one floating-point value:

import json
from pathlib import Path
import struct

header = json.dumps({
    "__metadata__": {"format": "pt"},
    "weight": {"dtype": "F32", "shape": [1], "data_offsets": [0, 4]},
}, separators=(",", ":")).encode("utf-8")
header += b" " * ((8 - len(header) % 8) % 8)
Path("fixture.safetensors").write_bytes(
    struct.pack("<Q", len(header)) + header + struct.pack("<f", 1.0)
)

In a separate environment with Python 3.10–3.13, install and run the pinned ModelAudit release:

python3 -m venv .venv
.venv/bin/python -m pip install modelaudit==0.2.52
PROMPTFOO_DISABLE_TELEMETRY=1 .venv/bin/modelaudit scan \
  --format json fixture.safetensors > scan-results.json

The local check for this article used ModelAudit 0.2.52 and Python 3.12.0 on macOS. It scanned one 108-byte fixture, reported no issues or scan errors, and returned exit code 0. This verifies the fixture and command path, not the safety of a production model. JSON was redirected from standard output because that release's --output path handling encountered a macOS compatibility error in the test environment.

Preserve exit status and scan coverage

ModelAudit's CLI documentation distinguishes no findings, security findings, and scan errors. In the documented interface, those correspond to exit codes 0, 1, and 2. Treat unexpected exits, a missing report, or invalid JSON as execution failures as well.

Capture the report even when the command returns nonzero. In a shell-based job, this pattern retains the original status:

scan_status=0
PROMPTFOO_DISABLE_TELEMETRY=1 .venv/bin/modelaudit scan \
  --format json ./candidate > scan-results.json || scan_status=$?
printf '%s\n' "$scan_status" > scan-exit-code.txt
# Preserve both files as run artifacts before the job returns this status.
exit "$scan_status"

An exit code alone is not a coverage report. Compare the expected inventory with the files and formats actually inspected. Review unsupported formats, skipped files, parser errors, timeouts, size limits, and archive contents. A clean result for a supported subset does not approve the rest of the package.

Define how findings affect promotion. A conservative initial policy is to require review for every finding and block incomplete scans. If you allow exceptions, bind each exception to a specific finding and artifact digest with an owner and expiration.

Prevent the candidate from changing after review

The scan, review, and registration steps must refer to the same snapshot. If a candidate path can be overwritten between those steps, an approval can accidentally apply to different bytes.

Use content-addressed or immutable storage where available. Record a digest manifest in the scan report and verify the selected artifact identities again when registering the version. For a directory, include every required file and use a deterministic manifest order. Document how symlinks and external references are handled rather than silently following them into unreviewed content.

Keep provenance separate from scanning. A digest establishes content identity; it does not establish who produced the artifact or whether that producer is trusted. Retain source and build evidence alongside the digest.

Connect scanning to Polyaxon registry workflows

Run the scanner as a job, preserving the report, exit status, inventory, and digest manifest through artifact logging. Record the scanner version and policy revision with the run.

Use a separate pipeline step to evaluate coverage and review status before registering a model version. Connect the registered version to its producing and scanning runs. Polyaxon provides the execution, tracking, and registry workflow; your pipeline must implement the scanning policy and promotion decision.

Re-scan when the package changes or a scanner update adds relevant coverage. Preserve earlier reports so reviewers can distinguish a newly detected issue from a newly introduced artifact change.

Continue with the model registry path and MLOps foundations. For behavior after loading, use LLM evaluation and AI red teaming.