MainSDK
Search⌘K
How-to

Seal your build and prove what runs

Sign one record of what your build is, serve it at /.well-known/mainsdk.json, check the files at boot, and review new sensitive capabilities before a release.

Planned for 0.2

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

Your build will carry one signed record of what it is: a hash of exactly the files that ship, every MainSDK call the code makes and which of them are sensitive, what main.yaml grants, its SBOM and its check result. The app serves the record at /.well-known/mainsdk.json, checks its own files against it at boot, and anyone can verify it with mainsdk info <url>. It takes about 15 minutes.

You need

  • A MainSDK app with a main.yaml and a lockfile (pnpm-lock.yaml or package-lock.json).
  • A place outside git for the private key, such as your CI secrets.

1. Make a key and pin it

bash
npx mainsdk seal keygen --out ~/.keys/orders-seal.pem

The private key is written with mode 0600 and the public key is printed as ed25519:<base64>. Pin it, so only this key's seals count as verified:

main.yaml
seal:
  mode: warn                      # off | warn | refuse
  include: [.next/standalone, .next/static, public]
  exclude: ["**/*.map"]
  signers:
    - key: ed25519:MCowBQYDK2VwAyEA…

Never put the private key in main.yaml or in the repository.

2. Seal at build time

Run it after the build, in the folder that holds main.yaml:

bash
npx mainsdk seal --key ~/.keys/orders-seal.pem

It writes mainsdk.seal.json (the served document), mainsdk.seal.files (the file manifest the boot check names files from) and mainsdk.sbom.cdx.json (CycloneDX 1.6). Ship all three with the build. If the record would hold a secret, an email address or the value of an environment variable, nothing is written and the command fails with MAIN-SEAL-0003 naming the field.

3. Serve it and check at boot

Every adapter serves mainsdk.seal.json unchanged at GET /.well-known/mainsdk.json; you write no route. At boot the app recomputes the build hash and reports app.info().seal. In warn mode a mismatch is logged once as MAIN-SEAL-0001 with the changed paths, after the app is up. In refuse mode the app stops before it listens; use it for long-running servers, since it reads every shipped file at each start. Keep the mode on in production: mainsdk check --env prod reports MAIN-SEAL-0006 for seal.mode: off.

4. Review capabilities before a release

bash
npx mainsdk seal diff https://orders.acme.dev ./ --key ed25519:MCowBQYDK2VwAyEA… --fail-on sensitive

It lists new sensitive kinds, new sensitive calls (payments, AI spend, programs, cross-tenant access and more), widened grants and new destructive actions as MAIN-SEAL-0008, before everything else. In CI, --fail-on sensitive stops the release until someone has looked.

Check it worked

Run npx mainsdk seal verify .; it prints verified and exits 0. Change one shipped file and run it again: it exits 1 with MAIN-SEAL-0007 and names the file. Against the running app, npx mainsdk info https://orders.acme.dev --key ed25519:… prints verified with the project, version, commit and capabilities. See mainsdk seal.

5. Sign keyless in CI with Sigstore

Keyless signing needs your CI's OIDC identity, so the CLI never signs by itself. It writes the statement, your CI signs it, and the CLI checks what comes back:

bash
npx mainsdk seal --sigstore
cosign attest-blob --statement .mainsdk/seal.statement.json --bundle seal.bundle.json
npx mainsdk seal --sigstore --bundle seal.bundle.json --trust-root ./trusted_root.json

Keep a copy of Sigstore's trusted_root.json in your repository and pass it as --trust-root; the CLI fetches nothing. Pin who may sign in main.yaml:

main.yaml
seal:
  signers:
    - sigstore:
        issuer: https://token.actions.githubusercontent.com
        identity: https://github.com/acme/orders/.github/workflows/release.yml@refs/heads/release
bash
npx mainsdk seal verify . --trust-root ./trusted_root.json
npx mainsdk seal verify . --trust-root ./trusted_root.json --identity "^https://github.com/acme/orders/" --issuer https://token.actions.githubusercontent.com

verified means the chain, the Rekor entry and the signer all check out. A bundle that is genuine but signed by someone else is signed (MAIN-SEAL-0004); a bundle that doesn't verify fails with MAIN-INFO-0002 and the reason. Add --provenance <bundle> when sealing to carry an SLSA provenance bundle from a hardened builder. The running app does not check the chain: it reports the signer as unverified-at-runtime, so verify from outside with mainsdk info <url> --trust-root ….

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet