Image Signing & Verification

Verifying Aperion images before they run

Every image in registry.aperion.ai is cosign-signed at push time with a long-lived Aperion keypair. The public verification key is published openly so your build pipeline, CISO, auditor, or regulator can prove independently that the binary you are about to run was signed by Aperion and has not been tampered with in transit.

Registry
registry.aperion.ai
Public key
docs.aperion.ai/cosign.pub
Signing tool
cosign (Sigstore)
Algorithm
ECDSA P-256 + SHA-256
Maps to
Annex IV §1(b) · NIS2 21(2)(d)
Quick start

1 Authenticate with the private registry

Every customer is issued a per-tenant pull credential from the licensing dashboard. The username + password are scoped, revocable, and tracked in the audit log.

docker login registry.aperion.ai \
  -u acme-corporation-1dd0 \
  -p 'df8c9488ae571001e4819add2760eb7c79918a1dbaaf414da720e075536ab609'
Login Succeeded
Lost your credential?

Contact [email protected] and we will mint a new one and revoke the old one. Aperion ops never re-issue an existing credential — every reissue is a new bcrypt-hashed secret with a new audit-log entry.

2 Install cosign on the host that runs your CI / admission controller

Cosign is a single static binary. No daemon, no service account, no privileged install.

# Linux x86_64
curl -fsSL -o /usr/local/bin/cosign \
  https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
chmod +x /usr/local/bin/cosign
cosign version

macOS: brew install cosign · Windows: download cosign-windows-amd64.exe from the same release page.

3 Pin the Aperion public key

Fetch the published key once and commit it into your CI repository so a future-you can verify what was signed today even if docs.aperion.ai is unreachable. The key is short, plain text, and a perfect candidate for source control.

curl -fsSL https://docs.aperion.ai/cosign.pub -o /etc/aperion-cosign.pub
chmod 644 /etc/aperion-cosign.pub
cat /etc/aperion-cosign.pub
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6RbqCbiJFe7JiLUwf2flWF8pUL0R
b75JOA7DZRiPfXifz8y3NuIOEeNl3q7hG9u5mluzg2SKOjvOMKIN8bPldw==
-----END PUBLIC KEY-----

Fingerprint: Always confirm the key fingerprint matches the value Aperion publishes in the latest release notes before trusting it for the first time.

sha256sum /etc/aperion-cosign.pub
184aea9844e0c9d3ca296bb8b22135a203a406422a69fd8a072ac35983707482  /etc/aperion-cosign.pub

4 Verify an image

One command, exit code 0 or 1.

cosign verify --key /etc/aperion-cosign.pub \
  registry.aperion.ai/aperion/safechat-enterprise:v1.8.0
Verification for registry.aperion.ai/aperion/safechat-enterprise:v1.8.0 --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - Existence of the claims in the transparency log was verified offline
  - The signatures were verified against the specified public key

[{"critical":{"identity":{"docker-reference":"registry.aperion.ai/aperion/safechat-enterprise:v1.8.0"},
"image":{"docker-manifest-digest":"sha256:0811776648b20db914385db8df68e18ac018874429c89958f85f0014b7a9211c"},
"type":"https://sigstore.dev/cosign/sign/v1"},"optional":{}}]

The docker-manifest-digest in the output is the exact SHA-256 of the manifest you're about to run. Pin it in your docker-compose.yaml or Kubernetes Deployment spec to lock the runtime to that exact digest:

services:
  smartflow:
    image: registry.aperion.ai/aperion/safechat-enterprise@sha256:0811776648b20db914385db8df68e18ac018874429c89958f85f0014b7a9211c
Wire it into your CI / CD

5 Verify in your build pipeline

Run cosign verify as a step before any helm upgrade, docker compose up, or kubectl apply. Verification is fast (~150 ms) and runs entirely offline once the public key is pinned.

- name: Verify Aperion image signature
  run: |
    cosign verify \
      --key https://docs.aperion.ai/cosign.pub \
      registry.aperion.ai/aperion/safechat-enterprise:v1.8.0
verify-aperion-image:
  stage: pre-deploy
  image: cgr.dev/chainguard/cosign:latest
  script:
    - cosign verify --key https://docs.aperion.ai/cosign.pub
        registry.aperion.ai/aperion/safechat-enterprise:v1.8.0
stage('Verify Aperion signature') {
  steps {
    sh '''
      curl -fsSL https://docs.aperion.ai/cosign.pub -o /tmp/aperion.pub
      cosign verify --key /tmp/aperion.pub \
        registry.aperion.ai/aperion/safechat-enterprise:v1.8.0
    '''
  }
}

Use Argo CD resource hooks (PreSync) to run a Job that calls cosign verify before the actual rollout. Failed verification aborts the sync.

Kubernetes admission policies

6 Block unsigned images at the cluster boundary

The strongest posture is to prevent unsigned or wrongly-signed Aperion images from running at all — enforced at admission time, not in the deployment pipeline. Three popular admission systems all support this with a few lines of YAML.

Recommended. Native Sigstore tooling, ECC P-256 verification baked in, written for exactly this use case.

apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: aperion-images-must-be-signed
spec:
  images:
    - glob: registry.aperion.ai/aperion/**
  authorities:
    - name: aperion-keypair
      key:
        # Pin the public key in your cluster (Secret, ConfigMap, or inline).
        # The fingerprint of this key is published in every Aperion release note.
        data: |
          -----BEGIN PUBLIC KEY-----
          MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6RbqCbiJFe7JiLUwf2flWF8pUL0R
          b75JOA7DZRiPfXifz8y3NuIOEeNl3q7hG9u5mluzg2SKOjvOMKIN8bPldw==
          -----END PUBLIC KEY-----

Apply it; from that moment, any pod whose image matches registry.aperion.ai/aperion/** and whose signature does not verify against the pinned key is rejected at admission time.

apiVersion: kyverno.io/v2beta1
kind: ClusterPolicy
metadata:
  name: verify-aperion-images
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: aperion-keyless-or-keyed-signature
      match:
        any:
          - resources:
              kinds: [Pod]
      verifyImages:
        - imageReferences:
            - registry.aperion.ai/aperion/*
          attestors:
            - entries:
                - keys:
                    publicKeys: |-
                      -----BEGIN PUBLIC KEY-----
                      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6RbqCbiJFe7JiLUwf2flWF8pUL0R
                      b75JOA7DZRiPfXifz8y3NuIOEeNl3q7hG9u5mluzg2SKOjvOMKIN8bPldw==
                      -----END PUBLIC KEY-----
          mutateDigest: true     # rewrite tag → digest after verification

mutateDigest: true is what closes the "tag drift" loophole — Kyverno rewrites every image reference to the verified content-digest so a later :latest push cannot silently change what runs in the cluster.

Gatekeeper requires a cosign-gatekeeper-provider external-data provider plus a ConstraintTemplate. The provider runs cosign verification out-of-band and feeds the result back to Gatekeeper. Skeleton template:

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sverifyaperion
spec:
  crd:
    spec:
      names: { kind: K8sVerifyAperion }
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sverifyaperion
        violation[{"msg": msg}] {
          input.review.object.kind == "Pod"
          some i
          c := input.review.object.spec.containers[i]
          startswith(c.image, "registry.aperion.ai/aperion/")
          response := external_data({"provider": "cosign-gatekeeper-provider",
                                     "keys":     [c.image]})
          response.responses[0].verified == false
          msg := sprintf("Aperion image %q failed cosign verification", [c.image])
        }

If you don't already run Gatekeeper, prefer Sigstore Policy Controller instead — it's purpose-built for this and you avoid managing a custom external-data provider.

For auditors and regulators

7 Independent third-party verification

An auditor or a national supervisor can prove the chain of custody from the build server to a running container without trusting Aperion's runtime code:

This three-step ladder is what an EU AI Act notified body or a US insurance regulator should be shown when they ask "how do I know the binary running matched the binary you tested?"

Annex IV mapping

Image signing satisfies EU AI Act Annex IV §1(b) (provenance of components and tools used) and contributes to NIS2 Article 21(2)(d) (supply-chain security). Combined with the AI-BOM endpoint (/api/aibom), the WORM audit archive, and the open-source verifier, you have a complete provenance story end-to-end.

Operational notes

8 Key rotation and revocation

The current Aperion signing key is long-lived. If a future event requires rotation, Aperion will:

Plan your admission policy with this in mind: pin the key fingerprint, not just the URL. The Sigstore Policy Controller and Kyverno examples above embed the key directly in cluster YAML, which is the right pattern.

Rotation drill — completed 2026-05-07

Aperion has rehearsed the full rotation procedure end-to-end on a live image. A staged v2 key was generated on the build server, published at cosign-v2.pub, used to overlay-sign safechat-enterprise:v1.8.0 alongside the existing v1 signature, and verified with both keys independently. The procedure below is the same one we will use whenever a production rotation is required.

Operator runbook (90-day overlap):

# 1) Generate v2 keypair on the build server only
COSIGN_PASSWORD='' cosign generate-key-pair
chmod 600 cosign.key
sha256sum cosign.pub          # publish this fingerprint in the next release note

# 2) Publish v2 public key under a versioned URL on docs.aperion.ai
#    (cosign.pub keeps pointing at v1 throughout the overlap window)
scp cosign.pub docs:/var/www/html/cosign-v2.pub
# Add /cosign-v2.pub to the Caddy public allowlist; reload caddy

# 3) Overlay-sign every currently-published tag with v2
for img in safechat-enterprise safechat safechat-dashboard; do
  for tag in v1.8.0 latest; do
    cosign sign --yes --key cosign.key registry.aperion.ai/aperion/$img:$tag
  done
done

# 4) Verify both keys produce a passing verdict
cosign verify --key cosign.pub    registry.aperion.ai/aperion/safechat-enterprise:v1.8.0  # v1 still valid
cosign verify --key cosign-v2.pub registry.aperion.ai/aperion/safechat-enterprise:v1.8.0  # v2 now valid

# 5) Customers update their admission policy on their own schedule (90 days).
#    Past day 90, /cosign.pub flips to point at v2, /cosign-v1.pub becomes the legacy URL.

Both signatures live as separate OCI referrer artifacts on the image manifest; they do not conflict and you can confirm with cosign tree <image>.

9 What is not covered by signing

Image signing proves "this binary came from Aperion's build server". It does not prove:

Use all four together — image signing, conformity console, MCP Trust Registry, and the audit verifier — and you have the four pieces of trust an EU AI Act high-risk deployment needs.