Skip to Content
EnterpriseSecurityRelease Verification

Verify a Private Enterprise release

Every Private Enterprise release can be checked from the code that produced it to the record of what your installer wrote, without calling VeriProof. This page gives the commands. None of them require VeriProof credentials, and none of them go through VeriProof systems except to download the immutable, signed release files.

The chain has four links. Verify as many as your review needs.

  1. The release manifest and every package were signed by VeriProof’s protected build workflow, at the commit the manifest names.
  2. The packages are reproducible: the workflow built them twice from that commit and recorded matching digests.
  3. What your subscription installed is the release and manifest digest the template pinned.
  4. Nothing in that release has been revoked since.

With a source license there is a fifth: rebuild the named commit yourself and compare.

Where the trusted values come from

Your installer does not trust VeriProof’s word for which identities may sign a release. The Marketplace template you deployed carries them as parameters, and the installer verifies every signature against those values. Your verification should use the same source, so that what you check is what your installer checked.

Read them from the installer deployment inside your managed resource group. The deployment is named private-enterprise-artifact-installer.

export SUBSCRIPTION="<subscription-id>" export RESOURCE_GROUP="<managed-resource-group>" az deployment group show \ --subscription "$SUBSCRIPTION" \ --resource-group "$RESOURCE_GROUP" \ --name private-enterprise-artifact-installer \ --query '{ releaseId: properties.parameters.releaseId.value, manifestSha256: properties.parameters.artifactManifestSha256.value, releaseBaseUri: properties.parameters.artifactReleaseBaseUri.value, signerIdentity: properties.parameters.artifactSignerIdentity.value, publicationIdentity: properties.parameters.artifactPublicationIdentity.value, revocationSignerIdentity: properties.parameters.artifactRevocationSignerIdentity.value, oidcIssuer: properties.parameters.artifactCertificateOidcIssuer.value, revocationFeedUri: properties.parameters.artifactRevocationFeedUri.value, revocationBundleUri: properties.parameters.artifactRevocationBundleUri.value }' \ --output json

Export the values for the commands that follow:

export RELEASE_ID="<releaseId>" export MANIFEST_SHA256="<manifestSha256>" export RELEASE="<releaseBaseUri>/private-enterprise/releases/${RELEASE_ID}" export SIGNER="<signerIdentity>" export PUBLISHER="<publicationIdentity>" export REVOKER="<revocationSignerIdentity>" export ISSUER="<oidcIssuer>" export REVOKE_FEED="<revocationFeedUri>" export REVOKE_BUNDLE="<revocationBundleUri>"

Three identities are involved, and they are deliberately different:

ValueSignsForm
signerIdentityThe release manifest, the six packages, the SBOM index, and the mirrored verifierA GitHub Actions workflow identity: https://github.com/<organization>/<repository>/.github/workflows/<workflow>.yml@refs/heads/main
publicationIdentityrelease.complete.json, the marker that the release was fully promotedA different workflow identity in the same repository
revocationSignerIdentityThe artifact revocation feedA third workflow identity, run from a protected environment

Each identity is a keyless Sigstore certificate bound to one workflow file on the main branch of one repository, issued by GitHub’s OIDC provider. A signature that verifies against it proves the file was signed inside that exact workflow. There is no long-lived signing key for anyone to hold or leak.

The repository itself is private. Customers who license the source receive its location with the license; you do not need it to run anything on this page.

Expect releaseBaseUri to be https://releases.veriproof.app and oidcIssuer to be https://token.actions.githubusercontent.com for a production release. If either differs, stop and contact VeriProof before continuing.

What you need

  • Cosign v2.5.3. Obtain it from sigstore.dev  so the verifier does not come from the thing it verifies. Each release also mirrors a Linux binary at tools/cosign-v2.5.3-linux-amd64 for air-gapped installs; its SHA-256 is published below so you can check it before use.
  • Azure CLI, signed in to the subscription that holds the managed application, with read access to the managed resource group’s deployments.
  • jq and sha256sum, or equivalents.

Release layout

Releases are published to an immutable path and never rewritten:

