docs/developer_guide/release_security.md
This page describes the security model for the NautilusTrader release pipeline. It explains how release artifacts are built, published, attested, and verified.
Use this page with:
The release pipeline has four goals:
The GitHub release anchors package integrity. Stable releases attach wheel and sdist assets to a draft GitHub release before any package index publish starts. The pipeline publishes package indexes, verifies those indexes against the GitHub release assets, attaches final integrity assets, then publishes the GitHub release.
The pipeline defends against:
nautechsystems/nautilus_trader, build.yml, and the release environment.CRATES_IO_MANUAL_PUBLISH_EXCEPTIONS
entries and recording those exceptions in crates-manifest.json.The pipeline does not defend against:
master and immutable v* release tags are the relevant records.https://token.actions.githubusercontent.com.release environment gates package publishing and release approvals.
The environment restricts deployment to master and requires reviewer approval.nautechsystems/nautilus_trader, workflow build.yml,
and environment release.nautechsystems, repository nautilus_trader, workflow
build.yml, and environment release.flowchart TD
source["Reviewed commit on master"]
gates["Release gates
cargo-deny + cargo-vet"]
build["Build wheels and sdist"]
draft["Create tag and draft GitHub release"]
assets["Attach wheels and sdist to draft release"]
registries["Publish PyPI and crates.io
Trusted Publishing"]
verify["Verify registries against release assets"]
integrity["Attach SHA256SUMS, manifests,
Sigstore bundles, DSSE envelopes"]
publish["Publish GitHub release"]
release_attest["Verify GitHub release attestation"]
docker["Build, sign, and attest Docker images"]
source --> gates
source --> build
gates --> draft
build --> draft
draft --> assets
assets --> registries
registries --> verify
verify --> integrity
integrity --> publish
publish --> release_attest
source --> docker
The Docker workflow is separate from the package release workflow, but it follows the same identity model: image signatures and SBOM attestations bind the image digest to the expected GitHub Actions workflow identity.
packages.nautechsystems.io). SHA256SUMS, per-asset
.sha256 files, and dist-manifest.json record integrity. GitHub artifact
attestations, PyPI publish attestations, .sigstore bundles, and
.intoto.jsonl envelopes record provenance.crates-manifest.json record integrity. crates.io trustpub_data records
provenance unless an explicit manual exception is present.Detailed commands live in Verifying releases. The checks below show the public data each consumer should verify.
Verify:
SHA256SUMS, the per-asset .sha256 file, or
dist-manifest.json.nautechsystems/nautilus_trader/.github/workflows/build.yml on master or nightly.nautechsystems/nautilus_trader,
workflow build.yml, and environment release.Fish-compatible example:
set -gx TAG v1.228.0
set -gx REPO nautechsystems/nautilus_trader
set -gx ARTIFACT nautilus_trader-1.228.0.tar.gz
set -gx ISSUER https://token.actions.githubusercontent.com
set -gx IDENTITY \
'^https://github\.com/nautechsystems/nautilus_trader/\.github/workflows/build\.yml@refs/heads/(master|nightly)$'
gh release download $TAG --repo $REPO --pattern $ARTIFACT --pattern $ARTIFACT.sha256
sha256sum -c $ARTIFACT.sha256
gh attestation verify $ARTIFACT \
--repo $REPO \
--cert-identity-regex $IDENTITY \
--cert-oidc-issuer $ISSUER
Verify:
dist-manifest.json.pypi-attestations verify accepts the downloaded file URL.Fish-compatible example:
set -gx VERSION 1.228.0
set -gx ARTIFACT nautilus_trader-1.228.0.tar.gz
set -gx PYPI_URL (curl -sS https://pypi.org/pypi/nautilus_trader/$VERSION/json | \
jq -r --arg artifact "$ARTIFACT" '.urls[] | select(.filename == $artifact) | .url')
uv run --no-project --no-build --with pypi-attestations -- \
pypi-attestations verify pypi \
--repository https://github.com/nautechsystems/nautilus_trader \
$PYPI_URL
Verify:
.crate file.trustpub_data.provider is github.trustpub_data.repository is nautechsystems/nautilus_trader.published_by is null, unless crates-manifest.json records an explicit
manual_token_publish exception.Fish-compatible example:
set -gx CRATE nautilus-core
set -gx VERSION 0.58.0
set -gx REPO nautechsystems/nautilus_trader
set -gx VERSION_JSON (curl -sS https://crates.io/api/v1/crates/$CRATE/versions | \
jq -c --arg version "$VERSION" '.versions[] | select(.num == $version)')
set -gx CRATE_SHA256 (printf '%s\n' "$VERSION_JSON" | jq -r '.checksum')
printf '%s\n' "$VERSION_JSON" | jq -e --arg repo "$REPO" \
'.trustpub_data.provider == "github" and .trustpub_data.repository == $repo and .published_by == null'
curl -sSL https://static.crates.io/crates/$CRATE/$CRATE-$VERSION.crate -o $CRATE-$VERSION.crate
test (sha256sum $CRATE-$VERSION.crate | cut -d ' ' -f 1) = $CRATE_SHA256
Verify:
Fish-compatible example:
set -gx IMAGE_BASE ghcr.io/nautechsystems/nautilus_trader
set -gx DIGEST (crane digest $IMAGE_BASE:latest)
set -gx IMAGE $IMAGE_BASE@$DIGEST
set -gx ISSUER https://token.actions.githubusercontent.com
set -gx IDENTITY \
'^https://github\.com/nautechsystems/nautilus_trader/\.github/workflows/docker\.yml@refs/heads/(master|nightly)$'
cosign verify $IMAGE --certificate-identity-regexp $IDENTITY --certificate-oidc-issuer $ISSUER
cosign verify-attestation \
--type https://spdx.dev/Document/v2.3 \
$IMAGE \
--certificate-identity-regexp $IDENTITY \
--certificate-oidc-issuer $ISSUER
Normal releases use Trusted Publishing only. Manual package publishing is a last-resort recovery path after a partial release.
Rules for manual recovery:
crate@version in
CRATES_IO_MANUAL_PUBLISH_EXCEPTIONS.crates-manifest.json with
release_status: "manual_token_publish".No routine release path depends on a long-lived PyPI or crates.io token.
published_by instead of trustpub_data. Record the explicit exception,
document affected crates, and preserve the audit trail.Python release artifacts carry build provenance through GitHub artifact attestations
and PyPI publish attestations. Docker images carry Sigstore signatures and SPDX SBOM
attestations. Rust crates rely on crates.io Trusted Publishing metadata and the
release crates-manifest.json.
This page does not assert a named SLSA level for all artifact classes. Any future SLSA level claim must cite this architecture, name the artifact classes it covers, and include CI validation that the published provenance parses as the claimed predicate type.