---
title: "Supply chain"
description: "How we build, sign and deliver MainSDK itself, from reproducible SLSA level 3 builds to packs delivered through The Update Framework."
section: "architecture"
status: "Planned for 0.1"
spec: "repo-foundation"
canonical: "https://mainsdk.dev/docs/architecture/supply-chain"
updated: "2026-10-28"
---

# Supply chain

> How we build, sign and deliver MainSDK itself, from reproducible SLSA level 3 builds to packs delivered through The Update Framework.

Status: Planned for 0.1 · Canonical: https://mainsdk.dev/docs/architecture/supply-chain · Updated: 2026-10-28

We hold our own releases to the bar we ask of every app. Two paths matter: how our packages reach your machine, and how packs and driver metadata reach your running app.

```mermaid
flowchart LR
  subgraph build["Release workflow (only place that publishes)"]
    src["Tagged commit"] --> b["Hosted, isolated build<br/>reproducible"]
    b --> sig["Sigstore signature<br/>keyless, GitHub OIDC"]
    b --> prov["SLSA L3 provenance"]
    b --> sbom["CycloneDX SBOM"]
  end
  sig & prov & sbom --> reg["npm: mainsdk, @mainsdk/*"]
  reg --> you["Your install<br/>verify before use"]
  subgraph tuf["TUF repository"]
    packs["Baseline and market packs<br/>driver metadata"]
  end
  packs -- "mainsdk sync, startup, background" --> app["Your running app"]
```

## Our releases

| Practice | What it means |
| --- | --- |
| SLSA level 3 | Hosted, isolated build with provenance nobody can forge |
| Reproducible builds | Two clean builds of the same commit give byte-identical artifacts |
| Sigstore | Every artifact is signed keyless with the release workflow's identity. An artifact changed by one byte fails verification |
| SBOM | A CycloneDX SBOM with every release |
| No install scripts | No published package declares `preinstall`, `install` or `postinstall`, and none needs a native binary: `mainsdk` is pure TypeScript with zero runtime dependencies (ADR-0066) |
| Few dependencies, pinned | `pnpm-lock.yaml` with integrity, GitHub Actions pinned to commit SHAs (`Cargo.lock` too, once the Rust SDK lands in phase 5) |
| No laptop publishing | Only the release workflow publishes; never a developer machine |
| Apache-2.0 and DCO | Everything in the app is Apache-2.0; every commit carries a `Signed-off-by` line, checked in CI |

Each release ships these artifacts:

```text
mainsdk, @mainsdk/cli,     pure JavaScript tarballs, ESM only, no native binary
@mainsdk/testing
*.sigstore.json            Sigstore bundle per artifact
provenance.intoto.jsonl    SLSA level 3 provenance
sbom.cdx.json              CycloneDX SBOM
```

Package names stay placeholders until the owner approves them, and the pipeline defaults to a dry run.

## Delivery to running apps: TUF

Signing proves who signed a file. It does not stop an attacker who steals one key, replays an old but validly signed pack, hides newer ones, or mixes files from different releases. The Update Framework does (ADR-0005). From milestone 0.4, every baseline pack, market pack and driver metadata file is a TUF target.

| TUF role | In MainSDK | Default expiry |
| --- | --- | --- |
| Root | Our root of trust, kept offline and signed by a threshold of keys (for example 3 of 5). Rotating it never breaks running apps; they walk the chain of root versions | 1 year |
| Targets, with delegations | We sign baseline and market packs; verified driver publishers get delegated roles limited to their own paths | 90 days |
| Snapshot | One consistent set of versions, so files from different releases are never mixed | 7 days |
| Timestamp | Short-lived freshness, so a hidden newer set is detected | 1 day |

Still open: who holds the root keys, where they are stored, the final threshold and the host that serves the repository.

## Attacks it refuses

```mermaid
flowchart TB
  f["Fetch metadata and target"] --> r{"Version lower than trusted?"}
  r -- yes --> rb["Refuse: MAIN-TUF-0001 rollback"]
  r -- no --> t{"Timestamp expired?"}
  t -- yes --> st["Keep last good set: MAIN-TUF-0004 stale"]
  t -- no --> s{"Target listed in current snapshot?"}
  s -- no --> mm["Refuse: mix and match"]
  s -- yes --> h{"Bytes match the hash?"}
  h -- no --> bad["Refuse; never handed to the engine"]
  h -- yes --> ok["Load"]
```

A publisher signing outside its delegation is refused too. On expiry or an unreachable mirror, the app keeps its last good verified targets and never blocks traffic. The app verifies TUF metadata in TypeScript on WebCrypto with no dependency; the signing pipeline and CLI helpers use `tuf-js`.

`main.lock` records each target's hash, so two clean builds from the same lock load byte-identical packs.

## Tenant-signed Fleet policies

Phase 4 (Fleet). Each Fleet tenant gets a delegated TUF role signed only with its own keys, with a threshold (for example 2 of 3, one offline). We cannot forge a tenant's policy: a policy signed with one tenant key, or with our own key, is rejected and the app keeps its last good policy. Fleet sends only signed, declarative policy and data, never code.

## Security policy

`SECURITY.md` states:

- private disclosure through GitHub security advisories, with CVE numbering;
- critical issues fixed within 7 days and high within 30, with an advisory for each;
- a bug bounty live at public beta (platform and budget not chosen yet);
- a long-term support line of at least 24 months for each baseline.

## Later

| What | Phase |
| --- | --- |
| Verified developers; MainSDK countersigns passing releases as their Main mark | 2 |
| Independent security audit of the TypeScript core and SDK, published in full | Before 1.0, then yearly |
| Status page for MainSDK Cloud and TUF delivery | 2 |

## Next

- [Contracts and versioning](https://mainsdk.dev/docs/architecture/contracts-and-versioning): what `main.lock` pins.
- [Security architecture](https://mainsdk.dev/docs/architecture/security-model): where delivery sits among the trust boundaries.
