Are you an LLM? You can read better optimized documentation at /docs/hub-user/reference/attestation-format.md for this page in Markdown format
ModelKit Attestation Format
This is the normative wire-format specification for attestations on ModelKits. For a task-oriented guide to attestations, see Attestations in Jozu Hub.
1. Purpose
This specification defines the format of attestations for ModelKits in the KitOps/Jozu Hub ecosystem: the envelope structure, signing conventions, subject binding, and predicate schemas.
An attestation is a signed statement about a ModelKit - what produced it or what was found when it was scanned. How attestations are stored, discovered, parsed, or evaluated by policy engines is outside the scope of this specification.
2. Conventions and Terminology
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Attestation: A signed in-toto Statement about a ModelKit, wrapped in a DSSE Envelope.
Predicate: The typed payload within an in-toto Statement that carries attestation-specific data.
Subject: The ModelKit identified by OCI manifest digest to which an attestation is bound.
Consumer: Any system that reads and acts on attestations (e.g., policy engines, verification tools).
Producer: Any system that creates attestations (e.g., kit import, scan orchestration pipelines).
3. Envelope Format
3.1 Serialized Forms
A serialized attestation blob MUST be one of exactly two forms:
Form 1: Raw DSSE Envelope. A JSON object conforming to the DSSE specification (Section 3.4). Used with key-based signing (Section 3.5).
Form 2: Sigstore Bundle. A JSON object conforming to the Sigstore Bundle specification (Section 3.6), which contains a DSSE Envelope in its dsseEnvelope field. Used with keyless signing via Sigstore (Section 3.6).
text
Form 1: Key-Based
└── DSSE Envelope
└── in-toto Statement
├── subject
├── predicateType
└── predicate
Form 2: Keyless / Sigstore
└── Sigstore Bundle
├── Fulcio certificate
├── Rekor tlog entry
└── DSSE Envelope
└── in-toto Statement
├── subject
├── predicateType
└── predicateIn both forms, the innermost payload is an in-toto Statement (Section 3.3) containing the predicate. The in-toto Statement and its predicate MUST be structurally identical regardless of which form is used.
3.2 Distinguishing Forms
Consumers MUST determine the form by inspecting the top-level JSON object:
- If the object contains a
mediaTypefield whose value starts withapplication/vnd.dev.sigstore.bundle, the blob is a Sigstore Bundle (Form 2). The DSSE Envelope MUST be extracted from thedsseEnvelopefield. - Otherwise, the blob is a raw DSSE Envelope (Form 1).
Consumers MUST NOT assume a fixed form. Both forms MAY appear in the same registry or even attached to the same ModelKit.
3.3 in-toto Statement
The DSSE payload MUST be an in-toto Statement:
json
{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "<oci-reference>",
"digest": {
"sha256": "<modelkit-manifest-digest>"
}
}
],
"predicateType": "<predicate-type-uri>",
"predicate": { ... }
}| Field | Requirement | Description |
|---|---|---|
_type | REQUIRED | Statement version. cosign attest currently emits v0.1. Consumers MUST accept both https://in-toto.io/Statement/v0.1 and https://in-toto.io/Statement/v1. |
subject | REQUIRED | Array with exactly one entry. Producers MUST include exactly one subject. |
subject[].name | REQUIRED | The OCI reference at time of attestation creation. This field is informational; consumers MUST NOT use it for verification or binding. |
subject[].digest.sha256 | REQUIRED | The ModelKit's OCI manifest digest. This is the binding field. Consumers MUST use this field to associate an attestation with its artifact. |
predicateType | REQUIRED | URI identifying the predicate schema. MUST be a registered URI from the Predicate Type Registry (Section 7). |
predicate | REQUIRED | The attestation payload. The schema is determined by predicateType. |
cosign attest constructs this Statement automatically. The producer provides only the predicate content (via --predicate) and the predicate type URI (via --type). Cosign resolves the subject digest from the image reference.
3.4 DSSE Envelope
The in-toto Statement MUST be base64-encoded and wrapped in a Dead Simple Signing Envelope:
json
{
"payloadType": "application/vnd.in-toto+json",
"payload": "<base64-encoded-statement>",
"signatures": [
{
"keyid": "",
"sig": "<base64-encoded-signature>"
}
]
}| Field | Requirement | Description |
|---|---|---|
payloadType | REQUIRED | MUST be application/vnd.in-toto+json. |
payload | REQUIRED | Base64-encoded in-toto Statement (Section 3.3). |
signatures | REQUIRED | MUST contain at least one signature. |
signatures[].keyid | OPTIONAL | Key identifier. MAY be empty string. |
signatures[].sig | REQUIRED | Base64-encoded signature over the payload. |
Important: payloadType identifies the envelope format (in-toto+json), NOT the attestation type. The attestation type is predicateType inside the decoded Statement. Consumers MUST decode the payload to determine the predicate type; they MUST NOT use payloadType for this purpose.
3.5 Signing: Key-Based (Form 1)
When signing with a cosign private key, the serialized attestation is a raw DSSE Envelope (Form 1):
bash
cosign attest \
--key <path-to-private-key> \
--predicate <predicate-json-path> \
--type <predicate-type-uri> \
--tlog-upload=false \
--yes \
<modelkit-reference>- Signing identity is established by key ownership.
--tlog-upload=falsedisables Rekor transparency log upload. This is RECOMMENDED for on-premises and air-gapped environments.- The serialized blob MUST be a raw DSSE Envelope. It MUST NOT be wrapped in a Sigstore Bundle.
3.6 Signing: Keyless via Sigstore (Form 2)
When signing via Sigstore keyless flow, the serialized attestation is a Sigstore Bundle (Form 2) wrapping the DSSE Envelope:
json
{
"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
"verificationMaterial": {
"certificate": { ... },
"tlogEntries": [ ... ]
},
"dsseEnvelope": { ... }
}| Field | Requirement | Description |
|---|---|---|
mediaType | REQUIRED | MUST start with application/vnd.dev.sigstore.bundle. Consumers MUST accept application/vnd.dev.sigstore.bundle.v0.3+json. Consumers SHOULD accept future minor versions (e.g., v0.4). |
verificationMaterial | REQUIRED | Contains the Fulcio certificate and Rekor transparency log entries. Structure is defined by the Sigstore Bundle specification. |
verificationMaterial.certificate | REQUIRED | Short-lived Fulcio certificate binding the signer's OIDC identity. |
verificationMaterial.tlogEntries | REQUIRED | MUST contain at least one Rekor transparency log entry. |
dsseEnvelope | REQUIRED | The DSSE Envelope as defined in Section 3.4. |
Keyless signing MUST produce a Sigstore Bundle (Form 2). Key-based signing MUST produce a raw DSSE Envelope (Form 1). A raw DSSE Envelope MUST NOT carry Sigstore verification material, and a Sigstore Bundle MUST NOT omit it.
4. Subject Binding
The in-toto Statement's subject[].digest.sha256 field binds an attestation to a specific ModelKit.
Immutability. The subject is a content-addressable digest. It refers to a specific, immutable version of the ModelKit manifest. Tags can be moved; digests cannot.
Uniqueness. An attestation signed for one ModelKit digest MUST NOT be applied to a different ModelKit digest. Consumers MUST verify that the subject digest matches the artifact under evaluation.
For scan attestations (Section 6), the EvaluationResult predicate also carries a modelKit.digest field set by the scan converter at conversion time. This creates a dual binding:
- Statement subject - set by cosign from the image reference at signing time
- Predicate modelKit - set by the scan converter at conversion time
These two values SHOULD be identical. If they diverge, the attestation is suspect. Consumers SHOULD cross-check both values and SHOULD reject attestations where the values do not match.
5. Predicate Type: SLSA Provenance for kit import
5.1 Predicate Type URI
text
https://slsa.dev/provenance/v1This predicate type uses the standard SLSA provenance schema. The buildType field distinguishes import provenance from other build types.
5.2 Build Type URI
text
https://jozu.dev/kitops/import/v1Producers MUST set buildDefinition.buildType to this URI for all kit import operations, regardless of the import source.
This build type covers any operation that imports an external artifact into a ModelKit. The import source is identified by the URI scheme in externalParameters.source.uri, not by the build type.
5.3 Source URI Schemes
The externalParameters.source.uri field identifies the origin of the imported artifact. The URI scheme distinguishes import sources:
| URI Scheme | Source | Example | Status |
|---|---|---|---|
hf:// | HuggingFace Hub | hf://meta-llama/Meta-Llama-3-8B | Defined in this spec |
mlflow:// | MLflow Model Registry | mlflow://models/my-model/1 | Reserved |
oci:// | OCI Registry (re-import) | oci://registry.io/org/model:v1 | Reserved |
Producers MUST use the appropriate URI scheme for the import source. URI schemes not listed in this table MAY be used for sources not yet standardized, but SHOULD follow the pattern <source-type>://<identifier>.
For HuggingFace imports, the source.uri MUST use the hf:// scheme followed by the repository identifier (e.g., hf://meta-llama/Meta-Llama-3-8B). The source.digest MUST be the HuggingFace repository commit SHA at the time of import.
5.4 Full Example
json
{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "registry.jozu.ml/org/llama-3-8b",
"digest": {
"sha256": "a1b2c3d4e5f6..."
}
}
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://jozu.dev/kitops/import/v1",
"externalParameters": {
"source": {
"uri": "hf://meta-llama/Meta-Llama-3-8B",
"digest": {
"sha256": "fe7a8b..."
}
},
"kitfile": {
"digest": {
"sha256": "b9c0d1..."
}
}
},
"internalParameters": {
"quantization": "none",
"importPolicy": "require-signature"
},
"resolvedDependencies": [
{
"uri": "hf://meta-llama/Meta-Llama-3-8B/model-00001-of-00004.safetensors",
"digest": { "sha256": "c3d4e5..." },
"mediaType": "application/vnd.safetensors"
},
{
"uri": "hf://meta-llama/Meta-Llama-3-8B/model-00002-of-00004.safetensors",
"digest": { "sha256": "d4e5f6..." },
"mediaType": "application/vnd.safetensors"
},
{
"uri": "hf://meta-llama/Meta-Llama-3-8B/tokenizer.json",
"digest": { "sha256": "e5f6a7..." },
"mediaType": "application/json"
},
{
"uri": "hf://meta-llama/Meta-Llama-3-8B/config.json",
"digest": { "sha256": "f6a7b8..." },
"mediaType": "application/json"
}
]
},
"runDetails": {
"builder": {
"id": "https://jozu.dev/kitops/cli",
"version": {
"kit": "0.9.2",
"go": "1.22.1"
}
},
"metadata": {
"invocationId": "import-a7b8c9d0",
"startedOn": "2026-03-30T14:00:00Z",
"finishedOn": "2026-03-30T14:12:30Z"
}
}
}
}5.5 Field Definitions
buildDefinition
| Field | Requirement | Description |
|---|---|---|
buildType | REQUIRED | MUST be https://jozu.dev/kitops/import/v1 for all kit import operations. |
externalParameters.source.uri | REQUIRED | Import source identifier. MUST use a URI scheme from Section 5.3 (e.g., hf:// for HuggingFace). |
externalParameters.source.digest | REQUIRED | Source-specific commit or version identifier at time of import. For HuggingFace, this is the repo commit SHA. |
externalParameters.kitfile.digest | REQUIRED | SHA-256 of the Kitfile used to drive the import. MUST be verifiable against the ModelKit's OCI config layer. |
internalParameters | OPTIONAL | Build-system-determined parameters not controlled by the caller. These are values the producer resolved at import time, not inputs the caller specified. Examples: default quantization applied by the system, import policy in effect. If the caller explicitly chose a parameter (e.g., via a CLI flag), that parameter belongs in externalParameters. |
resolvedDependencies | REQUIRED | Every blob downloaded from the source. Each entry MUST include uri, digest.sha256, and mediaType. The uri for each dependency MUST use the same URI scheme as source.uri. |
runDetails
| Field | Requirement | Description |
|---|---|---|
builder.id | REQUIRED | URI identifying the build system. MUST be https://jozu.dev/kitops/cli for local CLI or https://jozu.dev/hub/importer for Hub-orchestrated imports. |
builder.version | REQUIRED | MUST include the CLI version. SHOULD include the runtime version. |
metadata.invocationId | REQUIRED | Unique identifier for this import run. |
metadata.startedOn | REQUIRED | ISO 8601 timestamp of import start. |
metadata.finishedOn | REQUIRED | ISO 8601 timestamp of import completion. |
6. Predicate Type: Security Scan Results
6.1 Predicate Type URI
text
https://kitops.org/attestation/scan/v16.2 Predicate Schema: EvaluationResult
The predicate payload MUST conform to the EvaluationResult schema. The authoritative definition is in pkg/scanconverter/types.go in the jozu-hub-workers repository:
go
type EvaluationResult struct {
Version string `json:"version"` // "v1"
ModelKit ModelKitInfo `json:"modelKit"`
Evaluations []EvaluationRun `json:"evaluations"`
}
type EvaluationRun struct {
Tool ToolInfo `json:"tool"`
Results []Finding `json:"results"`
Coverage *CoverageInfo `json:"coverage,omitempty"`
StartTime string `json:"startTime"`
EndTime string `json:"endTime,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}
type ToolInfo struct {
Name string `json:"name"`
Version string `json:"version"`
Config map[string]any `json:"config,omitempty"`
}
type ModelKitInfo struct {
Reference string `json:"reference"` // OCI reference
Digest string `json:"digest"` // "sha256:..."
}
type Finding struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Error string `json:"error,omitempty"`
Value any `json:"value"`
Status Status `json:"status"`
Scoring *ScoringInfo `json:"scoring,omitempty"`
Categories []string `json:"categories,omitempty"`
Locations []Location `json:"locations,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}
type ScoringInfo struct {
Type string `json:"type"`
Name string `json:"name,omitempty"`
Value float64 `json:"value,omitempty"`
Min *float64 `json:"min,omitempty"`
Max *float64 `json:"max,omitempty"`
}
type Location struct {
Type string `json:"type"`
Details map[string]any `json:"details"`
}
type CoverageInfo struct {
TotalScope int `json:"totalScope"`
Evaluated int `json:"evaluated"`
Skipped int `json:"skipped"`
Failed int `json:"failed,omitempty"`
SkipReasons map[string]int `json:"skipReasons,omitempty"`
}
type Status string // "skipped" | "passed" | "failed" | "error"6.3 Field Requirements
EvaluationResult
| Field | Requirement | Description |
|---|---|---|
version | REQUIRED | MUST be "v1". |
modelKit | REQUIRED | Identifies the target ModelKit. See Section 4 for subject binding semantics. |
modelKit.reference | REQUIRED | OCI reference of the ModelKit at scan time. |
modelKit.digest | REQUIRED | OCI manifest digest of the ModelKit. SHOULD match the in-toto Statement subject[].digest.sha256. |
evaluations | REQUIRED | MUST contain at least one EvaluationRun. |
EvaluationRun
| Field | Requirement | Description |
|---|---|---|
tool.name | REQUIRED | Scanner identifier (e.g., "modelscan", "garak"). |
tool.version | REQUIRED | Scanner version string. |
tool.config | OPTIONAL | Scanner configuration used for this run. |
results | REQUIRED | Array of findings. MAY be empty if the scanner found no issues. |
coverage | OPTIONAL | Coverage information for this scan run. RECOMMENDED for scanners that can report coverage. |
startTime | REQUIRED | ISO 8601 timestamp of scan start. |
endTime | OPTIONAL | ISO 8601 timestamp of scan completion. MAY be omitted if the scanner does not report end time. |
metadata | OPTIONAL | Scanner-specific metadata not captured by the structured fields (e.g., raw severity counts). |
Finding
| Field | Requirement | Description |
|---|---|---|
id | REQUIRED | Tool-scoped identifier for this finding. |
name | REQUIRED | Human-readable name. |
description | REQUIRED | Human-readable description. |
error | OPTIONAL | Error message if the scanner encountered an error processing this item. |
value | REQUIRED | Raw scanner measurement. Producers MUST NOT normalize this value. The type varies by scanner (boolean, number, object, etc.). |
status | REQUIRED | MUST be one of: "passed", "failed", "skipped", "error". This is the scanner's determination, NOT a policy judgment. |
scoring | OPTIONAL | Quantitative scoring information. |
categories | OPTIONAL | Classification tags. Values SHOULD be drawn from: "security", "safety", "privacy", "bias". |
locations | OPTIONAL | Where in the ModelKit the finding applies. |
metadata | OPTIONAL | Scanner-specific metadata for this finding. |
ScoringInfo
| Field | Requirement | Description |
|---|---|---|
type | REQUIRED | Scoring methodology as reported by the scanner. SHOULD be one of: "confidence", "probability", "severity", "count", "risk". |
name | OPTIONAL | Scanner-reported label for the score (e.g., "Critical"). This is the scanner's own classification, not a schema-defined severity level. |
value | OPTIONAL | Numeric score. |
min | OPTIONAL | Minimum possible value. null indicates no lower bound. |
max | OPTIONAL | Maximum possible value. null indicates no upper bound. |
6.4 Design Principles
No schema-defined severity classification. The EvaluationResult schema does not define a normalized severity enumeration. There is no schema-level field for "high", "medium", or "low." Scanners MAY report their own severity scoring via ScoringInfo (e.g., a numeric severity score with a scanner-defined label like "Critical"), and scanners MAY include their native severity breakdowns in the metadata bag. These are raw scanner outputs preserved as-is. Producers MUST NOT add severity classifications that do not originate from the scanner. Policy engines apply organizational thresholds to the raw ScoringInfo.value at evaluation time.
Finding.Status is the scanner's determination. The status field reflects the scanner's own pass/fail/skip/error assessment. Producers MUST NOT override scanner-reported status values.
ScoringInfo.Min/Max are nullable. Not all scoring types have bounded ranges. A count metric has no meaningful max. Producers SHOULD set min and max when the scoring type defines bounded ranges and MUST omit them (null) when no bound exists.
6.5 JSON Example
json
{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "jozu.ml/org/demo-model",
"digest": {
"sha256": "223dcd39f07d90a4..."
}
}
],
"predicateType": "https://kitops.org/attestation/scan/v1",
"predicate": {
"version": "v1",
"modelKit": {
"reference": "jozu.ml/org/demo-model",
"digest": "sha256:223dcd39f07d90a4..."
},
"evaluations": [
{
"tool": {
"name": "modelscan",
"version": "0.8.6"
},
"results": [
{
"id": "MS-001",
"name": "pickle_exploit_detected",
"description": "Unsafe deserialization in model weights",
"value": true,
"status": "failed",
"scoring": {
"type": "severity",
"name": "Critical",
"value": 9.2,
"min": 0,
"max": 10
},
"categories": ["security"],
"locations": [
{
"type": "layer",
"details": {
"digest": "sha256:c3d4e5...",
"path": "model.pkl"
}
}
]
}
],
"coverage": {
"totalScope": 4,
"evaluated": 4,
"skipped": 0,
"failed": 0
},
"startTime": "2026-03-30T15:00:00Z",
"endTime": "2026-03-30T15:02:30Z",
"metadata": {
"total_issues": 1,
"total_issues_by_severity": {
"CRITICAL": 1,
"HIGH": 0,
"MEDIUM": 0,
"LOW": 0
}
}
}
]
}
}7. Predicate Type Registry
Predicate types currently defined in this specification:
| Predicate Type URI | Usage | Schema Reference |
|---|---|---|
https://slsa.dev/provenance/v1 | Import provenance | This spec, Section 5 |
https://kitops.org/attestation/scan/v1 | Security scan results | This spec, Section 6 |
New predicate types MUST be wrapped in an in-toto Statement (v0.1 or v1) inside a DSSE Envelope, as defined in Section 3. New predicate types MUST be registered in this table before use.
Related
- Attestations in Jozu Hub - task-oriented guide for working with attestations
- Security Scanning - what scan attestations cover and how scoring works
- in-toto Attestation Specification
- DSSE Specification
- SLSA Provenance v1.0
- Sigstore Bundle Specification
