# Generación de SBOMs


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## Objetivo

Este notebook explica el proceso para generar **Software Bill of
Materials (SBOMs)** para un conjunto de repositorios de código y cómo
analizar los resultados de manera centralizada. Utilizaremos la
herramienta **Syft** para la generación y la librería **Pandas** en
Python para el análisis.

El flujo de trabajo completo está diseñado para ser reproducible y se
basa en las siguientes etapas:

1.  **Descubrimiento de Repositorios**: Identificar los proyectos a los
    que se les generará un SBOM.
2.  **Generación de SBOMs**: Ejecutar Syft para analizar cada
    repositorio y crear un SBOM en formato JSON.
3.  **Análisis de Resultados**: Cargar todos los SBOMs generados en un
    DataFrame de Pandas para su inspección y análisis.

## Estructura de Directorios

El proyecto sigue una estructura organizada para separar los datos de
entrada, los scripts y los resultados:

    ciberseguridad_2026/
    ├── data/
    │   ├── repos/      # Directorio para los repositorios a analizar
    │   └── results/    # Directorio para los SBOMs generados en JSON
    ├── nbs/            # Notebooks de Jupyter
    └── scripts/        # Scripts de automatización
        └── generate_sboms.py

## Paso 1: Generación de SBOMs

El primer paso es ejecutar el script `generate_sboms.py`, que se encarga
de orquestar la generación de los SBOMs.

Este script realiza las siguientes acciones:

1.  **Busca repositorios**: Escanea el directorio `data/repos` en busca
    de subdirectorios que contengan código fuente.
2.  **Ejecuta Syft**: Para cada repositorio encontrado, invoca a `syft`
    para analizar su contenido.
3.  **Guarda los resultados**: El SBOM generado para cada repositorio se
    guarda como un archivo JSON en el directorio `data/results`.

``` python
!python ../../scripts/generate_sboms.py
```

    INFO | Usando Syft CLI: /usr/local/bin/syft
    INFO | [1/3] Procesando repositorio data/repos/claude-code-action
    INFO | SBOM guardado en data/results/claude-code-action-sbom.json
    INFO | [2/3] Procesando repositorio data/repos/genai-code-review
    INFO | SBOM guardado en data/results/genai-code-review-sbom.json
    INFO | [3/3] Procesando repositorio data/repos/opencode
    INFO | SBOM guardado en data/results/opencode-sbom.json
    INFO | Resumen final | total_repos=3 | repos_generados=3 | archivos_generados=3 | omitidos=0 | errores=0

## Paso 2: Análisis de los SBOMs con Pandas

Una vez que los SBOMs han sido generados, podemos cargarlos en un
DataFrame de Pandas para analizarlos. Esto nos permite tener una visión
consolidada de todas las dependencias y artefactos encontrados en los
diferentes repositorios.

El siguiente código se encarga de:

1.  **Localizar los archivos JSON**: Busca todos los archivos `.json` en
    el directorio `data/results`.
2.  **Cargar y normalizar los datos**: Para cada archivo JSON, lo carga
    y utiliza `pd.json_normalize` para aplanar la estructura anidada de
    los artefactos.
3.  **Añadir información del repositorio**: Agrega una columna `repo`
    para identificar a qué repositorio pertenece cada artefacto.
4.  **Concatenar los resultados**: Une todos los DataFrames individuales
    en uno solo.

