Check the software you download
Scope: how operators verify that a specific LQ.AI release’s container images, SBOM, and SLSA provenance are authentic — i.e., that they were built by the LegalQuants release workflow and not tampered with in transit.
In short
Section titled “In short”Every LQ.AI release publishes signed container images, each with its own SBOM and SLSA build-provenance attestation — and you can check all three yourself, not take them on faith.
- Signature (cosign): proves an image came from the LegalQuants release workflow and hasn’t been altered since.
- SBOM: lists every dependency at build time, for scanning with your own tooling — one per image.
- Provenance: pins each image to the exact commit and workflow that built it. From the first release after v0.7.1 it’s signed by an isolated builder job the release workflow can’t tamper with — SLSA Build Level 3; earlier releases carry Build Level 2.
None of this proves the software is free of vulnerabilities, or that a release was a deliberate maintainer choice rather than a compromised one — see “What the verification does NOT prove” below. Forks that need operator-key signing (for example, air-gapped deployments without OIDC) must set up and document their own signing identity — see “Operator-key signing (forks)” below.
LQ.AI uses sigstore keyless signing (cosign + Fulcio + Rekor) and SLSA build provenance attestations.
Quick verify (single image)
Section titled “Quick verify (single image)”cosign verify \ --certificate-identity-regexp "https://github.com/legalquants/lq-ai" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ ghcr.io/legalquants/lq-ai-api:v0.1.0A successful run prints the signing certificate’s issuer, subject (the workflow OIDC identity), and Rekor transparency-log entry. Failure prints a diagnostic explaining whether the signature is missing, the cert doesn’t match, or the cert’s identity doesn’t match the regexp.
Verify all four images in a release
Section titled “Verify all four images in a release”TAG=v0.1.0for service in api gateway web proxy; do echo "=== ${service} ===" cosign verify \ --certificate-identity-regexp "https://github.com/legalquants/lq-ai" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ "ghcr.io/legalquants/lq-ai-${service}:${TAG}"doneVerify the SBOM attestation
Section titled “Verify the SBOM attestation”The SBOM is bound to the image digest as a cosign attestation of type spdxjson. Checking it
needs cosign, jq, and an SBOM scanner (grype below; use your own):
cosign verify-attestation \ --type spdxjson \ --certificate-identity-regexp "https://github.com/legalquants/lq-ai" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ ghcr.io/legalquants/lq-ai-api:v0.1.0 | jq -r '.payload' | base64 -d | jq '.predicate' > api.spdx.json
# Then scan the SBOM with your preferred toolgrype sbom:./api.spdx.jsonVerify the SLSA build provenance
Section titled “Verify the SLSA build provenance”SLSA provenance is attached via GitHub’s attestations service by actions/attest-build-provenance, which runs in .github/workflows/build-image.yml. That is a reusable workflow that release.yml calls once per service. Verify with the gh CLI, pinning the builder:
gh attestation verify \ oci://ghcr.io/legalquants/lq-ai-api:<tag> \ --owner legalquants \ --signer-workflow LegalQuants/lq-ai/.github/workflows/build-image.ymlA successful run prints the signer workflow (build-image.yml), the calling workflow (release.yml), the trigger event (tag push), and the input commit SHA. Together these show the image was built from that exact source by that exact workflow.
SLSA level, and which releases it covers. The --signer-workflow pin is what makes this SLSA v1.0 Build Level 3. The provenance is signed by an isolated reusable workflow that the calling workflow cannot tamper with (GitHub’s guidance). Releases up to and including v0.7.1 also carry a provenance attestation, but it was generated inside release.yml’s own jobs, so it is signed by release.yml and is Build Level 2. For those tags, drop --signer-workflow (or pin --signer-workflow LegalQuants/lq-ai/.github/workflows/release.yml). Build Level 3 applies from the first release after v0.7.1.
What the verification proves
Section titled “What the verification proves”- Image authenticity: the signature was produced by a process holding a short-lived Fulcio cert bound to the LegalQuants workflow OIDC identity. An attacker who obtained access to the registry but not to the workflow’s OIDC identity cannot produce a valid signature.
- Image provenance: the SLSA attestation pins the image to a specific commit SHA + workflow + builder (the isolated
build-image.ymlreusable workflow, for releases after v0.7.1). An attacker who modified the source after the fact cannot reproduce the attestation without re-running the workflow (which would produce a new, distinct attestation with a new timestamp). - Dependency snapshot: the SBOM lists every dependency at build time. An attacker who substituted a dependency post-build cannot match the SBOM hash.
What the verification does NOT prove
Section titled “What the verification does NOT prove”- The image is free of vulnerabilities. Verification proves authenticity, not safety. Operators run SCA tools against the SBOM independently — see docs/security/dependencies.md.
- The release was a deliberate choice by LegalQuants maintainers. A compromised maintainer with the ability to push a tag could produce a valid release. This is the trust assumption shared by all OSS — see SECURITY.md for the disclosure process if you suspect a compromised release.
Operator-key signing (forks)
Section titled “Operator-key signing (forks)”If you fork LQ.AI and prefer operator-controlled key signing (e.g., for air-gapped deployments where short-lived OIDC certs aren’t available), replace the keyless flow in .github/workflows/release.yml step “Sign container image (keyless)” with:
- name: Sign container image (operator key) env: COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} run: | cosign sign --yes --key env://COSIGN_PRIVATE_KEY \ ghcr.io/your-org/lq-ai-${{ matrix.service }}:${{ steps.tag.outputs.tag }}Then operators verify with:
cosign verify --key cosign.pub ghcr.io/your-org/lq-ai-api:v0.1.0This trades keyless’s ergonomic advantages for an operator-managed key lifecycle. For most public OSS deployments, keyless is the right default.
Cross-references
Section titled “Cross-references”- docs/security/README.md — security-doc index.
- docs/security/dependencies.md — vulnerability monitoring.
- .github/workflows/release.yml — the release workflow that produces these artifacts.