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.
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.
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.yamland a lockfile (pnpm-lock.yamlorpackage-lock.json). - A place outside git for the private key, such as your CI secrets.
1. Make a key and pin it
npx mainsdk seal keygen --out ~/.keys/orders-seal.pemThe 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:
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:
npx mainsdk seal --key ~/.keys/orders-seal.pemIt 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
npx mainsdk seal diff https://orders.acme.dev ./ --key ed25519:MCowBQYDK2VwAyEA… --fail-on sensitiveIt 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:
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.jsonKeep 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:
seal:
signers:
- sigstore:
issuer: https://token.actions.githubusercontent.com
identity: https://github.com/acme/orders/.github/workflows/release.yml@refs/heads/releasenpx 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.comverified 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?