Resposta curta. Na assinatura “keyless” (sem chave) do Sigstore, o signatário comprova uma identidade com um token OIDC (um fluxo de trabalho do GitHub Actions, uma conta Google ou Microsoft). O Fulcio emite um certificado para uma chave efêmera, válido por cerca de dez minutos, com essa identidade dentro. A assinatura é registrada no log de transparência público Rekor, e tudo o que a verificação exige acaba em um bundle (.sigstore.json). A verificação precisa conferir quem assinou, e não apenas se a assinatura é válida. No cosign, isso significa --certificate-identity e --certificate-oidc-issuer, que o cosign exige no modo keyless. A proveniência do npm e os atestados de artefatos do GitHub usam o mesmo mecanismo: npm audit signatures e gh attestation verify fazem essa verificação por você.
Por que assinar no modo keyless
A assinatura clássica de releases com GPG ou com uma chave de assinatura de código de longa duração tem dois problemas crônicos. Os usuários precisam saber qual é a chave certa, então a distribuição de chaves é o verdadeiro problema de confiança. E a chave precisa ficar guardada em algum lugar por anos, em geral em um segredo do CI, de onde pode vazar. O Sigstore substitui a chave por uma identidade que as pessoas já entendem, como “o fluxo de trabalho de release do repositório X” ou “alice@example.com”. E substitui a vida útil da chave por uma entrada de log que prova quando a assinatura foi feita.
O Sigstore é formado por serviços públicos mantidos pela comunidade OpenSSF (a instância pública, a “public-good instance”) e por ferramentas cliente:
- O Fulcio é uma autoridade certificadora que emite certificados X.509 de curta duração para identidades OIDC.
- O Rekor é o log de transparência de assinaturas e atestados.
- Os metadados TUF distribuem aos clientes as raízes de confiança (a CA do Fulcio, a chave do Rekor, as autoridades de carimbo do tempo).
- O cosign é a CLI para blobs e imagens de contêiner. As bibliotecas sigstore-python, sigstore-js, sigstore-go e sigstore-java fazem as mesmas tarefas.
O que acontece quando você assina
- O cliente cria um par de chaves efêmero na memória.
- Ele obtém um token OIDC: no GitHub Actions, do runner (
id-token: write); em um notebook, por meio de um login no navegador. - O Fulcio verifica o token e emite um certificado que vincula a chave pública à identidade: o e-mail ou o URI do fluxo de trabalho vai para o Subject Alternative Name; o emissor e os detalhes do CI, para extensões.
- O cliente assina o digest do artefato e envia a assinatura e o certificado ao Rekor, que devolve uma entrada de log com uma prova de inclusão e um carimbo do tempo assinado.
- A chave privada efêmera é descartada. O bundle guarda o certificado, a assinatura e as evidências do log.
O certificado do bundle que o cosign publica com a própria release v3.1.3 mostra como essa validade é curta:
issuer=O=sigstore.dev, CN=sigstore-intermediate
notBefore=Aug 6 01:03:25 2026 GMT
notAfter=Aug 6 01:13:25 2026 GMT
X509v3 Extended Key Usage:
Code Signing
X509v3 Subject Alternative Name: critical
email:keyless@projectsigstore.iam.gserviceaccount.com
1.3.6.1.4.1.57264.1.1:
https://accounts.google.com
Dez minutos de validade. A assinatura continua verificável depois disso, porque a entrada no Rekor prova que ela foi feita dentro dessa janela. O integratedTime dessa entrada no Rekor é 2026-08-06 01:03:25 UTC.
O bundle
Um bundle é um documento JSON que contém tudo de que um verificador precisa, exceto o artefato e as raízes de confiança. Este é o bundle da release do cosign, resumido:
{
"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json",
"verificationMaterial": {
"certificate": { "rawBytes": "MIIDDzCCApWgAwIBAgIUavXJ..." },
"tlogEntries": [
{
"logIndex": "2352242192",
"kindVersion": { "kind": "hashedrekord", "version": "0.0.1" },
"integratedTime": "1785978205",
"inclusionPromise": { "signedEntryTimestamp": "MEYCIQCvddr55BoX..." },
"inclusionProof": { "logIndex": "2230337930", "treeSize": "2230337938", "rootHash": "62m7TRzwvcKyu+r6...", "hashes": ["..."], "checkpoint": { "envelope": "..." } },
"canonicalizedBody": "eyJhcGlWZXJzaW9u..."
}
],
"timestampVerificationData": { "rfc3161Timestamps": ["..."] }
},
"messageSignature": {
"messageDigest": { "algorithm": "SHA2_256", "digest": "xdMk4JGCaw16eOsW..." },
"signature": "MEUCIECQp5FyO00l..."
}
}
certificateé o certificado folha do Fulcio, com a identidade.tlogEntriescontém a entrada do Rekor: uma prova de inclusão em relação a um checkpoint assinado e um carimbo do tempo assinado da entrada (signed entry timestamp). Com eles, o verificador não precisa consultar o Rekor em momento algum.timestampVerificationDatacontém carimbos do tempo RFC 3161 opcionais, emitidos por uma autoridade de carimbo do tempo.- O conteúdo é
messageSignature, para uma assinatura simples de blob, oudsseEnvelope, para um atestado — uma declaração in-toto assinada.
O verificador de bundles do Sigstore confere todas essas partes no navegador e mostra a identidade do certificado, que é a primeira coisa a olhar quando uma verificação falha.
Verificação: o que importa é a identidade
Veja o cosign verificando o binário da própria release (ghcr.io/sigstore/cosign/cosign:v3.1.3, 20 de outubro de 2026). A release é gerada na infraestrutura do Google, então a identidade é uma conta de serviço do Google e o emissor é o Google, não o GitHub:
$ cosign verify-blob cosign-linux-arm64 --bundle cosign-linux-arm64.sigstore.json \
--certificate-identity keyless@projectsigstore.iam.gserviceaccount.com \
--certificate-oidc-issuer https://accounts.google.com
Verified OK
Agora, os casos de falha. Cada um tem uma mensagem que diz exatamente qual verificação falhou:
--- right email, wrong issuer
error during command execution: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected issuer value "https://token.actions.githubusercontent.com", got "https://accounts.google.com"
--- a different identity
error during command execution: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value "release@example.com", got "keyless@projectsigstore.iam.gserviceaccount.com"
--- no identity at all
error during command execution: --certificate-identity or --certificate-identity-regexp is required for verification in keyless mode
--- a modified binary
error during command execution: failed to verify signature: could not verify message: invalid signature when validating ASN.1 encoded signature
O terceiro caso é uma escolha deliberada de design. Qualquer pessoa pode obter um certificado do Fulcio para a própria identidade e assinar qualquer coisa. Uma assinatura só tem significado depois que você confere se a identidade é a que você espera. O cosign se recusa a verificar assinaturas keyless sem uma identidade (página de erro). O último caso é o que uma adulteração produz (página de erro).
Onde conseguir a identidade esperada? Na documentação do projeto, ou lendo-a uma vez em um bundle em que você confia e fixando-a no seu script de verificação. A opção --certificate-identity-regexp é útil para tags (por exemplo, ...release.yml@refs/tags/v.*), mas ancore a expressão nas duas extremidades. Um padrão como .*sigstore.* corresponde a identidades que qualquer pessoa pode criar.
Assinatura no GitHub Actions
No CI, a identidade é o fluxo de trabalho. O Fulcio coloca o arquivo do fluxo de trabalho e a ref no SAN, e os detalhes em extensões. O bundle de proveniência npm de sigstore@5.0.0 mostra o que um certificado do GitHub Actions carrega, decodificado das extensões X.509 com um pequeno script:
1.3.6.1.4.1.57264.1.8 Issuer (V2) https://token.actions.githubusercontent.com
1.3.6.1.4.1.57264.1.9 Build Signer URI https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main
1.3.6.1.4.1.57264.1.12 Source Repository URI https://github.com/sigstore/sigstore-js
1.3.6.1.4.1.57264.1.13 Source Repository Digest 7d2900eca1c22b3f87c13987c8d4b7c9a29b733a
1.3.6.1.4.1.57264.1.14 Source Repository Ref refs/heads/main
1.3.6.1.4.1.57264.1.18 Build Config URI https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main
1.3.6.1.4.1.57264.1.20 Build Trigger push
1.3.6.1.4.1.57264.1.21 Run Invocation URI https://github.com/sigstore/sigstore-js/actions/runs/26781866618/attempts/1
O cosign também pode verificar essas claims, além do SAN:
$ cosign verify-blob-attestation --bundle provenance.sigstore.json --type slsaprovenance1 \
--certificate-identity-regexp '^https://github\.com/sigstore/sigstore-js/\.github/workflows/release\.yml@' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-github-workflow-repository sigstore/sigstore-js \
--certificate-github-workflow-trigger push \
sigstore-5.0.0.tgz
Verified OK
Para assinar as próprias releases no GitHub Actions, o job precisa de permissão para solicitar um token OIDC. O README do actions/attest, do GitHub, traz esta configuração mínima para a proveniência de build de um arquivo:
permissions:
id-token: write
attestations: write
artifact-metadata: write
steps:
# ... build steps that produce dist/app.tar.gz ...
- uses: actions/attest@v4
with:
subject-path: 'dist/app.tar.gz'
Repositórios públicos usam a instância pública do Sigstore. Repositórios privados usam a instância própria do GitHub, que exige o GitHub Enterprise Cloud. O actions/attest-build-provenance continua funcionando, mas, desde a v4, é só um wrapper em torno do actions/attest. Para assinar um blob com o cosign diretamente no job, o comando é cosign sign-blob --bundle app.sigstore.json app.tar.gz --yes. A ajuda da opção --oidc-provider do cosign lista github-actions entre as fontes de token que ele tenta quando nenhuma é definida. Não fizemos uma assinatura keyless para esta página, porque toda assinatura keyless na instância pública vira uma entrada de log permanente e pública.
Uma assinatura feita por um fluxo de trabalho só é tão confiável quanto o próprio fluxo de trabalho. Qualquer pessoa que consiga fazer push para o branch, ou disparar o fluxo de trabalho, consegue produzir uma assinatura válida com essa identidade. Proteja os fluxos de trabalho de release com proteção de branches e de tags, revisões obrigatórias e aprovações de ambiente, e fixe a verificação em uma ref de tag (@refs/tags/...) quando você publicar releases a partir de tags.
Proveniência do npm
O npm publica dois tipos de evidência do Sigstore para pacotes gerados em um CI compatível: um atestado de publicação, assinado pelo registro, e um atestado de proveniência SLSA, assinado pelo build. O npm audit signatures verifica os dois para tudo o que está instalado:
$ npm audit signatures
audited 52 packages in 2s
52 packages have verified registry signatures
26 packages have verified attestations
(use --json --include-attestations to view attestation details)
Os atestados de uma versão são públicos, em https://registry.npmjs.org/-/npm/v1/attestations/<name>@<version>. Para sigstore@5.0.0:
https://github.com/npm/attestation/tree/main/specs/publish/v0.1 | bundle application/vnd.dev.sigstore.bundle+json;version=0.2
subject: pkg:npm/sigstore@5.0.0 849a897e81bf7b8a8541a6ae...
https://slsa.dev/provenance/v1 | bundle application/vnd.dev.sigstore.bundle.v0.3+json
subject: pkg:npm/sigstore@5.0.0 849a897e81bf7b8a8541a6ae...
workflow: https://github.com/sigstore/sigstore-js .github/workflows/release.yml refs/heads/main
builder: https://github.com/actions/runner/github-hosted
commit: 7d2900eca1c22b3f87c13987c8d4b7c9a29b733a
O digest do subject é o SHA-512 do tarball (849a897e...), então o bundle de proveniência pode ser verificado com o cosign em relação ao .tgz baixado, como mostrado acima. Com a publicação confiável (trusted publishing) do npm (npm CLI 11.5.1 ou posterior), os pacotes publicados a partir do GitHub Actions ou do GitLab CI via OIDC recebem proveniência automaticamente, sem a flag --provenance e sem um token npm de longa duração. O CircleCI tem suporte para a publicação confiável, mas ainda não para a proveniência. Fora de um CI compatível, npm publish --provenance falha com Automatic provenance generation not supported for provider: null. O verificador de proveniência do npm mostra o repositório, o fluxo de trabalho e o commit de qualquer versão de pacote.
Atestados de artefatos do GitHub
O GitHub armazena atestados para os arquivos das releases e os disponibiliza pela própria API. O tarball de release da CLI gh, gh_2.102.0_linux_arm64.tar.gz (SHA-256 7862c86c...), tinha dois atestados em 20 de outubro de 2026: a proveniência SLSA do fluxo de trabalho de implantação do cli/cli e um atestado de release assinado pelo próprio GitHub. Com o bundle de proveniência salvo localmente, o gh attestation verify funciona sem login e imprime a política que aplicou:
$ gh attestation verify gh_2.102.0_linux_arm64.tar.gz --repo cli/cli --bundle gh-1.sigstore.json
Loaded digest sha256:7862c86c72f43df3a2d93ddde6f473285b4e2af61b494849846827e513ef6484 for file:///d/gh_2.102.0_linux_arm64.tar.gz
Loaded 1 attestations from /d/gh-1.sigstore.json
The following policy criteria will be enforced:
- Predicate type must match:................ https://slsa.dev/provenance/v1
- Source Repository Owner URI must match:... https://github.com/cli
- Source Repository URI must match:......... https://github.com/cli/cli
- Subject Alternative Name must match regex: (?i)^https://github\.com/cli/cli/
- OIDC Issuer must match:................... https://token.actions.githubusercontent.com
✓ Verification succeeded!
The following 1 attestation matched the policy criteria
- Attestation #1
- Build repo:..... cli/cli
- Build workflow:. .github/workflows/deployment.yml@refs/heads/trunk
- Signer repo:.... cli/cli
- Signer workflow: .github/workflows/deployment.yml@refs/heads/trunk
Com --repo example/cli, a mesma verificação termina em ✗ Sigstore verification failed. A política padrão (proprietário, repositório, prefixo do SAN) é um bom começo. Para automatizar releases, acrescente --signer-workflow para fixar o arquivo exato do fluxo de trabalho, e --deny-self-hosted-runners se seus builds rodam só em runners hospedados pelo GitHub. O mesmo bundle também passa na verificação do cosign: cosign verify-blob-attestation --type slsaprovenance1, com o SAN https://github.com/cli/cli/.github/workflows/deployment.yml@refs/heads/trunk, imprimiu Verified OK.
O segundo atestado é diferente. O tipo de predicado dele é https://in-toto.io/attestation/release/v0.2. O certificado nomeia URI:https://dotcom.releases.github.com e foi emitido por O=GitHub, Inc., CN=Fulcio Intermediate l1, o Fulcio próprio do GitHub, com um carimbo do tempo RFC 3161 em vez de uma entrada no Rekor. O GitHub o cria para repositórios com releases imutáveis ativadas. Ele prova que o arquivo pertence à release, e não como ele foi gerado. Os comandos gh release verify e gh release verify-asset fazem essa verificação. No nosso teste, o gh release verify-asset precisou de um gh com login feito: sem token, o gh 2.102.0 parou com To get started with GitHub CLI, please run: gh auth login.
Quando o keyless não serve
Builds em ambiente isolado (air-gapped), artefatos privados que não devem aparecer em um log público ou políticas que exigem uma chave em um HSM: o cosign também assina com chaves, e o envio ao log pode ser desligado. Testado com o cosign v3.1.3:
$ cosign generate-key-pair
Private key written to cosign.key
Public key written to cosign.pub
$ cosign sign-blob --key cosign.key --tlog-upload=false --use-signing-config=false --bundle app.sigstore.json app-1.0.0.tar.gz --yes
Using payload from: app-1.0.0.tar.gz
Signing artifact...
Wrote bundle to file app.sigstore.json
$ cosign verify-blob --key cosign.pub --bundle app.sigstore.json --insecure-ignore-tlog=true app-1.0.0.tar.gz
WARNING: Skipping tlog verification is an insecure practice that lacks transparency and auditability verification for the blob.
Verified OK
$ cosign verify-blob --key cosign.pub --bundle app.sigstore.json app-1.0.0.tar.gz
error during command execution: failed to verify log inclusion: not enough verified log entries from transparency log: 0 < 1
Nesse caso, o bundle contém só uma indicação publicKey, e você volta a distribuir e proteger uma chave de longa duração — justamente o problema que a assinatura keyless elimina. Para uma configuração privada, mas ainda auditável, rode sua própria instância do Sigstore (Fulcio, Rekor, TUF) ou use a de um fornecedor. O GitHub faz isso para repositórios privados.
O estado da infraestrutura em 2026
- O Rekor v2, um log baseado em tiles, chegou à disponibilidade geral em outubro de 2025. Os verificadores precisam do cosign 2.6.0 ou 3.0.1, ou de uma versão posterior. O Rekor v2 abandonou o índice de busca.
- Em junho de 2026, os mantenedores anunciaram que a instância pública “continuará usando o Rekor v1 como log padrão no futuro previsível”. O Rekor v2 continua em produção para os ecossistemas que optarem por ele.
- Um Sigstore pós-quântico separado, com ML-DSA, tem “previsão para meados de 2027”, segundo o anúncio de outubro de 2026. A instância clássica deve continuar funcionando até uma desativação “provisoriamente programada para 2029 ou 2030”. De um jeito ou de outro, o formato continua sendo o bundle.
Do lado da verificação, quase nada muda: mantenha os clientes atualizados, verifique a partir de bundles e fixe identidades, não URLs de log.
Erros comuns
- Verificar a assinatura, mas não a identidade. Uma assinatura válida do Sigstore só diz que alguém assinou.
- Padrões de identidade amplos demais. Expressões regulares sem âncoras, ou uma verificação do emissor sem verificação do SAN.
- Fixar em um branch quando você publica releases a partir de tags.
@refs/heads/mainaceita qualquer coisa gerada a partir domain, inclusive um build que ninguém pretendia publicar. - Supor que a assinatura prova o build. Uma assinatura de blob prova quem assinou os bytes. A proveniência (SLSA, in-toto) diz como e a partir de quê eles foram gerados. Verifique aquela que sua política exige.
- Assinar releases pessoais com um e-mail pessoal sem perceber que o e-mail fica no certificado e, portanto, em um log público e permanente.
- Distribuir os arquivos desanexados
.sige.pemdas versões antigas do cosign. Publique o bundle.sigstore.json. É o formato que todos os verificadores atuais entendem.
Fontes
- Documentação do Sigstore e o formato do bundle.
- cosign v3.1.3 e os bundles das releases dele.
- Blog do Sigstore: Disponibilidade geral do Rekor v2 (outubro de 2025), O sigstore.dev e a evolução do Rekor (junho de 2026), Criptografia pós-quântica para o Sigstore (outubro de 2026).
- npm: publicação confiável (trusted publishing) e o endpoint de atestados do registro.
- GitHub:
actions/attest,gh attestation verify,gh release verifye o predicado de release do in-toto.
A verificação foi feita em 20 de outubro de 2026 em ghcr.io/sigstore/cosign/cosign:v3.1.3, com o npm 11.19.1 e o gh 2.102.0 (o tarball oficial da release) em node:24.22.0-trixie.