From 9149ed1610d551486ba984f60fa1faeed8bfb058 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 2 Oct 2026 11:28:16 +0200 Subject: [PATCH 1/2] ci: migrate cosign to v3 with legacy signature compatibility Keep legacy image payloads, .sig tags, and Rekor v1 service defaults. Document exact cosign v2/v3 image verification and unchanged release archive attestations. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/docker-publish.yml | 9 +- README.md | 10 +++ SECURITY.md | 7 ++ docs/verify-artifacts.md | 128 +++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 docs/verify-artifacts.md diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index db5e847a5a..559946a24f 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -48,7 +48,7 @@ jobs: if: github.event_name != 'pull_request' uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 #v4.1.2 with: - cosign-release: "v2.6.5" + cosign-release: "v3.1.3" # Set up BuildKit Docker container builder to be able to build # multi-platform images and export cache @@ -122,9 +122,8 @@ jobs: oauth_client_secret=${{ secrets.OAUTH_CLIENT_SECRET }} # Sign the resulting Docker image digest except on PRs. - # This will only write to the public Rekor transparency log when the Docker - # repository is public to avoid leaking data. If you would like to publish - # transparency data even for private images, pass --force to cosign below. + # Keep v2-compatible .sig tags and Rekor v1 entries during the v3 transition. + # --yes also consents to publishing signing metadata to the public log. # https://github.com/sigstore/cosign - name: Sign the published Docker image if: ${{ github.event_name != 'pull_request' }} @@ -134,4 +133,4 @@ jobs: DIGEST: ${{ steps.build-and-push.outputs.digest }} # This step uses the identity token to provision an ephemeral certificate # against the sigstore community Fulcio instance. - run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} + run: echo "${TAGS}" | xargs -I {} cosign sign --yes --new-bundle-format=false --use-signing-config=false --registry-referrers-mode=legacy {}@${DIGEST} diff --git a/README.md b/README.md index c1f9857d1a..3f0b645760 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,16 @@ To keep your GitHub PAT secure and reusable across different MCP hosts: +### Verify published images and release archives + +Container images are signed keylessly with cosign. The publisher uses cosign v3 +in legacy compatibility mode: existing cosign v2 image verification commands +continue to work. The cosign v3 examples explicitly select that format with +`--new-bundle-format=false`. +Release archives use GitHub artifact attestations, not cosign blob signatures. +See [Verifying published artifacts](docs/verify-artifacts.md) for exact commands, +certificate identities, and the signature-format transition policy. + ### GitHub Enterprise Server and Enterprise Cloud with data residency (ghe.com) The flag `--gh-host` and the environment variable `GITHUB_HOST` can be used to set diff --git a/SECURITY.md b/SECURITY.md index 67a9cbf2c6..fef88afcc1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,6 +2,13 @@ Thanks for helping make GitHub safe for everyone. # Security +## Verifying published artifacts + +See [Verifying published artifacts](docs/verify-artifacts.md) for cosign v2 and v3 +container-image verification commands and GitHub release-archive attestation +verification. Verification must check the expected workflow identity and OIDC +issuer as well as the artifact digest. + GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). Even though [open source repositories are outside of the scope of our bug bounty program](https://bounty.github.com/index.html#scope) and therefore not eligible for bounty rewards, we will ensure that your finding gets passed along to the appropriate maintainers for remediation. diff --git a/docs/verify-artifacts.md b/docs/verify-artifacts.md new file mode 100644 index 0000000000..1f4963fe14 --- /dev/null +++ b/docs/verify-artifacts.md @@ -0,0 +1,128 @@ +# Verifying published artifacts + +## Container images: cosign v2 and v3 + +Images published to `ghcr.io/github/github-mcp-server` by +`.github/workflows/docker-publish.yml` are signed keylessly using GitHub Actions +OIDC and Sigstore Fulcio, with signing metadata recorded in the public Rekor +transparency log. Verify the image digest, the exact workflow certificate +identity, and the issuer before running the image. + +The publisher uses **cosign v3.1.3 in legacy compatibility mode**. Signatures +remain in `sha256-.sig` tags in the image repository, with the legacy +cosign payload and Rekor v1 verification material. We do not publish the new +protobuf-based Sigstore bundles or OCI 1.1 referring signature artifacts during +this transition. + +Use a security-patched cosign client: v2.6.5 or v3.1.3 (or a later compatible +patch). Both fix [GHSA-fx35-mq7g-6g98](https://github.com/sigstore/cosign/security/advisories/GHSA-fx35-mq7g-6g98). +Existing v2 verification commands do not need new flags. Cosign v3.1.3 image +verification probes for new bundles and falls back to legacy signatures when +none are found. The v3 command below explicitly selects +`--new-bundle-format=false` rather than relying on that auto-detection; it also +works for signatures published before the publisher upgrade. + +For a release image, replace both placeholders below with the digest obtained +from your trusted release/deployment configuration and its full Git tag (for +example, `vX.Y.Z`, not the Docker alias `X.Y.Z`). Use the multi-platform image +index digest, which is what the publishing workflow signs, rather than an +individual platform manifest digest. Do not verify a mutable tag and then pull +that tag: verify and run the same digest. + +```sh +IMAGE='ghcr.io/github/github-mcp-server@sha256:' +REF='refs/tags/vX.Y.Z' +IDENTITY="https://github.com/github/github-mcp-server/.github/workflows/docker-publish.yml@${REF}" +ISSUER='https://token.actions.githubusercontent.com' +``` + +With **cosign v2.6.5**: + +```sh +cosign verify \ + --certificate-identity "${IDENTITY}" \ + --certificate-oidc-issuer "${ISSUER}" \ + "${IMAGE}" +``` + +With **cosign v3.1.3**: + +```sh +cosign verify \ + --new-bundle-format=false \ + --certificate-identity "${IDENTITY}" \ + --certificate-oidc-issuer "${ISSUER}" \ + "${IMAGE}" +``` + +For a main-branch, nightly, or manually published main-branch image, use +`REF='refs/heads/main'` instead. Images from `next` use +`REF='refs/heads/next'`. Other manually selected refs need their exact ref. +Pull-request builds are not published or signed by this workflow. +Do not broaden the certificate identity to accept arbitrary workflows or refs, +and do not bypass certificate or transparency-log verification. + +### Why the publisher opts out of the v3 defaults + +Cosign v3 changes both signature format and signing-service discovery: + +| Publisher flag | Compatibility behavior | +| --- | --- | +| `--new-bundle-format=false` | Keeps the legacy cosign image payload and verification material instead of the protobuf-based Sigstore bundle (serialized as JSON). | +| `--use-signing-config=false` | Keeps the legacy service defaults, including Rekor v1, instead of fetching signing-service URLs from TUF. This is needed as well as the format flag: v3 rejects legacy image signing with its default signing config unless a local bundle output is supplied. | +| `--registry-referrers-mode=legacy` | Explicitly keeps `.sig` tag storage instead of opting into OCI 1.1 referrers for legacy signatures. | + +The workflow still requests an ephemeral Fulcio certificate from GitHub Actions +OIDC and uploads to Rekor; it does not disable transparency logging or certificate +checks. `--yes` accepts the public transparency-log disclosure, including for +private repositories. Do not reuse this workflow for private artifacts without +reviewing that disclosure. + +The signing compatibility flags remain supported in v3.1.3, although +`--new-bundle-format` is deprecated. Image verification selects the legacy path +with `--new-bundle-format=false`; `cosign verify` does not accept the signing +flag `--registry-referrers-mode`. The v3 default new-format path stores a +Sigstore bundle as an OCI referring artifact (using the OCI referrers fallback +tag when the registry does not support the referrers API); +changing only the registry mode does not restore the legacy payload. +V2 clients before v2.6 cannot verify those new image bundles; v2.6.x supports +them with `--new-bundle-format=true`, while v3 expects them by default. +V2.6.5 image verification also auto-detects new bundles, but consumers using +older clients or relying on `.sig` tags must not assume that support. +No new-format signature is currently promised by this repository. + +This is a compatibility bridge, not a permanent commitment to legacy storage. +A future format migration must announce consumer changes, validate registry +referrer support and verification tooling, and consider dual signing before +removing `.sig` signatures. It must also account for Rekor v1 service availability. +See the upstream [v3 announcement](https://blog.sigstore.dev/cosign-3-0-available/) +and [v3.0.0 changelog](https://github.com/sigstore/cosign/blob/v3.0.0/CHANGELOG.md). + +## Release archives: GitHub artifact attestations + +The GoReleaser workflow (`.github/workflows/goreleaser.yml`) builds release +archives and checksums, then uses `actions/attest-build-provenance` for +`dist/*.tar.gz`, `dist/*.zip`, and `dist/*.txt`. Neither that workflow nor +`.goreleaser.yaml` uses cosign, and releases do not provide cosign +`sign-blob` signatures or bundle files. Their attestation format is unaffected +by the cosign upgrade. + +Using a current GitHub CLI, download the desired archive from a trusted release +tag and verify its build provenance: + +```sh +TAG='vX.Y.Z' +ARCHIVE='github-mcp-server_Linux_x86_64.tar.gz' +gh release download "${TAG}" \ + --repo github/github-mcp-server \ + --pattern "${ARCHIVE}" +gh attestation verify "${ARCHIVE}" \ + --repo github/github-mcp-server \ + --signer-workflow github/github-mcp-server/.github/workflows/goreleaser.yml \ + --source-ref "refs/tags/${TAG}" +``` + +Choose the archive name for your OS and architecture; Windows archives use +`.zip`. Verification fails if no matching attestation exists, for example for +an older release that predates artifact attestations. A downloaded checksum +file alone is not proof of publisher identity. From 813e3fa03465d1aee39f70771349c15ae68bb839 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 2 Oct 2026 12:39:39 +0200 Subject: [PATCH 2/2] ci: dual-publish legacy and native cosign image signatures Sign the same immutable digest in separate fail-fast steps. Retain legacy signatures and publish native bundles with v3 service defaults. Remove the verification documentation introduced by this PR as requested; keep compatibility evidence and rollout plans in the PR description. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/docker-publish.yml | 13 ++- README.md | 10 --- SECURITY.md | 7 -- docs/verify-artifacts.md | 128 --------------------------- 4 files changed, 9 insertions(+), 149 deletions(-) delete mode 100644 docs/verify-artifacts.md diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 559946a24f..a810a716ca 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -122,15 +122,20 @@ jobs: oauth_client_secret=${{ secrets.OAUTH_CLIENT_SECRET }} # Sign the resulting Docker image digest except on PRs. - # Keep v2-compatible .sig tags and Rekor v1 entries during the v3 transition. + # Publish both v2-compatible .sig tags and native Sigstore bundles. # --yes also consents to publishing signing metadata to the public log. # https://github.com/sigstore/cosign - - name: Sign the published Docker image + - name: Sign the published Docker image (legacy) if: ${{ github.event_name != 'pull_request' }} env: # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable - TAGS: ${{ steps.meta.outputs.tags }} DIGEST: ${{ steps.build-and-push.outputs.digest }} # This step uses the identity token to provision an ephemeral certificate # against the sigstore community Fulcio instance. - run: echo "${TAGS}" | xargs -I {} cosign sign --yes --new-bundle-format=false --use-signing-config=false --registry-referrers-mode=legacy {}@${DIGEST} + run: cosign sign --yes --new-bundle-format=false --use-signing-config=false --registry-referrers-mode=legacy "${REGISTRY}/${IMAGE_NAME}@${DIGEST}" + + - name: Sign the published Docker image (native bundle) + if: ${{ github.event_name != 'pull_request' }} + env: + DIGEST: ${{ steps.build-and-push.outputs.digest }} + run: cosign sign --yes "${REGISTRY}/${IMAGE_NAME}@${DIGEST}" diff --git a/README.md b/README.md index 3f0b645760..c1f9857d1a 100644 --- a/README.md +++ b/README.md @@ -242,16 +242,6 @@ To keep your GitHub PAT secure and reusable across different MCP hosts: -### Verify published images and release archives - -Container images are signed keylessly with cosign. The publisher uses cosign v3 -in legacy compatibility mode: existing cosign v2 image verification commands -continue to work. The cosign v3 examples explicitly select that format with -`--new-bundle-format=false`. -Release archives use GitHub artifact attestations, not cosign blob signatures. -See [Verifying published artifacts](docs/verify-artifacts.md) for exact commands, -certificate identities, and the signature-format transition policy. - ### GitHub Enterprise Server and Enterprise Cloud with data residency (ghe.com) The flag `--gh-host` and the environment variable `GITHUB_HOST` can be used to set diff --git a/SECURITY.md b/SECURITY.md index fef88afcc1..67a9cbf2c6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,13 +2,6 @@ Thanks for helping make GitHub safe for everyone. # Security -## Verifying published artifacts - -See [Verifying published artifacts](docs/verify-artifacts.md) for cosign v2 and v3 -container-image verification commands and GitHub release-archive attestation -verification. Verification must check the expected workflow identity and OIDC -issuer as well as the artifact digest. - GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). Even though [open source repositories are outside of the scope of our bug bounty program](https://bounty.github.com/index.html#scope) and therefore not eligible for bounty rewards, we will ensure that your finding gets passed along to the appropriate maintainers for remediation. diff --git a/docs/verify-artifacts.md b/docs/verify-artifacts.md deleted file mode 100644 index 1f4963fe14..0000000000 --- a/docs/verify-artifacts.md +++ /dev/null @@ -1,128 +0,0 @@ -# Verifying published artifacts - -## Container images: cosign v2 and v3 - -Images published to `ghcr.io/github/github-mcp-server` by -`.github/workflows/docker-publish.yml` are signed keylessly using GitHub Actions -OIDC and Sigstore Fulcio, with signing metadata recorded in the public Rekor -transparency log. Verify the image digest, the exact workflow certificate -identity, and the issuer before running the image. - -The publisher uses **cosign v3.1.3 in legacy compatibility mode**. Signatures -remain in `sha256-.sig` tags in the image repository, with the legacy -cosign payload and Rekor v1 verification material. We do not publish the new -protobuf-based Sigstore bundles or OCI 1.1 referring signature artifacts during -this transition. - -Use a security-patched cosign client: v2.6.5 or v3.1.3 (or a later compatible -patch). Both fix [GHSA-fx35-mq7g-6g98](https://github.com/sigstore/cosign/security/advisories/GHSA-fx35-mq7g-6g98). -Existing v2 verification commands do not need new flags. Cosign v3.1.3 image -verification probes for new bundles and falls back to legacy signatures when -none are found. The v3 command below explicitly selects -`--new-bundle-format=false` rather than relying on that auto-detection; it also -works for signatures published before the publisher upgrade. - -For a release image, replace both placeholders below with the digest obtained -from your trusted release/deployment configuration and its full Git tag (for -example, `vX.Y.Z`, not the Docker alias `X.Y.Z`). Use the multi-platform image -index digest, which is what the publishing workflow signs, rather than an -individual platform manifest digest. Do not verify a mutable tag and then pull -that tag: verify and run the same digest. - -```sh -IMAGE='ghcr.io/github/github-mcp-server@sha256:' -REF='refs/tags/vX.Y.Z' -IDENTITY="https://github.com/github/github-mcp-server/.github/workflows/docker-publish.yml@${REF}" -ISSUER='https://token.actions.githubusercontent.com' -``` - -With **cosign v2.6.5**: - -```sh -cosign verify \ - --certificate-identity "${IDENTITY}" \ - --certificate-oidc-issuer "${ISSUER}" \ - "${IMAGE}" -``` - -With **cosign v3.1.3**: - -```sh -cosign verify \ - --new-bundle-format=false \ - --certificate-identity "${IDENTITY}" \ - --certificate-oidc-issuer "${ISSUER}" \ - "${IMAGE}" -``` - -For a main-branch, nightly, or manually published main-branch image, use -`REF='refs/heads/main'` instead. Images from `next` use -`REF='refs/heads/next'`. Other manually selected refs need their exact ref. -Pull-request builds are not published or signed by this workflow. -Do not broaden the certificate identity to accept arbitrary workflows or refs, -and do not bypass certificate or transparency-log verification. - -### Why the publisher opts out of the v3 defaults - -Cosign v3 changes both signature format and signing-service discovery: - -| Publisher flag | Compatibility behavior | -| --- | --- | -| `--new-bundle-format=false` | Keeps the legacy cosign image payload and verification material instead of the protobuf-based Sigstore bundle (serialized as JSON). | -| `--use-signing-config=false` | Keeps the legacy service defaults, including Rekor v1, instead of fetching signing-service URLs from TUF. This is needed as well as the format flag: v3 rejects legacy image signing with its default signing config unless a local bundle output is supplied. | -| `--registry-referrers-mode=legacy` | Explicitly keeps `.sig` tag storage instead of opting into OCI 1.1 referrers for legacy signatures. | - -The workflow still requests an ephemeral Fulcio certificate from GitHub Actions -OIDC and uploads to Rekor; it does not disable transparency logging or certificate -checks. `--yes` accepts the public transparency-log disclosure, including for -private repositories. Do not reuse this workflow for private artifacts without -reviewing that disclosure. - -The signing compatibility flags remain supported in v3.1.3, although -`--new-bundle-format` is deprecated. Image verification selects the legacy path -with `--new-bundle-format=false`; `cosign verify` does not accept the signing -flag `--registry-referrers-mode`. The v3 default new-format path stores a -Sigstore bundle as an OCI referring artifact (using the OCI referrers fallback -tag when the registry does not support the referrers API); -changing only the registry mode does not restore the legacy payload. -V2 clients before v2.6 cannot verify those new image bundles; v2.6.x supports -them with `--new-bundle-format=true`, while v3 expects them by default. -V2.6.5 image verification also auto-detects new bundles, but consumers using -older clients or relying on `.sig` tags must not assume that support. -No new-format signature is currently promised by this repository. - -This is a compatibility bridge, not a permanent commitment to legacy storage. -A future format migration must announce consumer changes, validate registry -referrer support and verification tooling, and consider dual signing before -removing `.sig` signatures. It must also account for Rekor v1 service availability. -See the upstream [v3 announcement](https://blog.sigstore.dev/cosign-3-0-available/) -and [v3.0.0 changelog](https://github.com/sigstore/cosign/blob/v3.0.0/CHANGELOG.md). - -## Release archives: GitHub artifact attestations - -The GoReleaser workflow (`.github/workflows/goreleaser.yml`) builds release -archives and checksums, then uses `actions/attest-build-provenance` for -`dist/*.tar.gz`, `dist/*.zip`, and `dist/*.txt`. Neither that workflow nor -`.goreleaser.yaml` uses cosign, and releases do not provide cosign -`sign-blob` signatures or bundle files. Their attestation format is unaffected -by the cosign upgrade. - -Using a current GitHub CLI, download the desired archive from a trusted release -tag and verify its build provenance: - -```sh -TAG='vX.Y.Z' -ARCHIVE='github-mcp-server_Linux_x86_64.tar.gz' -gh release download "${TAG}" \ - --repo github/github-mcp-server \ - --pattern "${ARCHIVE}" -gh attestation verify "${ARCHIVE}" \ - --repo github/github-mcp-server \ - --signer-workflow github/github-mcp-server/.github/workflows/goreleaser.yml \ - --source-ref "refs/tags/${TAG}" -``` - -Choose the archive name for your OS and architecture; Windows archives use -`.zip`. Verification fails if no matching attestation exists, for example for -an older release that predates artifact attestations. A downloaded checksum -file alone is not proof of publisher identity.