# VEX {#vex}

Takumi のイメージに対する脆弱性スキャンは、検出感度を高めるために疑わしい検出結果も残します。
そのため、報告のなかには実際には該当しないもの、すでに修正済みのもの、または脆弱性としての扱いに争いがあるものも含まれます。
**VEX**（Vulnerability Exploitability eXchange）は、検出結果ごとの判断と根拠を、機械が読める形で公開する仕組みです。
Takumi Images では [OpenVEX](https://openvex.dev/) 形式で、`not_affected`、`fixed`、`affected` の判断を公開しています。

公開している VEX は、単なる抑制リストではありません。
`affected` も含めて公開し、利用者が「どの検出結果が、どの理由で、どのイメージに対して判断済みなのか」を確認できるようにしています。

:::info
Takumi Images は現在ベータ版です。
仕様や挙動が予告なく変更される可能性があります。
:::

## 公開している経路 {#distribution}

Takumi Images は、同じ VEX の判断を2つの経路で公開しています。

**署名付き attestation**

イメージの digest に紐づく OpenVEX attestation です。
SBOM や SLSA Provenance と同じ keyless 署名の identity で検証でき、pull した成果物に対する証跡として使います。
VEX はアーキテクチャごとの子 manifest ではなく、原則としてマルチアーキテクチャの index に添付します。

**公開 VEX repository**

`https://vex.images.flatt.tech` で公開している VEX repository です。
[VEX Repository Specification](https://github.com/aquasecurity/vex-repo-spec) に沿った layout で、対応するスキャナや監査用のツールが参照できます。
イメージを pull する前に VEX を取得したい場合や、grype にまとめて OpenVEX ファイルを渡したい場合はこちらを使います。

どちらも、Takumi Images の `vex/takumi.openvex.json` を元に生成します。
元の文書は Nix のコンポーネント単位の `pkg:nix/...` を product として持ちますが、公開時にはイメージ単位の `pkg:oci/...` に変換します。

## 公開 VEX repository の構造 {#repository}

本番の discovery manifest は次の URL で公開しています。

```text
https://vex.images.flatt.tech/.well-known/vex-repository.json
```

manifest は、VEX repository のデータ本体として `v1/vex-data.tar.gz` を指します。
これは仕様上、repository client が archive を取得して展開するためです。
HTTP 上のディレクトリをたどって各ファイルを探すのではなく、archive の root に `index.json` と `pkg/...` を置きます。

```text
https://vex.images.flatt.tech/.well-known/vex-repository.json
https://vex.images.flatt.tech/v1/vex-data.tar.gz
https://vex.images.flatt.tech/v1/index.json
https://vex.images.flatt.tech/v1/pkg/oci/images.flatt.tech/takumi/<image>/vex.json
https://vex.images.flatt.tech/v1/all.openvex.json
```

それぞれの役割は次のとおりです。

**`.well-known/vex-repository.json`**

VEX repository の discovery manifest です。
`spec_version: "0.1"`、データ archive の URL、`update_interval: "24h"` を広告します。

**`v1/vex-data.tar.gz`**

スキャナ向けの配布 archive です。
archive の中には `index.json` と `pkg/oci/.../vex.json` が入ります。

**`v1/index.json`**

PURL から VEX 文書の location への対応表です。
PURL 文字列を key として引くため、`repository_url` の `/` は `%2F` に percent-encode された形で入ります。

**`v1/pkg/oci/images.flatt.tech/takumi/<image>/vex.json`**

イメージごとの OpenVEX 文書です。
たとえば `busybox` の VEX は次の URL から取得できます。

```text
https://vex.images.flatt.tech/v1/pkg/oci/images.flatt.tech/takumi/busybox/vex.json
```

**`v1/all.openvex.json`**

全イメージ分をまとめた OpenVEX 文書です。
grype にローカル VEX ファイルとして渡す場合や、人間が全体を監査する場合に使います。

データは Cloudflare R2 の public custom domain から直接配信しています。
VEX の更新は日次の publish workflow が R2 objects を書き換えるだけで完了し、worker の deploy は必要ありません。
書き込み順序は、per-image の leaf data、aggregate、archive、index、`.well-known` manifest の順です。
最後に pointer を更新することで、途中の publish によって「index はあるが参照先がない」という状態を見せないようにしています。

## ステートメントの内容 {#records}

OpenVEX の statement には、脆弱性 ID、対象 product、status、根拠を記録します。
脆弱性 ID は CVE に限られず、GHSA や OSV ID、ベンダー advisory ID なども同じ `vulnerability.name` に入ります。
`justification` は OpenVEX が定める機械可読な区分で、`impact_statement` や `action_statement` は読者が判断を検証するための説明です。

次は、公開中の `busybox` VEX から短縮した例です。

```json
{
  "vulnerability": {
    "name": "CVE-2019-1010022",
    "description": "glibc stack guard protection bypass -- DISPUTED by upstream."
  },
  "products": [
    {
      "@id": "pkg:oci/busybox@sha256:<index-digest>?repository_url=images.flatt.tech/takumi&tag=latest"
    },
    {
      "@id": "pkg:oci/busybox?repository_url=images.flatt.tech/takumi"
    }
  ],
  "status": "not_affected",
  "justification": "vulnerable_code_cannot_be_controlled_by_adversary",
  "impact_statement": "Disputed by the glibc maintainers ..."
}
```

この例では、1つの statement に2つの product ID が入っています。
これは互換性のためです。

**1つ目: digest に固定した canonical ID**

`pkg:oci/<image>@sha256:<index-digest>?repository_url=images.flatt.tech/takumi&tag=latest`

これは署名付き attestation と同じ、監査用の基準 ID です。
どの image index に対する判断なのかを digest で固定します。
タグだけに依存しないため、後から `latest` が別の digest を指しても判断の対象は変わりません。

**2つ目: grype 向けの ID**

`pkg:oci/<image>?repository_url=images.flatt.tech/takumi`

grype が作る OCI PURL は、image 名を PURL の name とし、`repository_url` には registry と namespace だけを入れます。
さらに grype は per-arch manifest digest を version として扱うため、こちらも version を省略しています。

`tag=latest` は canonical ID にだけ残します。
grype が生成する PURL には `tag` qualifier が入らないため、grype との照合には2つ目の versionless ID を使います。

## attestation として検証する {#attestation}

pull したイメージに紐づく VEX を検証する場合は、`openvex` attestation を取得します。

```bash
cosign verify-attestation \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  --certificate-identity-regexp 'https://github.com/flatt-security/takumi-images/.github/workflows/release.yml@.*' \
  --type openvex \
  images.flatt.tech/takumi/busybox:latest \
  | jq -r '.payload' | base64 -d | jq '.predicate'
```

この検証で確認できるのは、取得した OpenVEX 文書が対象 digest に対して署名され、署名時点から改ざんされていないことです。
署名の identity や attestation 全般の検証方法は、[Attestation](/docs/ja/t/images/features/attestations/#verify-attestation) を参照してください。

## grype で使う {#grype}

grype では aggregate 文書を取得し、`--vex` で渡します。
grype は VEX 文書を読むだけでは finding を ignore しないため、`not_affected` と `fixed` を抑制対象にする ignore rule も設定します。
`affected` は抑制してはいけません。

```yaml
# grype-vex.yaml
ignore:
  - vex-status: not_affected
  - vex-status: fixed
```

```bash
curl -fsSLo takumi.openvex.json \
  https://vex.images.flatt.tech/v1/all.openvex.json

grype registry:images.flatt.tech/takumi/busybox:latest-amd64 \
  --vex takumi.openvex.json \
  --config grype-vex.yaml \
  --show-suppressed
```

この設定では、VEX が `not_affected` または `fixed` と判断した finding は suppressed として扱われます。
未修正として公開している `affected` は残るため、レポートから除外せずに対応判断の対象にしてください。

## トリアージとの関係 {#triage}

VEX は、スキャン結果を見かけ上少なくするための後処理ではありません。
上流リリースへの追従、パッケージ単位のパッチ、実際には該当しない検出結果の判断を区別し、それぞれを検証可能な形で残すための記録です。
このトリアージの考え方は、[既知脆弱性への対応](/docs/ja/t/images/architecture/known-vulnerability-patching) で説明しています。
