Skip to content

Installation

pixi-sbom is a pixi extension: a standalone executable named pixi-sbom. Pixi finds any pixi-<name> binary on PATH (or in its own global bin directory) and runs it when you type pixi <name>. There is no plugin registration; installing the binary is the whole setup.

Methods

Method Command
pixi global (recommended) pixi global install pixi-sbom
Prebuilt binary Download pixi-sbom-<version>-<platform>.tar.gz (or .zip on Windows) from the releases page, which has every version, check its provenance (below), and put pixi-sbom on your PATH
PyPI uvx pixi-sbom runs it without installing; uv tool install pixi-sbom or pip install pixi-sbom installs it. The wheels hold the same binary as the release archives (no Python code), for Linux (glibc and musl) x86_64/aarch64, macOS x86_64/arm64 and Windows x86_64, from 1.7.0 on
From source pixi run build in a clone, then copy target/release/pixi-sbom to ~/.pixi/bin/

Check it is picked up:

pixi --list          # ...  sbom  (via pixi-sbom)
pixi sbom --version

pixi-sbom --help and pixi sbom --help are equivalent; the binary can be run directly without pixi.

Checking where a binary came from

Each release archive has a .sha256 and a signed .sigstore.json beside it. They answer different questions. The checksum says the file did not change between the release page and your disk. It says nothing about who made the file — anyone can publish a binary and a correct checksum of it.

The provenance attestation is the one worth running. It is a Sigstore-signed statement that this exact archive was built by this repository's release workflow, from a named commit, in a named workflow run. Releases carry one from 0.11.0 onward; earlier archives have a checksum and nothing else.

gh attestation verify pixi-sbom-<version>-linux-64.tar.gz --repo aesir-oss/pixi-sbom

The PyPI wheels are attested the same way, from 1.7.0 on: gh attestation verify pixi_sbom-<version>-py3-none-<platform>.whl --repo aesir-oss/pixi-sbom checks a downloaded wheel (pip download pixi-sbom --no-deps fetches one), and PyPI shows its own publish attestation beside each file.

Loaded digest sha256:... for file://pixi-sbom-<version>-linux-64.tar.gz
Loaded 1 attestation from GitHub API

The following policy criteria will be enforced:
- Predicate type must match:................ https://slsa.dev/provenance/v1
- Source Repository Owner URI must match:... https://github.com/aesir-oss
- Source Repository URI must match:......... https://github.com/aesir-oss/pixi-sbom

✓ Verification succeeded!

That needs the GitHub CLI and reaches api.github.com to fetch the statement. On a machine that cannot, pass the bundle that ships with the archive and the check runs entirely offline:

gh attestation verify pixi-sbom-<version>-linux-64.tar.gz \
  --bundle pixi-sbom-<version>-linux-64.tar.gz.sigstore.json \
  --repo aesir-oss/pixi-sbom

The strict form

--repo alone accepts an archive signed by any workflow in this repository, from any branch. To also require that it came from the release workflow, run on the tag of the version you downloaded, on a GitHub-hosted runner:

gh attestation verify pixi-sbom-<version>-linux-64.tar.gz --repo aesir-oss/pixi-sbom \
  --signer-workflow aesir-oss/pixi-sbom/.github/workflows/release-artifacts.yml \
  --source-ref refs/tags/v<version> \
  --deny-self-hosted-runners

Older releases were built, and signed, in the repositories pixi-sbom lived in before: 1.12.0 to 1.13.1 in aesir-oss/pixi-ext-contrib (tagged pixi-sbom-v<version> there), up to 1.11.x in millsks/pixi-sbom. Their releases here carry each archive's signed bundle (<archive>.sigstore.json, from 0.11.0 on), so verify against the bundle and name the repository that built it. This checks the signature offline and does not need that repository to exist:

gh attestation verify pixi-sbom-v1.12.0-linux-64.tar.gz \
  --bundle pixi-sbom-v1.12.0-linux-64.tar.gz.sigstore.json \
  --repo aesir-oss/pixi-ext-contrib \
  --signer-workflow aesir-oss/pixi-ext-contrib/.github/workflows/release-artifacts.yml \
  --source-ref refs/tags/pixi-sbom-v1.12.0

For 1.11.x and older, use --repo millsks/pixi-sbom, --signer-workflow millsks/pixi-sbom/.github/workflows/release-artifacts.yml and --source-ref refs/tags/v<version>.

Flag Refuses
--signer-workflow An archive signed by any other workflow, such as a CI job that also builds binaries
--source-ref One built from a branch, or from a different release's tag
--deny-self-hosted-runners One built on a machine outside GitHub's hosted runners

It combines with --bundle for the offline check. The strict form holds from 0.12.0 onward. The 0.11.0 archives were signed by release.yml on refs/heads/main, before the build moved to the tag-triggered workflow (#218), so for 0.11.0 the plain --repo check is as far as verification goes.

The conda-forge package is deliberately not attested: the feedstock builds on conda-forge's infrastructure rather than ours, so the provenance there would be theirs to make. The binaries this project builds and hands out are the ones covered.

Not on crates.io any more. pixi-sbom is not published to crates.io after 1.13.0 (#106): cargo install pixi-sbom and cargo binstall pixi-sbom stay at 1.13.0. Use pixi global install pixi-sbom, uv tool install pixi-sbom (or pip), or a release archive.

Shell completion

pixi-sbom completes its flags, the values they take (--report <TAB> lists the report kinds, --format <TAB> the formats) and file paths. Flags and values are listed alphabetically in every shell, so the --fail-on-… gates sit together. Add the line for your shell to its startup file:

Shell Startup file Line
bash ~/.bashrc (~/.bash_profile on macOS) eval "$(PIXI_SBOM_COMPLETE=bash pixi-sbom)"
zsh ~/.zshrc source <(PIXI_SBOM_COMPLETE=zsh pixi-sbom)
fish ~/.config/fish/config.fish PIXI_SBOM_COMPLETE=fish pixi-sbom \| source
PowerShell $PROFILE $env:PIXI_SBOM_COMPLETE = "powershell"; pixi-sbom \| Out-String \| Invoke-Expression; Remove-Item Env:\PIXI_SBOM_COMPLETE
elvish ~/.config/elvish/rc.elv eval (E:PIXI_SBOM_COMPLETE=elvish pixi-sbom \| slurp)

For bash, eval rather than source <(…): macOS's own bash is 3.2, where source reading a process substitution gets nothing and completion silently stays off. The line asks the binary for its registration each time a shell starts, so it always matches the installed version; don't save its output to a file. Pressing Tab runs pixi-sbom briefly to ask for suggestions. That run reads no lockfile, writes nothing and makes no network request, and PIXI_SBOM_COMPLETE is set only for it, so every other run writes its SBOM as usual.

pixi sbom, with a space

pixi's own completion knows only pixi's built-in commands: it does not hand an extension's arguments to the extension (#424 has the tests, and prefix-dev/pixi#7225 asks pixi to). The mechanism above is the one pixi would use to forward them, so if it does, pixi sbom completes with no change here.

Until then, a short addition to your shell's startup file hands pixi sbom … to pixi-sbom's completion. Load it after pixi's completion (pixi completion --shell <shell>) and pixi-sbom's line above. Each one is run in that shell by CI on every change, next to pixi's own completion, which keeps working.

# Complete `pixi sbom ...` with pixi-sbom's own completion. Load after pixi's and pixi-sbom's.
_pixi_with_sbom() {
  if (( COMP_CWORD >= 2 )) && [[ ${COMP_WORDS[1]} == sbom ]]; then
    local spec fn old=${#COMP_LINE}
    spec=$(complete -p pixi-sbom 2>/dev/null) || return 0
    fn=${spec##*-F }; fn=${fn%% *}
    COMP_WORDS=(pixi-sbom "${COMP_WORDS[@]:2}"); (( COMP_CWORD-- ))
    COMP_LINE="pixi-sbom ${COMP_LINE#*sbom }"; (( COMP_POINT -= old - ${#COMP_LINE} ))
    "$fn" pixi-sbom "${COMP_WORDS[COMP_CWORD]}" "${COMP_WORDS[COMP_CWORD-1]}"
  else
    _pixi "$@"
  fi
}
# -o nosort keeps pixi-sbom's order, and needs bash 4.4 or later (macOS's /bin/bash is 3.2).
if (( BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 4) )); then
  complete -o bashdefault -o default -o nosort -F _pixi_with_sbom pixi
else
  complete -o bashdefault -o default -F _pixi_with_sbom pixi
fi
# Complete `pixi sbom ...` with pixi-sbom's own completion. Load after pixi's and pixi-sbom's.
_pixi_with_sbom() {
  if (( CURRENT > 2 )) && [[ ${words[2]} == sbom ]]; then
    shift words; (( CURRENT-- )); words[1]=pixi-sbom; _normal
  else
    _pixi "$@"
  fi
}
compdef _pixi_with_sbom pixi
# Complete `pixi sbom ...` with pixi-sbom's own completion.
complete -c pixi -n 'test (count (commandline -opc)) -ge 2; and test (commandline -opc)[2] = sbom' -f \
    -a '(complete -C "pixi-sbom "(string replace -r "^\s*\S+\s+sbom\s+" "" -- (commandline -cp)))'
# Complete `pixi sbom ...` with pixi-sbom's own completion. Replaces the usual
# `pixi completion --shell powershell | Out-String | Invoke-Expression` line, because a command
# has one native completer: pixi's is kept and called for everything that is not `pixi sbom`.
$s = (pixi completion --shell powershell | Out-String) -replace "Register-ArgumentCompleter -Native -CommandName 'pixi' -ScriptBlock", '$global:PixiCompleter ='
Invoke-Expression $s
Register-ArgumentCompleter -Native -CommandName pixi -ScriptBlock {
  param($w, $ast, $pos)
  $e = $ast.CommandElements
  if ($e.Count -ge 2 -and $e[1].Extent.Text -eq 'sbom' -and $pos -gt $e[1].Extent.EndOffset) {
    $o = $ast.Extent.StartOffset
    $line = $ast.Extent.Text.Substring(0, [math]::Min($pos - $o, $ast.Extent.Text.Length)).PadRight($pos - $o)
    $inner = 'pixi-sbom' + $line.Substring($e[1].Extent.EndOffset - $o)
    (TabExpansion2 -inputScript $inner -cursorColumn $inner.Length).CompletionMatches
  } else { & $global:PixiCompleter $w $ast $pos }
}

PowerShell allows one completer per command, so this replaces the usual pixi completion line and calls pixi's completer for everything that is not pixi sbom. It edits the text of pixi's script, and stops working if pixi changes that line.

Two limits: sbom is not added to the list pixi <TAB> offers, and pixi options placed before sbom (pixi -v sbom …) are not handled.

Upgrading

pixi global update pixi-sbom follows the conda-forge feedstock, which tracks releases within a day or two; a downloaded binary is replaced by the next one from the releases page. pixi sbom --version prints the running version, and every release is listed in the changelog.

Next: run it, use it in CI, or see which format to pick.