Skip to content

feat(security): sign protocol tarballs with Sigstore (cosign keyless)#2273

Merged
bokelley merged 1 commit intomainfrom
bokelley/issue-2272
Apr 17, 2026
Merged

feat(security): sign protocol tarballs with Sigstore (cosign keyless)#2273
bokelley merged 1 commit intomainfrom
bokelley/issue-2272

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Closes #2272.

Summary

  • /protocol/{version}.tgz is now signed during the release workflow with cosign keyless OIDC. Each released tarball ships with .sig and .crt sidecars next to the existing .sha256.
  • Verification proves the bundle came from adcontextprotocol/adcp/.github/workflows/release.yml, not just from adcontextprotocol.org. Defends against host compromise where an attacker could otherwise serve a malicious tarball with a matching SHA from the same origin.
  • No long-lived signing key — Sigstore Fulcio issues a short-lived cert bound to the workflow OIDC identity. Nothing to rotate or leak.

Changes

  • scripts/sign-protocol-tarball.sh — signs every dist/protocol/*.tgz when GITHUB_ACTIONS=true or SIGN_PROTOCOL_TARBALL=true; no-op locally so npm run version doesn't trigger a browser OAuth flow. Skips latest.tgz (rebuilt frequently, would go stale immediately) unless SIGN_LATEST_TARBALL=true.
  • package.json — chains the sign step into npm run version so signatures are produced and committed inside the changesets "Version Packages" PR alongside the tarball.
  • .github/workflows/release.yml — installs sigstore/cosign-installer@v3 and grants id-token: write so the workflow OIDC token can be exchanged for a Fulcio cert.
  • server/src/schemas-middleware.ts — serves .sig (application/octet-stream) and .crt (application/x-pem-file) with immutable cache headers for pinned versions; /protocol/ discovery endpoint surfaces signature / certificate URLs per version and the expected cosign verify-blob identity.
  • docs/building/schemas-and-sdks.mdx — verification quickstart documenting the certificate identity regexp and OIDC issuer to pin against.
  • scripts/build-protocol-tarball.cjs — release-bundle README now ships the cosign verify quickstart inside the tarball.
  • .gitignore — keeps latest.tgz.sig / latest.tgz.crt ignored alongside latest.tgz.

Verification model

cosign verify-blob \
  --signature 3.1.0.tgz.sig \
  --certificate 3.1.0.tgz.crt \
  --certificate-identity-regexp '^https://github\.com/adcontextprotocol/adcp/\.github/workflows/release\.yml@refs/heads/.*$' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  3.1.0.tgz

Older releases that predate signing remain checksum-only — clients (e.g. @adcp/client sync-schemas) should treat missing sidecars as "checksum-only trust" rather than a verification failure, and verify when sidecars are present.

Test plan

  • npx tsc --project server/tsconfig.json --noEmit clean
  • npm run test:unit (587 passed) + typecheck via pre-commit hook
  • bash scripts/sign-protocol-tarball.sh is a no-op without SIGN_PROTOCOL_TARBALL=true (verified locally)
  • Sign script fails hard when opt-in is set but cosign is missing (verified)
  • npm run build:protocol-tarball still builds latest.tgz cleanly (verified)
  • Verify in the next release that cosign step runs and produces sidecars in the version PR
  • Verify /protocol/ discovery endpoint surfaces the new fields after deploy
  • adcp-client#553 follow-up: wire sync-schemas.ts to verify signatures when present

🤖 Generated with Claude Code

Closes #2272.

The /protocol/{version}.tgz.sha256 sidecar lives on the same origin as the
tarball, so a host compromise could serve a malicious tarball with a matching
hash. Add detached Sigstore signatures (.sig + .crt) generated keyless during
the release workflow, so consumers can prove the bundle came from the AdCP
release workflow itself, not just from adcontextprotocol.org.

- scripts/sign-protocol-tarball.sh: signs every dist/protocol/*.tgz when
  GITHUB_ACTIONS=true or SIGN_PROTOCOL_TARBALL=true; no-op locally so
  npm run version doesn't trigger browser OAuth.
- package.json: chain sign step into the version script.
- release.yml: install cosign, grant id-token: write so the workflow OIDC
  token can be exchanged for a Fulcio cert.
- schemas-middleware.ts: serve .sig (octet-stream) and .crt (x-pem-file)
  with immutable cache for pinned versions; surface signature/certificate
  URLs and the expected verification identity in /protocol/.
- docs + bundle README: cosign verify-blob quickstart with the regexp
  identity binding to release.yml.
- .gitignore: keep latest.tgz sidecars ignored alongside the tarball.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Schema Link Check Results

Commit: 3b590df - feat(security): sign protocol tarballs with Sigstore (cosign keyless)

⚠️ Warnings (schema not yet released)

These schemas exist in source but haven't been released yet. The links will be broken until the next version is published:

  • https://adcontextprotocol.org/schemas/v3/enums/specialism.json
    • Schema exists in latest (source) but not yet released in v3
    • Action: This link will work after next 3.x release is published

To fix: Either:

  1. Wait for the next release and merge this PR after the release is published
  2. Use latest instead of a version alias if you need the link to work immediately (note: latest is the development version and may change)
  3. Coordinate with maintainers to cut a new release before merging

@bokelley bokelley merged commit 64a4409 into main Apr 17, 2026
16 checks passed
bokelley added a commit to adcontextprotocol/adcp-go that referenced this pull request Apr 17, 2026
Picks up adcontextprotocol/adcp#2273 — released tarballs now ship
cosign keyless signatures. When .sig/.crt sidecars are present and
cosign is installed, verify against the upstream release workflow
identity. Falls back to checksum-only trust when sidecars are missing
(latest.tgz, pre-signing releases).

Set ADCP_STRICT_VERIFY=1 to require signatures (fails closed when
sidecars are missing or cosign is unavailable).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit to adcontextprotocol/adcp-client that referenced this pull request Apr 17, 2026
Upstream adcontextprotocol/adcp#2273 signs released `/protocol/{version}.tgz`
with Sigstore keyless OIDC. `sync-schemas` now verifies the `.sig` + `.crt`
sidecars against the upstream release workflow's identity when they're
present. Graceful degradation:

  - `latest.tgz` is intentionally unsigned — skip.
  - Missing sidecars (predates signing) → checksum-only, informational log.
  - Sidecars present but `cosign` not installed → checksum-only, install hint.
  - Sidecars present and `cosign` available → verify; fail hard on mismatch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit to adcontextprotocol/adcp-client that referenced this pull request Apr 17, 2026
…rds (#553) (#560)

* feat!: capability-driven compliance runner + tarball-sourced storyboards (#553)

Storyboards move out of `@adcp/client` — `npm run sync-schemas` now pulls
`/protocol/{version}.tgz` from adcontextprotocol.org, verifies its sha256,
and extracts schemas + compliance into local caches. Bundled `storyboards/`
is gone; the compliance cache ships with the published package.

Selection is driven by `get_adcp_capabilities`: `supported_protocols`
resolves to domain baselines, `specialisms` resolves to specialism bundles.
Fails closed when a declared specialism's bundle isn't cached, when a
specialism's parent domain isn't declared, or when the capabilities probe
fails on an agent that advertises the tool. Unknown protocols warn.

Breaking: `platform_type`, `PlatformProfile`, `PLATFORM_STORYBOARDS`,
`platform_coherence`, `expected_tracks`, bundled loaders, and platform
coherence reporting are all removed. See changeset for full migration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: regenerate schemas + apply prettier formatting

Pulls latest `/protocol/latest.tgz` and regenerates types.generated,
core.generated, schemas.generated, and wellknown-schemas.generated.
Runs prettier over the files modified in this branch.

Fixes CI: Code Quality, Test & Build, Validate Schema Synchronization.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(docs): point generate-agent-docs at compliance/cache

The agent docs generator still read from the deleted `storyboards/`
directory, so llms.txt dropped all 49 storyboard flows. Walks the
compliance cache now (universal, domains/**, specialisms/**) and
updates the deep-dive + fictional-entities pointers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(sync): verify cosign sidecars on pinned protocol tarballs

Upstream adcontextprotocol/adcp#2273 signs released `/protocol/{version}.tgz`
with Sigstore keyless OIDC. `sync-schemas` now verifies the `.sig` + `.crt`
sidecars against the upstream release workflow's identity when they're
present. Graceful degradation:

  - `latest.tgz` is intentionally unsigned — skip.
  - Missing sidecars (predates signing) → checksum-only, informational log.
  - Sidecars present but `cosign` not installed → checksum-only, install hint.
  - Sidecars present and `cosign` available → verify; fail hard on mismatch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit to adcontextprotocol/adcp-go that referenced this pull request Apr 18, 2026
Fully wires the consumer side of adcontextprotocol/adcp#2273:
download.sh now reads signature_verification metadata from the
/protocol/ manifest rather than hardcoding the Sigstore identity,
and cosign is installed in CI. Released versions (non-"latest")
MUST be signed — the script fails closed when sidecars or cosign
are missing. latest.tgz remains unsigned by design and falls back
to checksum-only trust.

Migrates consumers to the new IdentityMatchRequest.identities[]
shape introduced upstream: user_token and uid_type moved into an
IdentityToken array (maxItems 3 to match TMPX budget). Touches
router provider filter, targeting engine primary-token selection,
tmpclient wire construction, reference identity agent, and every
test call site. targeting/valkeystore also picks up the earlier
tmproto.ExposeRequest -> targeting.ExposeRequest move.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detached signatures for /protocol/{version}.tgz (supply-chain defense-in-depth)

1 participant