Skip to main content
View Markdown ↗

Copy this page

Select and copy the Markdown below, then paste it into your LLM.

On this page

structured-data attestor

Generic JSON-ingestion attestor: reads a JSON file the wrapped command produced, canonicalizes it (RFC 8785 JCS) for a stable digest, selects in-toto subjects with an RFC 9535 JSONPath subset, and emits a signed envelope keyed on those subjects.

It is not a general import for JSON you already hold. It records bytes a command wrote during the run, so the signed claim covers that run and what it wrote, not who originally produced the data. See What the signature proves.

FieldValue
Namestructured-data
Predicate typehttps://aflock.ai/attestations/structured-data/v0.1
Lifecyclepostproduct
Default binary?Yes

What it captures

The Predicate struct defines the signed payload:

JSON fieldGo typeSource
dataTypestringRecipe-supplied label for the data shape (free-form).
collectedAttime.TimeUTC timestamp at attestation time.
subjectQuerystringThe JSONPath used to select subjects.
subjectPrefixstring (omitempty)Optional prefix applied to each subject key.
subjectCountintNumber of subjects derived from the query.
datajson.RawMessage (omitempty)The full JCS-canonical bytes — only when embed-data is true.
dataDigeststringSHA-256 (hex) of the JCS-canonical encoding of the data file.
subjectDigestsmap[string]string (omitempty)Per-subject key → sha256(identity) map.
sourceFilestring (omitempty)Product path the bytes were read from.
sourceDigestcryptoutil.DigestSet (omitempty)Re-computed digest of the source file (verified against the product digest before reading).

When to use

When you have JSON evidence from a source that doesn't have a dedicated attestor (Kratos admin-API responses, Splunk saved-search results, Snyk project exports, custom compliance API outputs) and shipping a bespoke attestor would be overkill. The combination of JCS canonicalization plus JSONPath subject selection lets policy join across attestations on stable identity values rather than file hashes.

Use it to recapture: run the command that fetches or produces the JSON under cilock run, so the attestation records that run. If the source has a dedicated attestor (for example sarif or sbom), prefer that: it understands the format instead of treating it as opaque JSON.

What the signature proves

  • Proves: the holder of the signing credential signed cilock's claim that the wrapped command wrote this file, that its JCS-canonical bytes hash to dataDigest, and that the selected identity values are exactly the listed subjects. Whether that credential belongs to the person or system that ran the command depends on how you issue it: a shared file key proves less than a keyless CI identity. The source file is re-digested before it is read, so a file swapped mid-run is refused.
  • Does not prove: that the data is true, complete, or came from the system it claims to describe. The signer is the credential cilock signed with, not the upstream API or tool. If the upstream source signs its own output, keep and verify that signature separately; this attestor does not carry it forward.
  • Does not cover a file that existed before the run. A --attestor-structured-data-data-file that is not a product of this run is refused with configured data-file "<path>" not in product list. Copying a pre-existing file inside the run makes it a product, but then the attestation records only the copy, not the file's earlier history.

Flags

Each option is a cilock run flag (listed under cilock run --help-advanced) and a package-level With* ConfigOption for recipes and embedders:

FlagOptionRequiredPurpose
--attestor-structured-data-data-fileWithDataFile(path)NoProduct path to attest over. It must be a product of this run. If unset, the first product with a JSON MIME type in sorted path order is picked.
--attestor-structured-data-subject-queryWithSubjectQuery(jsonpath)YesRFC 9535 JSONPath expression selecting subject identity values. Attest errors if empty.
--attestor-structured-data-subject-prefixWithSubjectPrefix(prefix)NoString prepended to each subject key.
--attestor-structured-data-data-typeWithDataType(label)NoFree-form label written into dataType.
--attestor-structured-data-embed-dataWithEmbedData(bool)NoWhen true, include the canonical bytes in predicate.data. Default false, so only the digest is recorded.

Output shape

The fields sit under a top-level predicate object:

{
  "predicate": {
    "dataType": "kratos.identities.v1",
    "collectedAt": "2026-05-21T14:02:11Z",
    "subjectQuery": "$.identities[*].id",
    "subjectPrefix": "kratos:identity:",
    "subjectCount": 3,
    "dataDigest": "9f2c…",
    "subjectDigests": {
      "kratos:identity:abc-123": "ba78…",
      "kratos:identity:def-456": "1d44…"
    },
    "sourceFile": "kratos-identities.json",
    "sourceDigest": { "sha256": "…" }
  }
}

A Rego rule reads them as input.predicate.dataDigest, input.predicate.subjectDigests, and so on.

Gotchas

  • Products only. resolveDataFile looks only at this run's products: an explicit data file that is not one is refused. Without one, the attestor walks products in sorted path order and takes the first JSON product it meets. With several JSON products, name the one you mean.
  • JSON only. resolveDataFile matches products by strings.Contains(p.MimeType, "json"). YAML, XML, and plain-text products are not picked up — pre-convert to JSON in the recipe.
  • Subject identity is the path's value, not the path. Subjects are keyed subjectPrefix + stringifyIdentity(value) with digest = sha256(identity-string). This matches the convention shared with prowler, aws-config, asff, and steampipe so cross-attestation graph joins work.
  • Only scalar matches become subjects. Strings, JSON numbers (whole numbers render as integers, never scientific notation), and booleans ("true"/"false") are kept. Maps, arrays, and nulls are logged at debug and dropped.
  • Subject query is required. Calling Attest without WithSubjectQuery errors out — there is no default selector.
  • Source file is re-digested on read. The attestor recomputes the file digest under the context's hash list and compares it to the product's recorded digest; a mismatch errors with "concurrent write?".
  • Data is not embedded by default. Without WithEmbedData(true), only dataDigest is signed. Verifiers needing the raw values must keep the source file out-of-band or opt in.
  • Each envelope is exported standalone. Export() returns true, so per-recipe envelopes are addressable independently rather than merged into the parent Collection — designed for FedRAMP 20x per-KSI VSA consumption.

CLI example

Fetch the data inside the run so the file is one of its products, then select subjects from it. Offline, with a local signing key:

cilock run --step kratos-identities --offline -k key.pem \
  -a structured-data \
  --attestor-structured-data-subject-query '$.identities[*].id' \
  --attestor-structured-data-subject-prefix 'kratos:identity:' \
  --attestor-structured-data-data-type 'kratos.identities.v1' \
  -o identities.att.json \
  -- sh -c 'curl -fsS "$KRATOS_ADMIN/admin/identities" | jq "{identities: .}" > identities.json'

The structured-data envelope is exported on its own, beside the collection, as identities.att.json-structured-data.json. Its subjects are kratos:identity:<id> for each id the query selects. Drop --offline and -k to sign keylessly through the platform instead.

This was run with the released cilock 4.6.2: a two-identity file produced two kratos:identity: subjects and a dataDigest over the canonical bytes, and the same flags pointed at a file created before the run were refused with configured data-file "identities.json" not in product list.

See also

Reference generated from the product documentation. Match commands and support details to your installed release.