<releaseBaseUri>/private-enterprise/releases/<release-id>/ candidate-manifest.json release.complete.json sboms/sbom-index.json tools/cosign-v2.5.3-linux-amd64 signatures/ candidate-manifest.sigstore.json release.complete.sigstore.json sbom-index.sigstore.json cosign-v2.5.3-linux-amd64.sigstore.json customer.sigstore.json customer-portal.sigstore.json database.sigstore.json ingest.sigstore.json mcp.sigstore.json workflows.sigstore.json

release.complete.json is written last, by the publication workflow, after every other file has been uploaded and read back. A release without it was never finished and must not be installed or trusted.

Step 1: Verify the manifest and the completion marker

Download the manifest and the completion marker with their bundles. The manifest verifies against the signer identity; the completion marker verifies against the publication identity.

mkdir -p signatures curl -fsSLO "${RELEASE}/candidate-manifest.json" curl -fsSLO "${RELEASE}/release.complete.json" curl -fsSL "${RELEASE}/signatures/candidate-manifest.sigstore.json" -o signatures/candidate-manifest.sigstore.json curl -fsSL "${RELEASE}/signatures/release.complete.sigstore.json" -o signatures/release.complete.sigstore.json cosign verify-blob \ --bundle signatures/candidate-manifest.sigstore.json \ --certificate-identity "${SIGNER}" \ --certificate-oidc-issuer "${ISSUER}" \ candidate-manifest.json cosign verify-blob \ --bundle signatures/release.complete.sigstore.json \ --certificate-identity "${PUBLISHER}" \ --certificate-oidc-issuer "${ISSUER}" \ release.complete.json

Both commands must print Verified OK. Then confirm the manifest you downloaded is the one your installer pinned:

echo "${MANIFEST_SHA256} candidate-manifest.json" | sha256sum --check

This is the link between the signed release and the bytes your installer used. If it fails, the release at that path is not the one your subscription installed, and nothing further on this page applies to it.

The manifest names the source commit and the six packages:

jq '{releaseId, commit, schema: .marketplaceRelease.schema, packages: [.packages[] | {nickname, sha256}]}' candidate-manifest.json

Expect releaseId to equal RELEASE_ID, schema to be veriproof.marketplace-release/v1, and exactly six packages: customer, customer-portal, database, ingest, mcp, and workflows. Record the commit value. It is the Git commit that this release was built from, and it is what a source license lets you rebuild.

Step 2: Confirm the reproducibility evidence and the packages

The build workflow compiles every candidate twice from the same commit and refuses to release unless the six package digests match. The manifest carries that evidence:

jq '.marketplaceRelease.packageReproducibility' candidate-manifest.json

Expect status of verified, buildCount of 2, and a 64-character evidenceSha256. This tells you the digests you are about to check were not produced by a single, unrepeatable build.

Download each package at its relativePath, verify its signature, and confirm its digest matches the manifest:

for nick in customer customer-portal database ingest mcp workflows; do rel=$(jq -r --arg n "$nick" '.packages[] | select(.nickname==$n) | .relativePath' candidate-manifest.json) want=$(jq -r --arg n "$nick" '.packages[] | select(.nickname==$n) | .sha256' candidate-manifest.json) mkdir -p "$(dirname "$rel")" curl -fsSL "${RELEASE}/${rel}" -o "$rel" curl -fsSL "${RELEASE}/signatures/${nick}.sigstore.json" -o "signatures/${nick}.sigstore.json" cosign verify-blob --bundle "signatures/${nick}.sigstore.json" \ --certificate-identity "${SIGNER}" --certificate-oidc-issuer "${ISSUER}" "$rel" have=$(sha256sum "$rel" | cut -d' ' -f1) [ "$have" = "$want" ] && echo "OK $nick" || { echo "FAIL $nick: manifest $want, file $have"; exit 1; } done

The software bill of materials is signed the same way:

mkdir -p sboms curl -fsSL "${RELEASE}/sboms/sbom-index.json" -o sboms/sbom-index.json curl -fsSL "${RELEASE}/signatures/sbom-index.sigstore.json" -o signatures/sbom-index.sigstore.json cosign verify-blob --bundle signatures/sbom-index.sigstore.json \ --certificate-identity "${SIGNER}" --certificate-oidc-issuer "${ISSUER}" sboms/sbom-index.json

sbom-index.json indexes the CycloneDX documents for the release.

