Skip to content
All library documents

NautilusTrader Release Artifact Security and Verification

Article NautilusTrader

Summary

This document explains how NautilusTrader builds, publishes, and verifies release artifacts across Python packages, Rust crates, Docker images, and GitHub Releases. Its release process anchors package integrity to a draft GitHub release: artifacts are attached first, published to registries, checked against release records, and then the release is finalized. Trusted Publishing and short-lived GitHub Actions identities avoid persistent registry tokens; checksums, manifests, attestations, signatures, and transparency records provide public evidence of integrity and provenance.

The guide outlines trust roots, threat coverage, consumer verification checks, recovery procedures, and incident responses. It distinguishes risks the pipeline addresses from limits it cannot address, including malicious authorized maintainers, compromised trust providers, and end-user machine compromise. It also states that digest and provenance verification do not guarantee bit-identical rebuilds, and it makes no blanket SLSA-level claim. These are operational supply-chain controls rather than trading methods, so their relevance is chiefly to users evaluating software release integrity.

Key ideas

  • The GitHub release is the integrity anchor for package artifacts before registry publication.
  • Trusted Publishing ties package publication to specific repository, workflow, and environment identities.
  • Consumers can compare artifact hashes and verify attestations against expected workflow identities.
  • Manual crate publishing requires an explicit exception and an audit record.
  • The pipeline verifies provenance and digests but does not promise reproducible builds or protect against every trust-root compromise.

Tags

Full text
# Security Architecture


# Security Architecture

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:

