Skip to content

Supply chain

Verified against v0.10.0 · go.mod, Dockerfile, .github/workflows/ci.yml, .github/workflows/codeql.yml, .github/workflows/scorecard.yml, .github/workflows/release.yml, .github/dependabot.yml

Go modules and both npm applications use checked-in lock or checksum files. Dependabot checks Go modules, web and documentation npm packages, GitHub Actions, and Docker images every week and groups each ecosystem’s updates.

The Docker build uses digest-pinned Node, Go, and distroless base images. It copies dependency manifests before source and copies only the source directories needed by each stage. The runtime remains a static Go binary in the non-root distroless image, with no shell or package manager. A -fips image is published beside it; see Cryptography.

The stable required / gate status is emitted for every pull request, including documentation-only changes. It requires:

  • Go formatting, vet, race-enabled tests against Postgres, and govulncheck
  • the same Go suite under GOFIPS140=v1.0.0 and GODEBUG=fips140=on
  • web lint, build, and dependency audit
  • documentation build and dependency audit
  • dependency review and Developer Certificate of Origin sign-offs
  • a container build, empty-database smoke test, and vulnerability scan
  • a --target fips image build and smoke that asserts the boot line reports fips140=on

CodeQL analyzes Go and TypeScript in a separate workflow. Workflow permissions default to read-only. Pages publishing, security result upload, package publishing, and identity-token use are granted only to the jobs that need them. Every action reference is pinned to a reviewed commit.

Publication starts only from a published GitHub Release whose tag is exactly vMAJOR.MINOR.PATCH. The workflow refuses a pre-release or a tag whose commit is not reachable from main.

v0.4.0 was tagged but never published: a broken SBOM step in the release workflow (fixed after) failed the publish before any artifact reached the registry. No image, chart, or attestation for v0.4.0 exists anywhere. v0.4.1 carries the same changes; the gap in the version sequence is expected, not a sign of a retagged or tampered release.

The multi-architecture image is built for linux/amd64 and linux/arm64 in a job with no registry write permission. That job creates an immutable OCI artifact with maximum BuildKit provenance and BuildKit SBOM attestations. A separate publisher job, scoped to the named release environment, writes the artifact to a run-specific staging tag, verifies that registry digest against the build digest, re-checks that the release tag still resolves to the validated commit, and only then assigns the final version, minor, and latest tags. It creates a GitHub build-provenance attestation for that final digest.

The workflow file shows the environment boundary and permissions; it does not prove that external environment reviewers or other GitHub protection settings are enabled. Those settings require separate API readback.

Beginning with the first release produced by this hardened workflow, each GitHub Release receives SBOMs (SPDX and CycloneDX) plus digest-qualified Compose and Kubernetes manifests generated from the published digest.

The image SBOM is generated after publication by scanning the linux/amd64 manifest resolved out of the published index, so the digest it records is one a consumer can pull. It describes that platform only. Because the frontend is built with npm and embedded into the Go binary, no image scan can see a single frontend dependency; a second, source-level SBOM covers the Go modules and web/’s npm tree, and excludes site/, the documentation site, which is never built into the image.

The SPDX and CycloneDX files are plain release assets. They are not signed and carry no attestation, unlike the per-platform BuildKit SBOM attestations and the GitHub build-provenance attestation on the published digest, which are the cryptographically verifiable artifacts. The two SBOMs are generated in a job holding registry read and no write permission, and attached by a job holding release write and no registry credential; neither job holds both.

Releases v0.1.0 through v0.2.1 were not backfilled with these artifacts or attestations. The readable manifests in the repository retain a version tag; use the release assets when an immutable deployment input is required.

Copy the digest from the release or registry, authenticate the GitHub CLI to GHCR, and verify that exact artifact:

Terminal window
digest=sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
gh attestation verify \
"oci://ghcr.io/lets-parley/parley@$digest" \
--repo lets-parley/parley \
--signer-workflow lets-parley/parley/.github/workflows/release.yml

Verification checks the digest, repository identity, workflow identity, signed provenance, and transparency-log evidence. Pull and deploy the same digest, not a mutable tag.

The command above calls the GitHub API and cannot run air-gapped. Every release also attaches parley-vX.Y.Z.sigstore.json: the same signed provenance statement as a Sigstore bundle, checkable with --bundle and no network call. Fetch a trusted root once while still online, then verify from the bundle and the digest alone:

Terminal window
gh attestation trusted-root > trusted_root.jsonl # do this once, online
digest=sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
gh attestation verify \
"oci://ghcr.io/lets-parley/parley@$digest" \
--owner lets-parley \
--bundle parley-vX.Y.Z.sigstore.json \
--custom-trusted-root trusted_root.jsonl

Address the image by digest (@sha256:...), not by tag: a tag reference resolves through the registry, which is exactly the network call this command is meant to avoid. See Air-gapped and private-CA installs for the full offline workflow, including mirroring the image and chart into a private registry.

CapabilityStatusWhat to do instead
Locked dependency setsBuilt inGo checksums and npm lockfiles are reviewed in pull requests and updated weekly.
Minimal runtime imageBuilt inDigest-pinned, non-root distroless static image with no shell or package manager.
SBOM (SPDX + CycloneDX)Built inPer release: image and source SBOMs are attached in both SPDX JSON and CycloneDX JSON formats. They are unsigned assets. Per-platform SBOM attestations are also attached to the published image by BuildKit.
Build provenanceBuilt inMaximum BuildKit provenance plus a GitHub artifact attestation bound to the final GHCR digest, also attached to the release as an offline-verifiable Sigstore bundle.
Vulnerability scanningBuilt ingovulncheck, CodeQL, dependency review, npm audits, and container scanning run in CI.

v0.1.0, v0.2.0 and v0.2.1 can be crashed remotely: a client disconnecting during a broadcast could panic the server on a goroutine nothing recovers, taking down every room on the instance rather than only that client’s. Denial of service only — no data disclosure, and unrevealed votes were never affected. Fixed in v0.2.2, which is the security floor. Deploy the current release, v0.10.0.

v0.1.0 additionally contains a room-code throttle bypass, fixed in v0.2.0.

No vulnerable tag or image was retagged onto fixed code, so anyone who pulled a digest still receives what they pulled. Do not deploy any of them.

There was no separate advisory for the v0.1.0 throttle bypass: the repository had no known installs at the time, and the release notes carry the warning. Report anything new through private vulnerability reporting (enabled on this repository) or email security@letsparley.io.