Step 3: Confirm the installation record

Step 1 already tied the signed manifest to the digest your installer pinned. The installer went one step further: it wrote a secret-free installation record, bound to that same manifest digest, into the veriproof-evidence container in your subscription under a locked retention policy. That record is yours, in your tenant, and states what was installed.

The installer deployment’s installationStatus output reports whether that write completed:

az deployment group show \ --subscription "$SUBSCRIPTION" \ --resource-group "$RESOURCE_GROUP" \ --name private-enterprise-artifact-installer \ --query 'properties.outputs.installationStatus.value' \ --output json

Look for the installation-evidence-persistence result. Your Azure operations team can read the record itself with a Storage data-plane role on the veriproof-evidence container.

If your team holds a source license, the same check is packaged as a script that also validates the deployment’s provisioning state and operation inventory:

./infrastructure/marketplace/Get-PrivateEnterpriseInstallationStatus.ps1 ` -SubscriptionId $env:SUBSCRIPTION ` -ResourceGroup $env:RESOURCE_GROUP ` -DeploymentName private-enterprise-artifact-installer ` -ExpectedReleaseId $env:RELEASE_ID ` -ExpectedArtifactManifestSha256 $env:MANIFEST_SHA256 ` -OutputPath ./installation-status.json

It stops with an error if the deployment does not match the expected release and manifest.

Step 4: Check the revocation feed

VeriProof publishes a separately signed, append-only list of revoked release IDs, manifest digests, and package digests. The installer refuses to proceed when a release is revoked, and it fails closed if the feed cannot be read or does not verify. Check it yourself the same way, against the revocation identity your template pinned:

curl -fsSL "${REVOKE_FEED}" -o artifact-revocation-list.json curl -fsSL "${REVOKE_BUNDLE}" -o artifact-revocation-list.sigstore.json cosign verify-blob \ --bundle artifact-revocation-list.sigstore.json \ --certificate-identity "${REVOKER}" \ --certificate-oidc-issuer "${ISSUER}" \ artifact-revocation-list.json jq --arg r "$RELEASE_ID" --arg m "$MANIFEST_SHA256" \ '[.. | strings] | map(select(. == $r or . == $m)) | if length == 0 then "not revoked" else "REVOKED" end' \ artifact-revocation-list.json

The revocation feed is signed by a different identity from the release, run from a protected environment. A feed that verifies against any other identity is not VeriProof’s.

Step 5: Rebuild from source and compare

This step requires a source license and the repository’s build tooling. It is the direct test that the code you inspected is the code you are running.

Check out the commit named in the manifest, rebuild the Marketplace candidate, and compare its manifest to the published one with the same check the release workflow runs:

git checkout <commit from candidate-manifest.json> ./vdev.ps1 publish -What marketplace -Env prod -OutputPath ./rebuild -Full -QualificationPending ./infrastructure/marketplace/Confirm-MarketplacePackageReproducibility.ps1 ` -CandidateManifestPath ./candidate-manifest.json ` -RebuildManifestPath ./rebuild/candidate-manifest.json

Confirm-MarketplacePackageReproducibility.ps1 compares the six package digests and stops with an error on any difference.

Reproducibility depends on the toolchain. The release workflow builds on a Windows runner with the .NET SDK pinned by the repository’s setup-dotnet action and Node.js 22. A rebuild on a different platform or toolchain version may produce different bytes for reasons unrelated to the source; match the pinned versions before treating a mismatch as a finding.

Checking the mirrored verifier

If you use the Cosign binary mirrored in the release rather than one obtained from sigstore, check its digest first. A verifier cannot vouch for itself, so this comparison is against a value published here rather than a signature in the release:

curl -fsSL "${RELEASE}/tools/cosign-v2.5.3-linux-amd64" -o cosign-v2.5.3-linux-amd64 echo "783b5d6c74105401c63946c68d9b2a4e1aab3c8abce043e06b8510b02b623ec9 cosign-v2.5.3-linux-amd64" | sha256sum --check

Installers supplied outside the Marketplace

Where procurement or network policy requires it, VeriProof can supply the signed installer for your team to run, or perform the installation with you. The release, its signatures, and the installation record are the same on every path, and every step on this page applies unchanged. The only difference is how the template reaches your subscription.