- [Supply chain policy](https://nautilustrader.io/security/supply-chain/), which states the public
  dependency and release integrity commitments.
- [Releases](releases.md), which documents the release workflow and checklist.
- [Security Policy](../../SECURITY.md), which gives consumer-facing verification commands.
- [GitHub Actions overview](../../.github/OVERVIEW.md#security), which documents CI/CD controls.

## Security goals

The release pipeline has four goals:

- Build every official artifact from a reviewed repository commit.
- Publish Python and Rust packages without long-lived package registry tokens.
- Attach checksums, manifests, and provenance before publishing the GitHub release.
- Give users enough public data to verify that downloaded artifacts match the release.

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.

## Threat model

The pipeline defends against:

- Compromised or mutable third-party GitHub Actions, by pinning actions to commit SHAs.
- Accidental release from the wrong workflow, branch, or environment, by binding OIDC publishers
  to `nautechsystems/nautilus_trader`, `build.yml`, and the `release` environment.
- Long-lived package registry token theft, by using PyPI and crates.io Trusted Publishing.
- Registry propagation lag or partial re-runs, by making publish and verify scripts idempotent
  and retry-tolerant.
- Registry substitution or upload drift, by comparing PyPI and crates.io artifacts against
  release manifests and registry metadata.
- Silent manual crate recovery, by requiring explicit `CRATES_IO_MANUAL_PUBLISH_EXCEPTIONS`
  entries and recording those exceptions in `crates-manifest.json`.

The pipeline does not defend against:

- A malicious maintainer with permission to change release workflows and approve releases.
- A compromise of GitHub, PyPI, crates.io, or Sigstore that can forge the trust roots users rely on.
- A compromised end-user machine before verification runs.
- Runtime compromise of an exchange, broker, data provider, or user trading strategy.
- Bit-identical rebuild drift for wheels and sdists. The current guarantee is provenance and
  digest verification, not reproducible builds.

## Trust roots

- GitHub repository rules protect reviewed source, release branches, and release tags.
  Protected `master` and immutable `v*` release tags are the relevant records.
- GitHub Actions OIDC issuer provides short-lived workflow identities from
  `https://token.actions.githubusercontent.com`.
- GitHub `release` environment gates package publishing and release approvals.
  The environment restricts deployment to `master` and requires reviewer approval.
- PyPI Trusted Publishing publishes wheels and sdist without a persistent token.
  It binds to repository `nautechsystems/nautilus_trader`, workflow `build.yml`,
  and environment `release`.
- crates.io Trusted Publishing publishes Rust crates without a persistent token.
  It binds to owner `nautechsystems`, repository `nautilus_trader`, workflow
  `build.yml`, and environment `release`.
- Sigstore Fulcio, Rekor, and TUF bind artifacts to OIDC identities and the
  transparency log. GitHub artifact attestations, PyPI publish attestations, and
  Docker cosign signatures rely on this root.
- GitHub release immutability prevents post-publish asset and tag replacement.
  Published release assets and the release tag become immutable.

## Release flow

```mermaid
flowchart TD
    source["Reviewed commit on master"]
    gates["Release gates<br/>Rust suite + cargo-deny + cargo-vet<br/>Cargo publish + docs/features preflights"]
    wheels["Build wheels"]
    draft["Create tag and draft GitHub release"]
    sdist["Build sdist"]
    assets["Attach wheels and sdist to draft release"]
    registries["Publish PyPI and crates.io<br/>Trusted Publishing"]
    verify["Verify registries against release assets"]
    integrity["Attach SHA256SUMS, manifests,<br/>Sigstore bundles, DSSE envelopes"]
    publish["Publish GitHub release"]
    release_attest["Verify GitHub release attestation"]
    docker["Build, sign, and attest Docker images"]

    source --> gates
    source --> wheels
    gates --> draft
    wheels --> draft
    draft --> sdist
    sdist --> assets
    wheels --> 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.

## Artifact records

- Python wheels are published to GitHub Releases, PyPI, and the Nautech Systems
  package index (`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.
- Python sdists are published to GitHub Releases and PyPI. They use the same integrity and
  provenance records as wheels but are not published to the wheel-only package index.
- Rust crates are published to crates.io. The crates.io checksum and
  `crates-manifest.json` record integrity. crates.io `trustpub_data` records
  provenance unless an explicit manual exception is present.
- Docker images are published to GitHub Container Registry. The image digest is
  the integrity record. Sigstore cosign signatures and SPDX SBOM attestations
  record provenance.
- The GitHub release record is published through GitHub Releases. Published
  release assets and the immutable tag record integrity. The GitHub release
  attestation records provenance.

## Consumer verification map

Detailed commands live in
[Verifying releases](https://github.com/nautechsystems/nautilus_trader/blob/develop/SECURITY.md#verifying-releases).
The checks below show the public data each consumer should verify.

### Python wheels and sdist

Verify:

- The artifact digest matches `SHA256SUMS`, the per-asset `.sha256` file, or
  `dist-manifest.json`.
- The GitHub artifact attestation identity matches
  `nautechsystems/nautilus_trader/.github/workflows/build.yml` on `master` or `nightly`.
- The PyPI publish attestation reports repository `nautechsystems/nautilus_trader`,
  workflow `build.yml`, and environment `release`.

Example:

```bash
: "${VERSION:?Set VERSION to the Python package version}"
: "${ARTIFACT:?Set ARTIFACT to the release asset filename}"
TAG="v$VERSION"
REPO=nautechsystems/nautilus_trader
ISSUER=https://token.actions.githubusercontent.com
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"
```

### PyPI publish provenance

Verify:

- PyPI file hashes match `dist-manifest.json`.
- PyPI provenance exposes the expected GitHub publisher identity.
- `pypi-attestations verify` accepts the downloaded file URL.

Example:

```bash
: "${VERSION:?Set VERSION to the Python package version}"
: "${ARTIFACT:?Set ARTIFACT to the release asset filename}"
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"
```

### Rust crates

Verify:

- The crates.io version checksum matches the downloaded `.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.

Example:

```bash
CRATE=${CRATE:-nautilus-core}
: "${VERSION:?Set VERSION to the crate version}"
REPO=nautechsystems/nautilus_trader
VERSION_JSON=$(curl -sS "https://crates.io/api/v1/crates/$CRATE/versions" | \
  jq -c --arg version "$VERSION" '.versions[] | select(.num == $version)')
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"
```

### Docker images

Verify:

- The mutable tag resolves to the digest you intend to run.
- The cosign signature identity matches the Docker workflow.
- The SPDX SBOM attestation is bound to the same image digest.

Example:

```bash
export IMAGE_BASE=ghcr.io/nautechsystems/nautilus_trader
export DIGEST=$(crane digest "$IMAGE_BASE:latest")
export IMAGE=$IMAGE_BASE@$DIGEST
export ISSUER=https://token.actions.githubusercontent.com
export 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"
```

## Manual recovery posture

Normal releases use Trusted Publishing only. Manual package publishing is a
last-resort recovery path after a partial release.

Rules for manual recovery:

- Prefer re-running the failed job or workflow when a registry or Sigstore verifier fails.
- Do not replace a release tag or GitHub release assets after publication.
- Do not silently accept manually published crates.
- If a crate must be recovered with a token, list each `crate@version` in
  `CRATES_IO_MANUAL_PUBLISH_EXCEPTIONS`.
- Record the exception in release notes and in `crates-manifest.json` with
  `release_status: "manual_token_publish"`.

No routine release path depends on a long-lived PyPI or crates.io token.

## Incident response posture

- PyPI publisher drift is detected by the PyPI provenance verifier. Stop
  publishing, fix the PyPI Trusted Publisher, and rerun verification.
- crates.io publisher drift is detected by the trusted-publishing check or
  registry verifier. Fix crate publisher settings and rerun. Use a manual
  exception only for partial recovery.
- GitHub release asset mismatch is detected by checksum or manifest verification.
  Stop the release before publication, or publish an advisory if assets already
  shipped.
- Sigstore, Rekor, or TUF lag is detected by retryable transparency errors.
  Retry with bounded backoff, then pause release sealing if lag persists.
- Sigstore trust root concern appears when attestation verification becomes
  ambiguous. Pause releases, verify against registry records, and rotate trust
  roots when supported.
- Workflow identity mismatch is detected by GitHub, PyPI, or cosign identity
  checks. Treat it as configuration drift or compromise until reviewed.
- Manual crate publish exceptions are detected when crates.io shows
  `published_by` instead of `trustpub_data`. Record the explicit exception,
  document affected crates, and preserve the audit trail.

## SLSA posture

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.

Shown in full with attribution under the source's licence. Licence: LGPL-3.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.