> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chainloop.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Attestations

## Overview

An attestation is a signed and verifiable unit of data sent to Chainloop. Users and CI systems use the Chainloop CLI to "craft" attestations, add pieces of evidence (materials) to them, and "push" them to Chainloop service (the evidence store).

**Attestations are performed with the Chainloop command line interface (CLI) and its lifecycle has the following stages: `init`, `add`, `push` or `reset`.**

As you can see, it mimics the workflow of a commonly used version control tool, and this is not by coincidence. Chainloop wants to make sure that the tooling feels familiar to developers and that no security jargon leaks into this stage of the process. For a developer, creating an attestation must be as simple as initializing it, adding pieces of evidence (materials) to it, and pushing it.

## Attestation lifecycle

```bash theme={"dark"}
# initialize the attestation. Downloading the contract
> chainloop att init --workflow workflow-name --project project-name

# add materials to the attestation
> chainloop att add --value ghcr.io/chainloop-dev/chainloop/control-plane:latest
> chainloop att add --value reports/cyclonedx.json --kind SBOM_CYCLONEDX_JSON

# generate, sign and push the attestation to chainloop
> chainloop att push
```

### attestation init

<Tip>
  A project version and optionally a version can be provided during initialization. You can read more [here](/concepts/projects-versions#attest-to-a-specific-version).
</Tip>

During this stage, the crafting tool will contact chainloop control plane to

* Signal the intent of starting an attestation.
* Retrieve or create the associated [workflow contract](/concepts/contracts)
* If the contract has a specified [runner context type](/concepts/contracts#runner-context), check that we are compliant with it.
* Initialize environment variables, explicitly stated in the contract and other contextual information.

### attestation add

Add the **materials required by [the contract](/concepts/contracts)** and any other additional (see [contractless materials](/concepts/attestations#contract-less%2C-auto-discoverable-materials) pieces of evidence, i.e artifact, OCI image ref, SBOM.

The `add` command knows how to handle each kind of material transparently to the user.

For example

* ARTIFACT kinds will be uploaded to your artifact registry and referenced by their content digest.
* CONTAINER\_IMAGE kinds will be resolved to obtain their repository digests using the local authentication keychain.
* SBOM\_CYCLONEDX\_JSON will validate the right SBOM format and upload it to the artifact registry.

<Tip>
  If your CycloneDX SBOM has minor schema deviations (e.g., generated by tools that produce slightly non-compliant output, or enriched/modified by scripts after generation), you can skip strict validation using the `--no-strict-validation` flag.
</Tip>

For a complete list of available material types, see the [reference](/concepts/material-types) section.

### attestation push

This stage will take the current crafting state, validate that it has all the required materials and

* Create a signed, attestation envelope.
* Push it to the control plane for storage

<Tip>
  You can leverage multiple signing mechanisms, including keyless signing and verification. For a complete list please refer to the [signing](/reference/signing) guide.
</Tip>

### attestation reset

By using the `reset` command we can indicate to the control plane that something went wrong or we want to abort the attestation process.

### attestation status

See the state of the current crafting process.

## Attestation format

The generated attestations are in the form of a [Sigstore bundle](https://github.com/sigstore/protobuf-specs/blob/21dac573b11eaa14208064140cb889a44f307377/protos/sigstore_bundle.proto#L111) signed with the [signing mechanism of your choice](/reference/signing).

This bundle contains the attestation in the form of a DSSE envelope, and the verification materials required to verify the attestation. These include intermediate certificates, in the case of ephemeral certificates or timestamp services.

Example

```json [expandable] theme={"dark"}
{
  "mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.3",
  "verificationMaterial": {
    "certificate": {
      "rawBytes": "MIIB5DCCAWqgAwIBAgIUXMnEd59cEi+F8IwW5xPEZowFKGgwCgYIKoZIzj0EAwMwLDEWMBQGA1UECgwNY2hhaW5sb29wLmRldjESMBAGA1UEAwwJY2hhaW5sb29wMB4XDTI1MDMyNDExNTMzOFoXDTI1MDMyNDEyMDMzOFowLzEtMCsGA1UEChMkOTNkMDIyNzUtODUzYy00YWQ2LTlkNjAtOGY1NjJiMTIzZmQyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEelmK4JQi5e/4Uv4DFi/IszhxhZUMtMhS5pMlt1M/y5V9JBNXllQ7lFCO0UZshUP0E8pLU2lb62dHUoaCen6qiKNnMGUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBTDS8wdOFGSi79ymJWQy3p9irm6szAfBgNVHSMEGDAWgBTmzi6ht2KmAQFfrGvKtZDRGyR0hzAKBggqhkjOPQQDAwNoADBlAjBEEgeDUR69LF/bMPEQ8pT0h/kd6r8FfhXZS7l04PVUxGHLLQfVSdmFoG6CIKUV18cCMQDpJBZw9ARQk29XKXzg/D8K96RG4MF0vS+F2K/+RmtsRDuSSwVugQFwiQ5cK0Jkvds="
    },
    "timestampVerificationData": {
      "rfc3161Timestamps": [
        {
          "signedTimestamp": "MIIEIzADAgEAMIIEGgYJKoZIhvcNAQcCoIIECzCCBAcCAQMxDzANBglghkgBZQMEAgEFADB3BgsqhkiG9w0BCRABBKBoBGYwZAIBAQYJYIZIAYb9bAcBMDEwDQYJYIZIAWUDBAIBBQAEIHIr+U3/ec9GMyj/uc5SV8SYq76EGyficbNlHb5Mfr7gAhACdzgFINJaQks5u3Cw+hDFGA8yMDI1MDMyNDExNTMzOFoxggN2MIIDcgIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAuuZrxaun+Vh8b56QTjMwQwDQYJYIZIAWUDBAIBBQCggdEwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTAzMjQxMTUzMzhaMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFNvThe5i29I+e+T2cUhQhyTVhltFMC8GCSqGSIb3DQEJBDEiBCCA8prjLb9KaHEJLWT0JjlVNEjGC5rKAWM++pSkO5Co3DA3BgsqhkiG9w0BCRACLzEoMCYwJDAiBCB2dp+o8mMvH0MLOiMwrtZWdf7Xc9sF1mW5BZOYQ4+a2zANBgkqhkiG9w0BAQEFAASCAgCWm6hXI4Bz3i1lLEs4LQaZdG+Znq51CnrmvWK/Pmh9YNAGg9zmL7tOIJDe/0x1uGXIvSugHmeb3lxCn5xVayh0gfcl3TvaT0MT2b/FgJZ7LAcu4onnpmASRT/36imQNO0zkrsfwanhtmTvSlAnmiDBqpypf2MvHkuYIxZEaGj8czsc7iJaFQbnc2yHNjwXYej7Q4R/TsRFZ76BY5QjuRnFXEs4ay/9wHONpfe3mqkW5xFRSg80owcQil84xPoy/vrHq+eQUXKSOwg1oUrxrmQQbj9h99I13df7FjK8TStdjIJYFF9iRCo27jceczL2lmcwHjxbTu209DlRyrkrFqoAGT2ayfRnVhLgUiuuCtQjhtrt2bZYzqqOC6vU1yuqXvNz7O1bv/KxOy3jWOPAUDKF+ScxJX9hlx60PhtetEl9/WGhSdynzRkvA5x7HnzeQ5zrv7cfXcOx5IxxKJxznVCPbpRPbNyLbHU2N0OwIg3Act4ZkCtsOmQwTTbz+zDsoQN/ZiKiRtnWBwSS3nCwAQeadHpy2qa/Px2arYK3KbMskyk8WvrX7vtfA20Qhp/P6gaG29nHCUEm8qhUR/MXP5hrPZPXliv5S099UZxhH1isSM977dnYOKFt5SlBh1FV72pEP0wmenQrPl47vLYNIjr/Jt64v5Y9rzZGoe/hKltugQ=="
        }
      ]
    }
  },
  "dsseEnvelope": {
    "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiY2hhaW5sb29wLndvcmtmbG93LmNvZGVxbCIsImRpZ2VzdCI6eyJzaGEyNTYiOiIxY2NkNjMyM2I3ZGZiODU5YTU1NDMzNGY4MmU2ZWUwYzY0NTVkOGVjZjQ2NzYwODNiNmYwODUxOTI3Y2UzNTg0In19LHsibmFtZSI6ImdpdC5oZWFkIiwiZGlnZXN0Ijp7InNoYTEiOiIwY2M0YjAyYWI4OWM3MDA2Y2I1NTVlYmU5YmFlZjE4OGJhNTc0NzE1In0sImFubm90YXRpb25zIjp7ImF1dGhvci5lbWFpbCI6IjQ5Njk5MzMzK2RlcGVuZGFib3RbYm90XUB1c2Vycy5ub3JlcGx5LmdpdGh1Yi5jb20iLCJhdXRob3IubmFtZSI6ImRlcGVuZGFib3RbYm90XSIsImRhdGUiOiIyMDI1LTAzLTI0VDExOjQ5OjE2WiIsIm1lc3NhZ2UiOiJjaG9yZShkZXBzKTogQnVtcCBnaXRodWIuY29tL2dvbGFuZy1qd3Qvand0L3Y0IGZyb20gNC41LjEgdG8gNC41LjIgKCMxOTEyKVxuXG5TaWduZWQtb2ZmLWJ5OiBkZXBlbmRhYm90W2JvdF0gPHN1cHBvcnRAZ2l0aHViLmNvbT5cbkNvLWF1dGhvcmVkLWJ5OiBkZXBlbmRhYm90W2JvdF0gPDQ5Njk5MzMzK2RlcGVuZGFib3RbYm90XUB1c2Vycy5ub3JlcGx5LmdpdGh1Yi5jb20+IiwicmVtb3RlcyI6W3sibmFtZSI6Im9yaWdpbiIsInVybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9jaGFpbmxvb3AtZGV2L2NoYWlubG9vcCJ9XSwic2lnbmF0dXJlIjoiLS0tLS1CRUdJTiBQR1AgU0lHTkFUVVJFLS0tLS1cblxud3NGY0JBQUJDQUFRQlFKbjRVYThDUkMxYVE3dXU1VWhsQUFBdGlrUUFEN2poZnUxTllERmFkYzRmd3JHR2dvNVxuK1FjUy9GZ1puOFZ5a1lhYTNJWjFQaFFIZTJUMTAxZHBHd08vNC83OFVwdkdkUkRtdFczZHpyL1IyUWZjY2lHWVxuaUdYUFluZ3FBUWtUQkZWbnpPdVdNa3lWSjgrTFdtbTgxM2g4Yk1XRG13eFovSkVWWkNUbFpjRnBoZVd5NVluSlxuVnlDVEcyT3BHTlRZR3VZRUNYanZmSDFFaWNSaXYrSDdUVkJSYzZRM0tFUWZ0d2hOelI5Z2t6Nzl0dWg0Wld5VVxuTmRvalBhd0VhcXJqVUR0L1hiQlhPRzVGSFZNYW0wTXVnYXJrdHBySm1rZnMvZldwL2pkMnp5SDcvRzNkWnFVZVxuTnlMMWV6dTRvVkRKOWorWVJsdFpscnlpdnVxVDdFU3hNY2hDQWV6TGh6dHpWcUFvdVE1OW5JNHBISjJBWERVQlxuQUU4aTRLcUxjc2JVeW5yUjFWNDhBSGpuOWZYd0l3WjlsUzh4elcybHBBcmpyeGlNb0JPSGtqUDVoUnZHVjhHRlxuM3JpTWlPZUc3d3NPakUwcHFCRUhoRm12T3JEK0VIT3RnRkFKZ05ZbkZYdTFrUDRCMms0V2FjUDhtWG5qWGVmZ1xudFNsTHZxVEh2UUpWbm9EMUwxVUZZYUZzQjR0MnpPQjU5bUd3MVJSK3lHcUNLMEdMYmt2Y0o1RU14UzdzWVJaZ1xuTHI2dzd2VGVCbGV6SCtSTjUwUWJWaFBxakRNUG1WejhCeXVUY2VlbldFbk1aN0FHa2p5WFZFS21TcFpUN2l2OFxuZ09nanVQL0dvT3ZjQS9STVpzN0dPaGVBRGhONlY2V29TcmZnODl6UFkzTXZSdFhwNlg2b3BUL1N0RDdTZ21sRVxuZVg4QVkrVE1EVHJrNFp6SW55Z1Fcbj0zYXBUXG4tLS0tLUVORCBQR1AgU0lHTkFUVVJFLS0tLS1cblxuIn19LHsibmFtZSI6ImdvLnNhcmlmIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImVjYTM3YjIyMDMxNDY4NGYxZmQ0OWQxMTJmNWFmY2ZhNzM2N2Y2ZDQwZGUyMGE3ZTFiNmRmODBhMDA1MGQ5NTAifSwiYW5ub3RhdGlvbnMiOnsiY2hhaW5sb29wLm1hdGVyaWFsLmNhcyI6dHJ1ZSwiY2hhaW5sb29wLm1hdGVyaWFsLm5hbWUiOiJzYXJpZi1yZXN1bHRzIiwiY2hhaW5sb29wLm1hdGVyaWFsLnR5cGUiOiJTQVJJRiJ9fV0sInByZWRpY2F0ZVR5cGUiOiJjaGFpbmxvb3AuZGV2L2F0dGVzdGF0aW9uL3YwLjIiLCJwcmVkaWNhdGUiOnsiYnVpbGRUeXBlIjoiY2hhaW5sb29wLmRldi93b3JrZmxvd3J1bi92MC4xIiwiYnVpbGRlciI6eyJpZCI6ImNoYWlubG9vcC5kZXYvY2xpLzAuMTgzLjBAc2hhMjU2OmVmNjIzZjY3ZTI3NDRjODFlMDc3NTNhMzdiOGM2NThhZDI2NGZlMTIwNWJmMzVjMTVlZTcxYzY4MTdkZmJkZjkifSwiZW52Ijp7IkdJVEhVQl9BQ1RPUiI6Im1pZ21hcnRyaSIsIkdJVEhVQl9SRUYiOiJyZWZzL2hlYWRzL21haW4iLCJHSVRIVUJfUkVQT1NJVE9SWSI6ImNoYWlubG9vcC1kZXYvY2hhaW5sb29wIiwiR0lUSFVCX1JFUE9TSVRPUllfT1dORVIiOiJjaGFpbmxvb3AtZGV2IiwiR0lUSFVCX1JVTl9JRCI6IjE0MDM0NTAxMTI1IiwiR0lUSFVCX1NIQSI6IjBjYzRiMDJhYjg5YzcwMDZjYjU1NWViZTliYWVmMTg4YmE1NzQ3MTUiLCJSVU5ORVJfTkFNRSI6IkdpdEh1YiBBY3Rpb25zIDU3IiwiUlVOTkVSX09TIjoiTGludXgifSwibWF0ZXJpYWxzIjpbeyJhbm5vdGF0aW9ucyI6eyJjaGFpbmxvb3AubWF0ZXJpYWwuY2FzIjp0cnVlLCJjaGFpbmxvb3AubWF0ZXJpYWwubmFtZSI6InNhcmlmLXJlc3VsdHMiLCJjaGFpbmxvb3AubWF0ZXJpYWwudHlwZSI6IlNBUklGIn0sImRpZ2VzdCI6eyJzaGEyNTYiOiJlY2EzN2IyMjAzMTQ2ODRmMWZkNDlkMTEyZjVhZmNmYTczNjdmNmQ0MGRlMjBhN2UxYjZkZjgwYTAwNTBkOTUwIn0sIm5hbWUiOiJnby5zYXJpZiJ9XSwibWV0YWRhdGEiOnsiY29udHJhY3ROYW1lIjoiY2hhaW5sb29wLXZhdWx0LWNvZGVxbCIsImNvbnRyYWN0VmVyc2lvbiI6IjEwIiwiZmluaXNoZWRBdCI6IjIwMjUtMDMtMjRUMTE6NTM6MzguNDM0NDIwOTY0WiIsImluaXRpYWxpemVkQXQiOiIyMDI1LTAzLTI0VDExOjQ5OjQyLjc1Mzc0MjY4N1oiLCJuYW1lIjoiY29kZXFsIiwib3JnYW5pemF0aW9uIjoicmVhZC1vbmx5LWRlbW8iLCJwcm9qZWN0IjoiY2hhaW5sb29wIiwicHJvamVjdFZlcnNpb24iOiJ2MC4xODQuMCIsInByb2plY3RWZXJzaW9uUHJlcmVsZWFzZSI6dHJ1ZSwidGVhbSI6ImNvcmUiLCJ3b3JrZmxvd0lEIjoiMjIzODk3ZTUtMTRiYi00ZDJjLWFmMDktZjU1Mzg4MzBjN2RiIiwid29ya2Zsb3dSdW5JRCI6IjE3MDI0ZWZkLTY1YWQtNDdkZC05NjE2LTVjYzA5ZTY2YjU2YSJ9LCJwb2xpY3lBdHRCbG9ja2VkIjpmYWxzZSwicG9saWN5QmxvY2tCeXBhc3NFbmFibGVkIjpmYWxzZSwicG9saWN5Q2hlY2tCbG9ja2luZ1N0cmF0ZWd5IjoiRU5GT1JDRUQiLCJwb2xpY3lFdmFsdWF0aW9ucyI6eyJDSEFJTkxPT1AuQVRURVNUQVRJT04iOlt7ImRlc2NyaXB0aW9uIjoiVmVyaWZpZXMgdGhhdCB0aGUgYXR0ZXN0YXRpb24gZXhwbGljaXRseSByZWZlcmVuY2VzIGEgc3BlY2lmaWMgR2l0IGNvbW1pdCIsIm5hbWUiOiJzb3VyY2UtY29tbWl0IiwicG9saWN5UmVmZXJlbmNlIjp7ImFubm90YXRpb25zIjp7Im5hbWUiOiJzb3VyY2UtY29tbWl0Iiwib3JnYW5pemF0aW9uIjoiIn0sImRpZ2VzdCI6eyJzaGEyNTYiOiIxNjRiYzMyMmJmMzUwZDZiYjQ1NzY4Yzg2ZGIxODBmNTMxNTA4NWM3OTFhZGViYWYwOTQ5MGY5Y2NlOTk1MjhjIn0sIm5hbWUiOiJzb3VyY2UtY29tbWl0IiwidXJpIjoiY2hhaW5sb29wOi8vY2hhaW5sb29wLXBsYXRmb3JtLWJhY2tlbmQucGxhdGZvcm0tcHJvZC5zdmMuY2x1c3Rlci5sb2NhbC9zb3VyY2UtY29tbWl0In0sInJlcXVpcmVtZW50cyI6WyJjaGFpbmxvb3AtYmVzdC1wcmFjdGljZXMvY29tbWl0LXNpZ25lZCJdLCJza2lwcGVkIjpmYWxzZSwidHlwZSI6IkFUVEVTVEFUSU9OIiwid2l0aCI6eyJjaGVja19zaWduYXR1cmUiOiJ5ZXMifX1dfSwicG9saWN5SGFzVmlvbGF0aW9ucyI6ZmFsc2UsInJ1bm5lclR5cGUiOiJHSVRIVUJfQUNUSU9OIiwicnVubmVyVVJMIjoiaHR0cHM6Ly9naXRodWIuY29tL2NoYWlubG9vcC1kZXYvY2hhaW5sb29wL2FjdGlvbnMvcnVucy8xNDAzNDUwMTEyNSJ9fQ==",
    "payloadType": "application/vnd.in-toto+json",
    "signatures": [
      {
        "sig": "MEQCIDsOnb1jq4cLgNwt5mIhskW6d0M1w0IZDqRsGiBA6FKbAiB4+KGLIDHFmajclbqNbUfNH465mSAljMsdJ9qTGN6I6Q=="
      }
    ]
  }
}
```

You can retrieve the attestation in multiple formats by inspecting its associated workflow run

<Tabs>
  <Tab title="Web UI">
    <img src="https://mintcdn.com/chainloop/qP5Aepelxm7aUyXA/concepts/img/attestation-1.png?fit=max&auto=format&n=qP5Aepelxm7aUyXA&q=85&s=ff48b0ea047a18339c88bd5c3b80dd52" alt="" width="2544" height="1772" data-path="concepts/img/attestation-1.png" />
  </Tab>

  <Tab title="CLI">
    ```bash theme={"dark"}
    chainloop workflow run describe --id [run-id] -o attestation
    ```

    ```
    {
       "mediaType":  "application/vnd.dev.sigstore.bundle+json;version=0.3",
       "verificationMaterial":  {
          "certificate":  {
             "rawBytes":  "MIIB5DCCAWqgAwIBAgIURkwgAWZJ8p9sCZAc+7EHWDwIzqYwCgYIKoZIzj0EAwMwLDEWMBQGA1UECgwNY2hhaW5sb29wLmRldjESMBAGA1UEAwwJY2hhaW5sb29wMB4XDTI1MDMyNDExNTExNFoXDTI1MDMyNDEyMDExNFowLzEtMCsGA1UEChMkOTNkMDIyNzUtODUzYy00YWQ2LTlkNjAtOGY1NjJiMTIzZmQyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfssrR0/iiWDa02II+QdXtkxNA3xYecBvqji+WYydbqPxq5XkOJH+Zvx/gt+MxXdb8Ys+CZjjqELkyZwIYoTFMqNnMGUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBS4SFINf79BlzryGyeog0mt4LD7MDAfBgNVHSMEGDAWgBTmzi6ht2KmAQFfrGvKtZDRGyR0hzAKBggqhkjOPQQDAwNoADBlAjEAu+N6rwnmAEvZfYVf/kCfLBnu1UuBAkRq2SuBDhXa7QTWNAdDtEbVaqSUN7cV9C+yAjAK1uGOaeDIzbwdyH5XrI9AX3Nr7wkKtzNfVqNuvUTaszsnlyBlWrKf6LCfUcm2Z04="
          },
          "timestampVerificationData":  {
             "rfc3161Timestamps":  [
                {
                   "signedTimestamp":  "MIIEIzADAgEAMIIEGgYJKoZIhvcNAQcCoIIECzCCBAcCAQMxDzANBglghkgBZQMEAgEFADB3BgsqhkiG9w0BCRABBKBoBGYwZAIBAQYJYIZIAYb9bAcBMDEwDQYJYIZIAWUDBAIBBQAEIOS4J9x9UvtEHDh9Y20ZZdE89O8ur1T7PHZD6rWoLTy+AhANITA+z32x2bAR68fdeViPGA8yMDI1MDMyNDExN
    ```
  </Tab>
</Tabs>

## Commit Verification

Chainloop automatically captures commit signature verification information during attestation initialization. When running in GitHub Actions or GitLab CI environments, the CLI queries the platform's API to verify if the commit was cryptographically signed and validated.

### How it works

During the `attestation init` stage, Chainloop:

1. **Detects the CI/CD environment** - Identifies if running in GitHub Actions, GitLab CI, or other supported platforms
2. **Queries platform APIs** - Uses `GITHUB_TOKEN` (for GitHub Actions) or `CI_JOB_TOKEN` (for GitLab CI) to check commit signature verification status
3. **Records verification metadata** - Captures verification status, signature algorithm, and key information
4. **Adds to attestation** - Includes verification data as annotations in the attestation

### Supported platforms

Commit verification is automatically performed in:

* **GitHub Actions** - Verifies GPG/PGP, SSH, and X.509 signatures through GitHub's commit verification API
* **GitLab CI** - Verifies GPG/PGP, SSH, and X.509 signatures through GitLab's commit signature API
* **Dagger** - Supports verification when running within GitHub Actions or GitLab CI environments

### Verification statuses

The commit verification status is recorded with one of the following values:

* `verified` - Commit signature was successfully verified by the platform
* `unverified` - Commit has a signature but verification failed (invalid signature, unverified key, etc.)
* `unavailable` - Verification could not be performed (API error, missing token, network issue)
* `not_applicable` - Commit is not signed or platform doesn't support verification
* `unspecified` - Verification status is unknown

### Attestation annotations

The commit verification information is stored in the attestation subject annotations:

```json theme={"dark"}
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    {
      "name": "chainloop.workflow.user-verification",
      "digest": {
        "sha256": "722c5b5c8b06e7a6f0857481cbcbcad547bb460c108a7fa41eacbd30d4ce45dc"
      }
    },
    {
      "name": "git.head",
      "digest": {
        "sha1": "8f6d1a7028511918c6b7e6a5f3ac6bf8bafe7213"
      },
      "annotations": {
        "author.email": "johndoe@example.com",
        "author.name": "John Doe",
        "author.verification_status": "verified",
        "date": "2026-01-13T12:31:25Z",
        "message": "Initial commit",
        "remotes": [
          {
            "name": "origin",
            "url": "https://github.com/johndoe/example-repo"
          }
        ],
        "signature": "-----BEGIN PGP SIGNATURE-----\n\THE SIGNATURE-----END PGP SIGNATURE-----\n\n",
        "signature.algorithm": "PGP"
      }
    }
  ],
```

<Note>
  Commit verification is performed on a best-effort basis and is non-blocking. If verification cannot be performed (e.g., missing token, API error), the attestation will continue with the status recorded as `unavailable`. This ensures that attestation workflows are not disrupted by verification issues.
</Note>

## Your first attestation

To perform your first attestation locally, please refer to the [getting started guide](/get-started/first-attestation).

## CI integration

Integrating the attestation process usually requires:

* [Create an expose an API token](/reference/api-tokens) or leverage native OIDC support (if applicable)
* Download the [Chainloop CLI](/get-started/setup) and perform the [attestation lifecycle](/concepts/attestations#attestation-lifecycle)

<Note>
  Native CI/CD runner integrations (e.g., Jenkins plugin, GitHub action) are under development. In the meantime, please use the chainloop CLI as shown in [these examples](/concepts/attestations#attestation-lifecycle)
</Note>

<Tabs>
  <Tab title="GitHub">
    ```yaml [expandable] theme={"dark"}
    # Example of GitHub action that 
    # - Builds a go binary and associated container image using go-releaser
    # - Extract a CycloneDX SBOM using Syft
    # - Stores the required materials stated in this Chainloop contract
    #   https://github.com/chainloop-dev/chainloop/blob/main/docs/examples/contracts/skynet/contract.yaml
    # - Pushes the resulting attestation to the control plane
    name: Release
    on:
      push:
        tags:
          - "v*.*.*"
    jobs:
      release:
        env:
          # Version of Chainloop to install
          CHAINLOOP_TOKEN: ${{ secrets.CHAINLOOP_WF_RELEASE }}
          # The name of the workflow registered in Chainloop control plane
          CHAINLOOP_WORKFLOW_NAME: build-and-test
          CHAINLOOP_PROJECT_NAME: skynet
        name: "Release binary and container images"
        runs-on: ubuntu-latest
        permissions:
          contents: write # required for goreleaser
        steps:
          - name: Install Chainloop
            run: |
              curl -sfL https://dl.chainloop.dev/cli/install.sh | bash -s

          - name: Checkout
            uses: actions/checkout@v4

          - name: Initialize Attestation
            run: |
              chainloop attestation init --workflow $CHAINLOOP_WORKFLOW_NAME --project $CHAINLOOP_PROJECT_NAME

          - name: Set up Go
            uses: actions/setup-go@v3
            with:
              go-version: "1.23.6"

          # Generate SBOM using syft in cycloneDX format
          - uses: anchore/sbom-action@v0
            with:
              image: ****.dkr.ecr.us-east-1.amazonaws.com/container-image:${{ github.ref_name }}
              format: cyclonedx-json
              output-file: /tmp/skynet.cyclonedx.json

          - name: Add Attestation Artifacts
            run: |
              # Add binaries created by goreleaser
              chainloop attestation add --name [binary-name] --value [binary-path]

              # Created container image
              chainloop attestation add --name control-plane-image --value ****.dkr.ecr.us-east-1.amazonaws.com/container-image:${{ github.ref_name }}

              # This is just an example of adding a key/val material type
              # Alternatively, GITHUB_SHA could have been added to the contract env variables allowList
              chainloop attestation add --name build-ref --value ${GITHUB_SHA}

              # Attach SBOM
              chainloop attestation add --name skynet-sbom --value /tmp/skynet.cyclonedx.json

          - name: Finish and Record Attestation
            if: ${{ success() }}
            run: |
              # Note that these commands are using CHAINLOOP_TOKEN env variable to authenticate
              chainloop attestation push

          - name: Mark attestation as failed
            if: ${{ failure() }}
            run: |
              chainloop attestation reset

          - name: Mark attestation as cancelled
            if: ${{ cancelled() }}
            run: |
              chainloop attestation reset --trigger cancellation
    ```
  </Tab>

  <Tab title="GitLab">
    <Note>
      For GitLab we support keyless attestations using GitLab's OIDC tokens. Please refer to the [GitLab Keyless Attestations](/guides/gitlab-keyless) guide for more information.
    </Note>

    ```yaml [expandable] theme={"dark"}
    # Example of GitLab Pipeline that
    # - Builds a go binary and associated container image using go-releaser
    # - Extract a CycloneDX SBOM using Syft
    # - Stores the required materials stated in this Chainloop contract
    #   https://github.com/chainloop-dev/chainloop/blob/main/docs/examples/contracts/container-image-sbom/gitlab.yaml
    # - Pushes the resulting attestation to the control plane
    stages:
      - release

    variables:
      # Service account associated to this workflow in Chainloop's control plane
      CHAINLOOP_TOKEN: $CHAINLOOP_TOKEN

      # This job pushed container images to GitLab OCI registry
      DOCKER_REGISTRY: $CI_REGISTRY
      DOCKER_USERNAME: $CI_REGISTRY_USER
      DOCKER_PASSWORD: $CI_REGISTRY_PASSWORD
      GITLAB_TOKEN: $CI_JOB_TOKEN

    # Download and store Chainloop CLI as an artifact since the next phase uses docker:stable
    # runner image has not capabilities (wget/curl) to install it
    download_chainloop:
      stage: release
      only:
        refs:
          - tags
      script:
        # We need to install it in the current path in order to be archived
        - curl -sfL https://dl.chainloop.dev/cli/install.sh | bash -s -- --path .
      artifacts:
        paths:
          - chainloop
        expire_in: 5 mins

    release:
      stage: release
      image: docker:stable
      services:
        - docker:dind
      needs:
        - job: download_chainloop
      only:
        refs:
          - tags

      before_script:
        # Initialize attestation
        - chainloop att init --token $CHAINLOOP_TOKEN --workflow build-and-test --project skynet

        # Install Syft
        - wget --no-verbose https://raw.githubusercontent.com/anchore/syft/main/install.sh -O - | sh -s -- -b /usr/local/bin

      script:
        # Both CI_JOB_TOKEN and GITLAB_TOKEN required to be passed as env variables
        # https://github.com/goreleaser/goreleaser/blob/8ebefd251e0eddd3c294b4d45b6e637783a252f3/internal/client/gitlab.go#L500
        - |
          docker run --rm --privileged \
            -v $PWD:/tmp/release-job \
            -w /tmp/release-job \
            -v /var/run/docker.sock:/var/run/docker.sock \
            -e DOCKER_USERNAME -e DOCKER_PASSWORD -e DOCKER_REGISTRY \
            -e CI_JOB_TOKEN -e GITLAB_TOKEN \
            goreleaser/goreleaser release --rm-dist

        # Build sbom
        - syft packages registry.gitlab.com/chainloop-dev/integration-demo:$CI_COMMIT_REF_NAME -o cyclonedx-json --file sbom.cyclonedx.json

        # Add attestation
        - chainloop attestation add --name sbom --value sbom.cyclonedx.json
        - chainloop attestation add --name image --value registry.gitlab.com/chainloop-dev/integration-demo:$CI_COMMIT_REF_NAME

        # Finish attestation
        - chainloop attestation push

      after_script:
        - chainloop attestation reset || true
    ```
  </Tab>
</Tabs>

## Advanced features

The basics of the attestation process is described in our [getting started guide](/get-started/first-attestation). This guide, instead will focus in some advance features guide

### Contract-less, auto-discoverable materials

Security and Compliance teams can define requirements on what must be included in the attestation through [contracts](/concepts/contracts).

Alternatively, and in addition to the materials defined on the contract, you can add as many **contract-less** materials as you like by providing its value.

The CLI will automatically discover the kind of material you are adding, but you can also specify it by providing the `--kind` flag.

For example

```sh theme={"dark"}
# Add a contract-less material by providing a name, value and kind
chainloop att add --name not-in-contract --value ./cyberdyne.cyclonedx.sbom --kind SBOM_CYCLONEDX_JSON
# If you do not provide the --kind, the CLI will automatically discover it
chainloop att add --name not-in-contract --value  --value ./cyberdyne.cyclonedx.sbom
# and if you do not provide the name, the CLI will generate one for you
chainloop att add --value ./cyberdyne.cyclonedx.sbom
```

Contract-less and auto-discovery and two features that walk hand by hand. They compose a new way of adding pieces of evidences to an existing contract in a frictionless way. You can see it in action in our [quickstart](/quickstart) guide.

### Remote State

By default, the attestation process state is stored locally. But this setup is not suitable when running a multi-step attestation process in a stateless environment, like our Dagger module, or when you want to leverage CI multi-job parallelism or similar.

For that, we implemented attestation remote state. Simply put, instead of the attestation CLI being in charge of maintaining the state during the attestation, this can be delegated to the server and retrieved at any time by providing an “attestation-id.”

```sh theme={"dark"}
# You can enable the feature by providing the --remote-state flag
# and it will return an attestation-id
chainloop attestation init --name my-workflow --project my-project --remote-state

# Then you can add any piece of evidence by providing the attestation-id
chainloop attestation add --value cyberdyne.cyclonedx.sbom --attestation-id deadbeef

# And finally craft the attestation, sign-it and push it
chainloop attestation push --attestation-id deadbeef
```

With this optional feature, as long as you have the attestation-id, you can add any piece of evidence to the attestation from anywhere.

### Add Annotations

You can add arbitrary, or pre-defined (in the contract) annotations to both the final attestation or any material you add to the attestation.

Material annotations can be added during the \`att add command by providing --annotation foo=bar

```sh theme={"dark"}
# A single annotation
chainloop attestation add --name my-material --value ./cyberdyne.cyclonedx.sbom --annotation key=value
# Multiple annotations
chainloop attestation add --name my-material --value ./cyberdyne.cyclonedx.sbom --annotation key=value --annotation another=value
```

To add annotations to the final attestation, you can provide them during the `attestation push` command.

```sh theme={"dark"}
chainloop attestation push --annotation key=value
```

### Preventing Implicit Workflow Creation

By default, Chainloop automatically creates workflows during the attestation initialization process if they don't already exist. While this provides convenience during initial setup, it can lead to workflow proliferation in automated environments where typos or configuration errors might accidentally create unintended workflows.

**Organization administrators can disable implicit workflow creation** to enforce explicit workflow management. When this setting is enabled:

* Workflows must be created explicitly using `chainloop workflow create` command before attestation
* Attempting to initialize an attestation with a non-existent workflow will fail with an error
* This prevents accidental workflow creation from automated systems or CI/CD pipelines

#### Enabling the Setting

Organization owners can enable this setting using the CLI:

```bash theme={"dark"}
chainloop organization update --name my-organization --prevent-implicit-workflow-creation
```

#### Example Behavior

When the setting is enabled, attempting to initialize an attestation with a non-existent workflow will result in an error:

```bash theme={"dark"}
chainloop att init --workflow sast --project my-project-2222
ERR creating workflows during the attestation process is disabled for this organization. Please create them in advance or contact your administrator
```

To resolve this, explicitly create the workflow first:

```bash theme={"dark"}
# Create the workflow explicitly
chainloop workflow create --name sast --project my-project-2222

# Now attestation initialization will work
chainloop att init --workflow sast --project my-project-2222
```

#### Use Cases

This feature is particularly useful for:

* **Large organizations** with many teams and automated systems
* **Preventing workflow sprawl** from typos, misconfigurations, or experimental pipelines
* **Enforcing governance** by requiring approval for new workflows
* **Centralized management** where workflow creation should be controlled by platform administrators
