{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "version": 1,
  "metadata": {
    "component": {
      "type": "application",
      "bom-ref": "zerologs",
      "name": "Zero Logs Reader",
      "version": "3.35.0",
      "description": "Reading, analysis and visualisation of Zero Motorcycles diagnostic logs."
    },
    "properties": [
      {
        "name": "zerologs:generator",
        "value": "backend/scripts/gen_sbom.py"
      }
    ]
  },
  "components": [
    {
      "type": "library",
      "bom-ref": "pkg:pypi/fastapi",
      "name": "fastapi",
      "purl": "pkg:pypi/fastapi"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/uvicorn",
      "name": "uvicorn",
      "purl": "pkg:pypi/uvicorn",
      "properties": [
        {
          "name": "zerologs:extras",
          "value": "standard"
        }
      ]
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/python-multipart",
      "name": "python-multipart",
      "purl": "pkg:pypi/python-multipart"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/sqlalchemy",
      "name": "sqlalchemy",
      "purl": "pkg:pypi/sqlalchemy",
      "properties": [
        {
          "name": "zerologs:extras",
          "value": "asyncio"
        }
      ]
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/asyncpg",
      "name": "asyncpg",
      "purl": "pkg:pypi/asyncpg"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/psycopg2-binary",
      "name": "psycopg2-binary",
      "purl": "pkg:pypi/psycopg2-binary"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/alembic",
      "name": "alembic",
      "purl": "pkg:pypi/alembic"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/greenlet",
      "name": "greenlet",
      "purl": "pkg:pypi/greenlet"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/redis",
      "name": "redis",
      "purl": "pkg:pypi/redis"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/celery",
      "name": "celery",
      "purl": "pkg:pypi/celery"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/minio",
      "name": "minio",
      "purl": "pkg:pypi/minio"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/PyJWT",
      "name": "PyJWT",
      "purl": "pkg:pypi/PyJWT"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/passlib",
      "name": "passlib",
      "purl": "pkg:pypi/passlib",
      "properties": [
        {
          "name": "zerologs:extras",
          "value": "bcrypt"
        }
      ]
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/bcrypt",
      "name": "bcrypt",
      "purl": "pkg:pypi/bcrypt"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/pydantic",
      "name": "pydantic",
      "purl": "pkg:pypi/pydantic"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/pydantic-settings",
      "name": "pydantic-settings",
      "purl": "pkg:pypi/pydantic-settings"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/email-validator",
      "name": "email-validator",
      "purl": "pkg:pypi/email-validator"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/langfuse",
      "name": "langfuse",
      "purl": "pkg:pypi/langfuse"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/wrapt",
      "name": "wrapt",
      "purl": "pkg:pypi/wrapt"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/openai",
      "name": "openai",
      "purl": "pkg:pypi/openai"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/python-dateutil",
      "name": "python-dateutil",
      "purl": "pkg:pypi/python-dateutil"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/aiofiles",
      "name": "aiofiles",
      "purl": "pkg:pypi/aiofiles"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/slowapi",
      "name": "slowapi",
      "purl": "pkg:pypi/slowapi"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/python-json-logger",
      "name": "python-json-logger",
      "purl": "pkg:pypi/python-json-logger"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/reportlab",
      "name": "reportlab",
      "purl": "pkg:pypi/reportlab"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/matplotlib",
      "name": "matplotlib",
      "purl": "pkg:pypi/matplotlib"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/Pillow",
      "name": "Pillow",
      "purl": "pkg:pypi/Pillow"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/pytest",
      "name": "pytest",
      "purl": "pkg:pypi/pytest"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/pytest-asyncio",
      "name": "pytest-asyncio",
      "purl": "pkg:pypi/pytest-asyncio"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/pytest-cov",
      "name": "pytest-cov",
      "purl": "pkg:pypi/pytest-cov"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/fitparse",
      "name": "fitparse",
      "purl": "pkg:pypi/fitparse"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/pgvector",
      "name": "pgvector",
      "purl": "pkg:pypi/pgvector"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/numpy",
      "name": "numpy",
      "purl": "pkg:pypi/numpy"
    },
    {
      "type": "library",
      "bom-ref": "pkg:pypi/prometheus-client",
      "name": "prometheus-client",
      "purl": "pkg:pypi/prometheus-client"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@maplibre/maplibre-gl-leaflet",
      "name": "@maplibre/maplibre-gl-leaflet",
      "purl": "pkg:npm/@maplibre/maplibre-gl-leaflet"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-accordion",
      "name": "@radix-ui/react-accordion",
      "purl": "pkg:npm/@radix-ui/react-accordion"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-alert-dialog",
      "name": "@radix-ui/react-alert-dialog",
      "purl": "pkg:npm/@radix-ui/react-alert-dialog"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-avatar",
      "name": "@radix-ui/react-avatar",
      "purl": "pkg:npm/@radix-ui/react-avatar"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-checkbox",
      "name": "@radix-ui/react-checkbox",
      "purl": "pkg:npm/@radix-ui/react-checkbox"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-collapsible",
      "name": "@radix-ui/react-collapsible",
      "purl": "pkg:npm/@radix-ui/react-collapsible"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-dialog",
      "name": "@radix-ui/react-dialog",
      "purl": "pkg:npm/@radix-ui/react-dialog"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-dropdown-menu",
      "name": "@radix-ui/react-dropdown-menu",
      "purl": "pkg:npm/@radix-ui/react-dropdown-menu"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-icons",
      "name": "@radix-ui/react-icons",
      "purl": "pkg:npm/@radix-ui/react-icons"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-label",
      "name": "@radix-ui/react-label",
      "purl": "pkg:npm/@radix-ui/react-label"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-popover",
      "name": "@radix-ui/react-popover",
      "purl": "pkg:npm/@radix-ui/react-popover"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-progress",
      "name": "@radix-ui/react-progress",
      "purl": "pkg:npm/@radix-ui/react-progress"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-scroll-area",
      "name": "@radix-ui/react-scroll-area",
      "purl": "pkg:npm/@radix-ui/react-scroll-area"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-select",
      "name": "@radix-ui/react-select",
      "purl": "pkg:npm/@radix-ui/react-select"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-separator",
      "name": "@radix-ui/react-separator",
      "purl": "pkg:npm/@radix-ui/react-separator"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-slot",
      "name": "@radix-ui/react-slot",
      "purl": "pkg:npm/@radix-ui/react-slot"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-tabs",
      "name": "@radix-ui/react-tabs",
      "purl": "pkg:npm/@radix-ui/react-tabs"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-toast",
      "name": "@radix-ui/react-toast",
      "purl": "pkg:npm/@radix-ui/react-toast"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@radix-ui/react-tooltip",
      "name": "@radix-ui/react-tooltip",
      "purl": "pkg:npm/@radix-ui/react-tooltip"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@tanstack/react-query",
      "name": "@tanstack/react-query",
      "purl": "pkg:npm/@tanstack/react-query"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@tanstack/react-virtual",
      "name": "@tanstack/react-virtual",
      "purl": "pkg:npm/@tanstack/react-virtual"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/@types/leaflet",
      "name": "@types/leaflet",
      "purl": "pkg:npm/@types/leaflet"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/class-variance-authority",
      "name": "class-variance-authority",
      "purl": "pkg:npm/class-variance-authority"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/clsx",
      "name": "clsx",
      "purl": "pkg:npm/clsx"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/date-fns",
      "name": "date-fns",
      "purl": "pkg:npm/date-fns"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/framer-motion",
      "name": "framer-motion",
      "purl": "pkg:npm/framer-motion"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/leaflet",
      "name": "leaflet",
      "purl": "pkg:npm/leaflet"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/lucide-react",
      "name": "lucide-react",
      "purl": "pkg:npm/lucide-react"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/maplibre-gl",
      "name": "maplibre-gl",
      "purl": "pkg:npm/maplibre-gl"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/next",
      "name": "next",
      "purl": "pkg:npm/next"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/next-themes",
      "name": "next-themes",
      "purl": "pkg:npm/next-themes"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/react",
      "name": "react",
      "purl": "pkg:npm/react"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/react-dom",
      "name": "react-dom",
      "purl": "pkg:npm/react-dom"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/react-dropzone",
      "name": "react-dropzone",
      "purl": "pkg:npm/react-dropzone"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/react-google-recaptcha-v3",
      "name": "react-google-recaptcha-v3",
      "purl": "pkg:npm/react-google-recaptcha-v3"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/react-markdown",
      "name": "react-markdown",
      "purl": "pkg:npm/react-markdown"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/recharts",
      "name": "recharts",
      "purl": "pkg:npm/recharts"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/remark-breaks",
      "name": "remark-breaks",
      "purl": "pkg:npm/remark-breaks"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/remark-gfm",
      "name": "remark-gfm",
      "purl": "pkg:npm/remark-gfm"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/tailwind-merge",
      "name": "tailwind-merge",
      "purl": "pkg:npm/tailwind-merge"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/tailwindcss-animate",
      "name": "tailwindcss-animate",
      "purl": "pkg:npm/tailwindcss-animate"
    },
    {
      "type": "library",
      "bom-ref": "pkg:npm/zustand",
      "name": "zustand",
      "purl": "pkg:npm/zustand"
    },
    {
      "type": "container",
      "bom-ref": "container:backend",
      "name": "backend",
      "purl": "pkg:oci/backend",
      "properties": [
        {
          "name": "zerologs:layer",
          "value": "app"
        },
        {
          "name": "zerologs:repository",
          "value": "registry.gitlab.com/raffaele.vitiello/zero-motorcycle-logs-dashboard/backend"
        }
      ]
    },
    {
      "type": "container",
      "bom-ref": "container:embed",
      "name": "embed",
      "purl": "pkg:oci/embed",
      "properties": [
        {
          "name": "zerologs:layer",
          "value": "data"
        },
        {
          "name": "zerologs:repository",
          "value": "registry.gitlab.com/raffaele.vitiello/zero-motorcycle-logs-dashboard/embed"
        }
      ]
    },
    {
      "type": "container",
      "bom-ref": "container:frontend",
      "name": "frontend",
      "purl": "pkg:oci/frontend",
      "properties": [
        {
          "name": "zerologs:layer",
          "value": "app"
        },
        {
          "name": "zerologs:repository",
          "value": "registry.gitlab.com/raffaele.vitiello/zero-motorcycle-logs-dashboard/frontend"
        }
      ]
    }
  ],
  "services": [
    {
      "bom-ref": "service:cloudflare",
      "name": "Cloudflare",
      "provider": {
        "name": "Cloudflare, Inc."
      },
      "description": "Public edge: TLS towards the visitor, WAF, rate limiting, and the tunnel through which traffic reaches the origin.",
      "authenticated": false,
      "x-trust-boundary": true,
      "data": [
        {
          "classification": "PII",
          "flow": "inbound"
        },
        {
          "classification": "technical",
          "flow": "bi-directional"
        }
      ],
      "properties": [
        {
          "name": "zerologs:layer",
          "value": "access"
        },
        {
          "name": "zerologs:note",
          "value": "Sees every HTTP request, so IP address and user agent. It terminates the visitor's TLS: request contents pass through it in the clear."
        }
      ]
    },
    {
      "bom-ref": "service:ovhcloud-object-storage",
      "name": "OVHcloud Object Storage",
      "provider": {
        "name": "OVH SAS"
      },
      "description": "S3-compatible storage for uploaded log files, photographs and exports.",
      "authenticated": true,
      "x-trust-boundary": true,
      "data": [
        {
          "classification": "PII",
          "flow": "bi-directional"
        }
      ],
      "properties": [
        {
          "name": "zerologs:layer",
          "value": "data"
        },
        {
          "name": "zerologs:note",
          "value": "Holds the binary files exactly as the rider uploads them, VIN included. Region eu-west-par."
        }
      ]
    },
    {
      "bom-ref": "service:deepseek-api",
      "name": "DeepSeek API",
      "provider": {
        "name": "DeepSeek"
      },
      "description": "Language model behind log analysis and the assistant.",
      "authenticated": true,
      "x-trust-boundary": true,
      "data": [
        {
          "classification": "PII",
          "flow": "outbound"
        }
      ],
      "properties": [
        {
          "name": "zerologs:layer",
          "value": "app"
        },
        {
          "name": "zerologs:note",
          "value": "Receives log excerpts and the rider's own notes. It never receives the `provider` field of a maintenance record, which is where a workshop address gets pasted."
        }
      ]
    },
    {
      "bom-ref": "service:resend",
      "name": "Resend",
      "provider": {
        "name": "Resend, Inc."
      },
      "description": "Transactional and newsletter email delivery.",
      "authenticated": true,
      "x-trust-boundary": true,
      "data": [
        {
          "classification": "PII",
          "flow": "outbound"
        }
      ],
      "properties": [
        {
          "name": "zerologs:layer",
          "value": "app"
        },
        {
          "name": "zerologs:note",
          "value": "Receives the email address and the message body."
        }
      ]
    },
    {
      "bom-ref": "service:scaleway-object-storage",
      "name": "Scaleway Object Storage",
      "provider": {
        "name": "Scaleway SAS"
      },
      "description": "Off-site backup: a restic repository in it-mil, plus a monthly encrypted archive in nl-ams under Object Lock.",
      "authenticated": true,
      "x-trust-boundary": true,
      "data": [
        {
          "classification": "PII",
          "flow": "outbound"
        }
      ],
      "properties": [
        {
          "name": "zerologs:layer",
          "value": "platform"
        },
        {
          "name": "zerologs:note",
          "value": "Receives the whole database and the stored objects, encrypted. The monthly archive is encrypted with age: without that private key it cannot be read even by someone holding the bucket credentials."
        }
      ]
    },
    {
      "bom-ref": "service:grafana-cloud",
      "name": "Grafana Cloud",
      "provider": {
        "name": "Grafana Labs"
      },
      "description": "Destination for operational logs and metrics.",
      "authenticated": true,
      "x-trust-boundary": true,
      "data": [
        {
          "classification": "technical",
          "flow": "outbound"
        }
      ],
      "properties": [
        {
          "name": "zerologs:layer",
          "value": "platform"
        },
        {
          "name": "zerologs:note",
          "value": "Receives only what the collector lets through — 500s, warnings, slow requests, task lifecycle — after a redaction pass that masks IP addresses and tokens in URLs."
        }
      ]
    },
    {
      "bom-ref": "service:gitlab-container-registry",
      "name": "GitLab Container Registry",
      "provider": {
        "name": "GitLab Inc."
      },
      "description": "Distribution of the container images.",
      "authenticated": true,
      "x-trust-boundary": true,
      "data": [
        {
          "classification": "technical",
          "flow": "inbound"
        }
      ],
      "properties": [
        {
          "name": "zerologs:layer",
          "value": "platform"
        },
        {
          "name": "zerologs:note",
          "value": "Sees no user data. It is listed because it is the supply chain the system takes its running code from."
        }
      ]
    }
  ]
}
