Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.kaireonai.com/llms.txt

Use this file to discover all available pages before exploring further.

What it does

Every call to GET /api/v1/decisions/:id/provenance returns a canonicalised provenance bundle (decision trace + model snapshots + audit chain + SLSA attestation + fairness slice). When the deployment is configured for sigstore signing, the bundle is also signed with cosign sign-blob and the detached signature lands in the response header:
X-Provenance-Digest: 7c3e…  (sha256 of the canonical bundle)
X-Provenance-Signature: MEUCIQDk…   (cosign sign-blob output)
X-Provenance-Schema: kaireon.provenance.v1
When signing is not configured, the same bundle is returned with:
X-Provenance-Signature: unsigned
so consumers can distinguish a key-bound bundle from a dev / unsigned bundle without re-parsing the body.

Configuration

Two env vars on the runtime container:
Env varRequiredMeaning
COSIGN_KEYyes (for signing)Cosign-format private key bytes (e.g., the contents of cosign.key your operator generated with cosign generate-key-pair). Cosign accepts the raw bytes via the env:// URI scheme — see below.
COSIGN_BINARYnoOverride the binary path (default: cosign on PATH). The image installs cosign at /usr/local/bin/cosign.
Optional sigstore-transparency env vars (Rekor upload, Fulcio OIDC) are honoured but not required — the V1 wire ships offline-key signing only. Keyless / Fulcio / OIDC support is a follow-up.

How the signing works

lib/supply-chain/cosign-sign-blob.ts invokes:
cosign sign-blob \
    --key=env://COSIGN_KEY \
    --output-signature - \
    --yes \
    -                          # blob payload via stdin
Three discipline rules are enforced by code:
  1. spawn only — never exec. No shell is invoked, so there is no command-injection surface. The full argv is a fixed array of constants.
  2. The signing key never appears on argv. Cosign reads the key bytes from the env var named by the env://COSIGN_KEY URI; only the URI string lives in argv.
  3. The blob payload arrives via stdin. No tempfile, no argv leak. The canonical-JSON bundle is piped to the child’s stdin and the stream is closed immediately.
A unit test at src/lib/supply-chain/__tests__/cosign-sign-blob.test.ts asserts that the secret bytes never appear in spawn’s argv (it scans every argv entry for the literal key value).

Failure modes (fail-soft to “unsigned”)

ConditionHeader valueAudit log changes field
COSIGN_KEY env unsetunsignedsigned: false (no signatureFailureCode)
Cosign binary missing on PATHunsignedsigned: false, signatureFailureCode: "binary_missing"
Cosign exited non-zerounsignedsigned: false, signatureFailureCode: "exit_nonzero"
Cosign exited 0 but with empty stdoutunsignedsigned: false, signatureFailureCode: "exit_nonzero"
30s spawn timeoutunsignedsigned: false, signatureFailureCode: "timeout"
spawn-time error (EACCES, ENOMEM, etc.)unsignedsigned: false, signatureFailureCode: "spawn_error"
Stderr from cosign is captured (capped at 8 KB) and the first 256 chars are written to the audit-log diagnostics field — never to the response body, never to the response header.

Image layout

The runtime stage of platform/Dockerfile installs cosign:
ARG COSIGN_VERSION=v2.4.1
RUN apk add --no-cache curl ca-certificates && \
    arch="$(uname -m)" && \
    case "$arch" in \
        x86_64)  cosign_arch="amd64";; \
        aarch64) cosign_arch="arm64";; \
        *) echo "unsupported arch: $arch"; exit 1;; \
    esac && \
    curl --fail --silent --show-error --location \
        --output /usr/local/bin/cosign \
        "https://github.com/sigstore/cosign/releases/download/${COSIGN_VERSION}/cosign-linux-${cosign_arch}" && \
    chmod 0755 /usr/local/bin/cosign && \
    /usr/local/bin/cosign version && \
    apk del curl
We deliberately do not hard-code a sha256 in the Dockerfile because any value baked in here would be unverifiable without network access during the review. The cosign release artefacts are themselves sigstore-signed; the trustworthy verification path is to run cosign verify-blob against the sigstore root after image build, as a separate CI step. That bootstrap is operator-authorised follow-up.

Operational checklist

  1. Generate the key pair with cosign generate-key-pair (cosign prompts for a password — set COSIGN_PASSWORD env when scripting).
  2. Store cosign.key and the password in your secret store (e.g., AWS Secrets Manager, App Runner secrets).
  3. Inject as COSIGN_KEY and COSIGN_PASSWORD env vars on the runtime container.
  4. Hit /api/v1/decisions/:id/provenance and verify the response includes X-Provenance-Signature: <base64> (not unsigned).
  5. Verify offline:
    curl -fsS .../api/v1/decisions/$ID/provenance > bundle.json
    curl -fsS .../api/v1/decisions/$ID/provenance -i | grep X-Provenance-Signature \
        | sed 's/^[^:]*: //' | tr -d '\r' > sig.b64
    cosign verify-blob --key cosign.pub --signature sig.b64 bundle.json
    
  6. Audit-log spot-check: auditLog.changes.signed should be true for every successful bundle. A signatureFailureCode field means the wire intended to sign but couldn’t — page whoever owns the image / key-management layer.

Roadmap

  • Keyless signing (Fulcio + OIDC + Rekor upload) so the deployment does not need a long-lived private key.
  • Image-time cosign verify-blob of the cosign release artefact itself (closes the bootstrap-trust gap noted above).
  • Reproducible-build verification step that re-canonicalises the bundle and re-signs against a CI-only key, comparing digests.
See also: Decision provenance bundle · Compliance