``` python
import pandas as pd
from pathlib import Path
import json

path = Path("../../data/results")

df_all = pd.concat(
    [
        pd.json_normalize(json.load(open(file))["artifacts"]).assign(repo=file.stem)
        for file in path.glob("*-sbom.json")
    ],
    ignore_index=True
)

df_all.head()
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }
&#10;    .dataframe tbody tr th {
        vertical-align: top;
    }
&#10;    .dataframe thead th {
        text-align: right;
    }
</style>

<table class="dataframe" data-quarto-postprocess="true" data-border="1">
<thead>
<tr style="text-align: right;">
<th data-quarto-table-cell-role="th"></th>
<th data-quarto-table-cell-role="th">id</th>
<th data-quarto-table-cell-role="th">name</th>
<th data-quarto-table-cell-role="th">version</th>
<th data-quarto-table-cell-role="th">type</th>
<th data-quarto-table-cell-role="th">foundBy</th>
<th data-quarto-table-cell-role="th">locations</th>
<th data-quarto-table-cell-role="th">licenses</th>
<th data-quarto-table-cell-role="th">language</th>
<th data-quarto-table-cell-role="th">cpes</th>
<th data-quarto-table-cell-role="th">purl</th>
<th data-quarto-table-cell-role="th">...</th>
<th
data-quarto-table-cell-role="th">metadata.dependencies.@actions/core</th>
<th
data-quarto-table-cell-role="th">metadata.dependencies.shell-quote</th>
<th data-quarto-table-cell-role="th">metadata.comment</th>
<th
data-quarto-table-cell-role="th">metadata.dependencies.@fastify/busboy</th>
<th data-quarto-table-cell-role="th">repo</th>
<th data-quarto-table-cell-role="th">metadata.name</th>
<th data-quarto-table-cell-role="th">metadata.versionConstraint</th>
<th data-quarto-table-cell-role="th">metadata.version</th>
<th data-quarto-table-cell-role="th">metadata.source</th>
<th data-quarto-table-cell-role="th">metadata.checksum</th>
</tr>
</thead>
<tbody>
<tr>
<td data-quarto-table-cell-role="th">0</td>
<td>3dde6d26a8867975</td>
<td>./.github/workflows/ci.yml</td>
<td>UNKNOWN</td>
<td>github-action-workflow</td>
<td>github-action-workflow-usage-cataloger</td>
<td>[{'path': '/.github/workflows/ci-all.yml', 'ac...</td>
<td>[]</td>
<td></td>
<td>[{'cpe': 'cpe:2.3:a:.\/.github\/workflows\/ci....</td>
<td></td>
<td>...</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>claude-code-action-sbom</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">1</td>
<td>9166fd4b121e55f3</td>
<td>./.github/workflows/test-base-action.yml</td>
<td>UNKNOWN</td>
<td>github-action-workflow</td>
<td>github-action-workflow-usage-cataloger</td>
<td>[{'path': '/.github/workflows/ci-all.yml', 'ac...</td>
<td>[]</td>
<td></td>
<td>[{'cpe': 'cpe:2.3:a:.\/.github\/workflows\/tes...</td>
<td></td>
<td>...</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>claude-code-action-sbom</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">2</td>
<td>95afc7fec8addb9b</td>
<td>./.github/workflows/test-custom-executables.yml</td>
<td>UNKNOWN</td>
<td>github-action-workflow</td>
<td>github-action-workflow-usage-cataloger</td>
<td>[{'path': '/.github/workflows/ci-all.yml', 'ac...</td>
<td>[]</td>
<td></td>
<td>[{'cpe': 'cpe:2.3:a:.\/.github\/workflows\/tes...</td>
<td></td>
<td>...</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>claude-code-action-sbom</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">3</td>
<td>f535590207532289</td>
<td>./.github/workflows/test-mcp-servers.yml</td>
<td>UNKNOWN</td>
<td>github-action-workflow</td>
<td>github-action-workflow-usage-cataloger</td>
<td>[{'path': '/.github/workflows/ci-all.yml', 'ac...</td>
<td>[]</td>
<td></td>
<td>[{'cpe': 'cpe:2.3:a:.\/.github\/workflows\/tes...</td>
<td></td>
<td>...</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>claude-code-action-sbom</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
</tr>
<tr>
<td data-quarto-table-cell-role="th">4</td>
<td>bdedac1ea6288e33</td>
<td>./.github/workflows/test-settings.yml</td>
<td>UNKNOWN</td>
<td>github-action-workflow</td>
<td>github-action-workflow-usage-cataloger</td>
<td>[{'path': '/.github/workflows/ci-all.yml', 'ac...</td>
<td>[]</td>
<td></td>
<td>[{'cpe': 'cpe:2.3:a:.\/.github\/workflows\/tes...</td>
<td></td>
<td>...</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>claude-code-action-sbom</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
<td>NaN</td>
</tr>
</tbody>
</table>

<p>5 rows × 30 columns</p>
</div>

## Exploración de los Resultados

Con los datos en un DataFrame, podemos realizar diversas consultas y
análisis. Por ejemplo, podemos ver cuántos artefactos se encontraron por
cada repositorio.

``` python
df_all['repo'].value_counts()
```

    repo
    opencode-sbom              775
    claude-code-action-sbom     37
    genai-code-review-sbom       5
    Name: count, dtype: int64

También podemos ver las licencias más comunes encontradas en todas las
dependencias.

``` python
df_all['licenses'].explode().value_counts().head(10)
```

    licenses
    {'value': 'MIT', 'spdxExpression': 'MIT', 'type': 'declared', 'urls': [], 'locations': [{'path': '/base-action/package-lock.json', 'accessPath': '/base-action/package-lock.json', 'annotations': {'evidence': 'primary'}}]}    8
    Name: count, dtype: int64

## Cómo Añadir Nuevos Repositorios al Análisis

El proyecto está diseñado para que sea sencillo añadir nuevos
repositorios al proceso de generación de SBOMs. El sistema utiliza **Git
submodules** para gestionar los repositorios externos.

#### 1. Edita el archivo `data/repos.json`

Abre `data/repos.json` y agrega nuevos repositorios con esta estructura:

``` json
{
  "url": "URL_DEL_REPOSITORIO_EN_GITHUB.git",
  "path": "data/repos/NOMBRE_DEL_REPOSITORIO"
}
```

- `url`: La URL `.git` del repositorio
- `path`: Ruta local donde se clonará (usa `data/repos/{nombre}`)

#### 2. Ejecuta en la terminal (esto clona los repositorios y los agrega como submódulos):

``` bash
uv run scripts/add_submodules.py && git submodule update --init --recursive
```

#### 3. Ejecuta la generación de SBOMs nuevamente:

``` bash
uv run scripts/generate_sboms.py
```

O desde el notebook: - Reinicia el kernel - Ejecuta la celda con
`!python ../../scripts/generate_sboms.py`
