Source vs Build SBOMs
Elixir’s Mix build tool provides excellent dependency management through mix.lock. This makes source SBOM generation reliable.
- Source SBOMs are generated from
mix.lock - Build SBOMs can analyze compiled dependencies in
deps/and_build/
For most Elixir projects, generating from mix.lock is preferred because it contains exact versions and content hashes from Hex.pm.
Lockfile Deep Dive
mix.exs (Manifest)
The mix.exs file declares your dependencies:
defmodule MyApp.MixProject do
use Mix.Project
def project do
[
app: :my_app,
version: "1.0.0",
elixir: "~> 1.16",
deps: deps()
]
end
defp deps do
[
{:phoenix, "~> 1.7"},
{:phoenix_ecto, "~> 4.4"},
{:ecto_sql, "~> 3.10"},
{:postgrex, ">= 0.0.0"},
{:jason, "~> 1.2"},
{:plug_cowboy, "~> 2.5"},
# Dev/test dependencies
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}
]
end
end
mix.lock
The mix.lock file contains resolved dependencies:
%{
"castore": {:hex, :castore, "1.0.4", "sha256hash...", [:mix], [], "hexpm", "checksum..."},
"cowboy": {:hex, :cowboy, "2.10.0", "sha256hash...", [:make, :rebar3], [
{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]},
{:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}
], "hexpm", "checksum..."},
"ecto": {:hex, :ecto, "3.11.1", "sha256hash...", [:mix], [
{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]},
{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]},
{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}
], "hexpm", "checksum..."},
"phoenix": {:hex, :phoenix, "1.7.10", "sha256hash...", [:mix], [
{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]},
{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]},
{:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]},
{:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]},
{:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]},
{:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}
], "hexpm", "checksum..."},
}
Key information:
- Package format:
{:hex, :package_name, "version", "hash", build_tools, dependencies, "hexpm", "checksum"} - Checksum: SHA-256 hash for integrity verification
- Dependencies: Nested dependency specifications
Hex Package Manager
Hex.pm is Elixir’s package repository, similar to npm or RubyGems:
# Get dependencies
mix deps.get
# Update dependencies
mix deps.update --all
# Check for outdated packages
mix hex.outdated
Umbrella Applications
Elixir umbrella projects contain multiple applications:
my_umbrella/
├── mix.exs
├── mix.lock # Shared lockfile
├── apps/
│ ├── core/
│ │ ├── mix.exs
│ │ └── lib/
│ ├── web/
│ │ ├── mix.exs
│ │ └── lib/
│ └── worker/
│ ├── mix.exs
│ └── lib/
The root mix.lock contains dependencies for all applications.
Umbrella mix.exs
defmodule MyUmbrella.MixProject do
use Mix.Project
def project do
[
apps_path: "apps",
version: "1.0.0",
deps: deps()
]
end
defp deps do
[] # Shared deps can go here
end
end
Development Dependencies
Elixir clearly separates dev/test dependencies:
defp deps do
[
# Runtime dependencies
{:phoenix, "~> 1.7"},
# Dev only
{:phoenix_live_reload, "~> 1.2", only: :dev},
# Dev and test
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
# Test only
{:mock, "~> 0.3.0", only: :test}
]
end
For production SBOMs, filter out :dev and :test dependencies.
Generating an SBOM
SBOM generation is the first step in the SBOM lifecycle. After generation, you typically need to enrich your SBOM with package metadata and augment it with your organization’s details.
Using sbomify GitHub Action (Recommended)
The sbomify GitHub Action is a swiss army knife for SBOMs that automatically selects the best generation tool for your ecosystem, enriches the output with package metadata, and optionally augments it with your business information—all in one step.
For Elixir, sbomify uses cdxgen or Syft under the hood.
Standalone (no account needed):
- uses: sbomify/github-action@master
env:
LOCK_FILE: mix.lock
OUTPUT_FILE: sbom.cdx.json
COMPONENT_NAME: my-elixir-app
COMPONENT_VERSION: ${{ github.ref_name }}
ENRICH: true
UPLOAD: false
Using github.ref_name automatically captures your git tag (e.g., v1.2.3) as the SBOM version. For rolling releases without tags, use github.sha instead. See our SBOM versioning guide for best practices.
With sbomify platform (adds augmentation and upload):
- uses: sbomify/github-action@master
env:
TOKEN: ${{ secrets.SBOMIFY_TOKEN }}
COMPONENT_ID: my-component-id
LOCK_FILE: mix.lock
OUTPUT_FILE: sbom.cdx.json
AUGMENT: true
ENRICH: true
Alternative Tools
If you prefer to run SBOM generation tools manually:
cdxgen:
npm install -g @cyclonedx/cdxgen
cdxgen -t elixir -o sbom.cdx.json
Syft:
syft . -o cyclonedx-json=sbom.cdx.json
sbom (Elixir-native):
# Add to mix.exs
{:sbom, "~> 0.8", only: :dev, runtime: false}
mix sbom.cyclonedx
When using these tools directly, you’ll need to handle enrichment and augmentation separately.
GitLab CI
generate-sbom:
image: sbomifyhub/sbomify-action
variables:
LOCK_FILE: mix.lock
OUTPUT_FILE: sbom.cdx.json
UPLOAD: "false"
ENRICH: "true"
script:
- /sbomify.sh
artifacts:
paths:
- sbom.cdx.json
Handling Special Cases
Git Dependencies
Dependencies from Git:
{:my_dep, git: "https://github.com/example/my_dep.git", tag: "v1.0.0"}
In mix.lock:
"my_dep": {:git, "https://github.com/example/my_dep.git", "abc123...", [tag: "v1.0.0"]}
Path Dependencies
Local dependencies:
{:shared, path: "../shared"}
These won’t have Hex metadata and need manual documentation.
Private Hex Repositories
For private Hex repos:
{:private_dep, "~> 1.0", organization: "myorg"}
Configure authentication:
mix hex.organization auth myorg
Override Dependencies
Forcing specific versions:
defp deps do
[
{:phoenix, "~> 1.7"},
{:plug, "~> 1.14", override: true} # Force version
]
end
Overrides affect the final dependency tree captured in mix.lock.
Native Dependencies (NIFs)
Elixir packages may include Native Implemented Functions (NIFs):
{:rustler, "~> 0.30"}, # Rust NIFs
{:bcrypt_elixir, "~> 3.0"} # C NIFs
These have native code dependencies not captured in mix.lock. Document them:
env:
ADDITIONAL_PACKAGES: "openssl:3.0,rust:1.75"
Best Practices
- Always commit mix.lock - Essential for reproducible builds
- Use exact versions for releases - Pin versions in production
- Separate dev dependencies - Use
only: :devandonly: :test - Check for vulnerabilities - Use
mix hex.audit - Document NIFs - Track native dependencies separately
- Update regularly - Use
mix hex.outdatedto track updates
Security Integration
# Audit dependencies for vulnerabilities
mix hex.audit
# Generate SBOM and audit in CI
mix deps.get
mix hex.audit --format json > audit.json
- name: Security audit
run: mix hex.audit
- name: Generate SBOM
uses: sbomify/github-action@master
env:
LOCK_FILE: 'mix.lock'
OUTPUT_FILE: 'sbom.cdx.json'
Further Resources
For more SBOM tools and resources, see our SBOM Resources page, which includes general SBOM utilities for generation, distribution, and analysis.