seal

How it works

Markdown

Sealing a secret

Sealing turns a key into a blob that only your enclave can open, for the apps you name and the destination you choose.

Three ways to seal#

The CLI. Use this one. It checks the enclave itself and reads the secret without echoing it. Piping it in works too.

Terminal
$seal secret --to https://api.openai.com/v1 --app checkout > openai.blob

The SDK. For scripts that rotate keys. It reads SEAL_ENDPOINT and SEAL_TENANT like your app, and needs no app identity.

import os, seal

blob = seal.seal(os.environ["OPENAI_API_KEY"], to="https://api.openai.com/v1", apps=["checkout"])

The console's Seal page. Handy for a test key. The page's code comes from Seal and sees the secret as you type it, so use the CLI for production keys.

What happens when you seal#

The secret is encrypted on your machine, for an enclave your machine has just checked.

Your machineseal secretWaiting for proof…
SealFront doorPasses traffic through.
EnclaveYour tenant'sfingerprint 5f02…991c ✓signed release ✓tenant acme ✓
FigureCheck the enclave. Before anything leaves your machine, the CLI asks the enclave to prove what it is. The chip signs a report of the exact build it runs, and the CLI checks it against Seal's public releases.

The CLI and SDK refuse a blob that doesn't name your tenant and root id or carry exactly your policy. A blob opens in every replica of your tenant, whoever runs it.

Anyone who can reach your enclave can seal for your tenant. That gains them nothing. A blob holds only the secret its sealer typed in. Sealing does need your root id, which seal use and SEAL_TENANT=acme@<root id> provide.

Options#

Options are written into the blob in the clear, and the enclave enforces them on every request. To change one, seal a new blob.

--to
What it doesDestination URL. Required.
--app
What it doesApps that may use it. Required, repeatable.
--header
What it doesHeader for the secret. Default authorization.
--format
What it doesHeader value with {key}. Default Bearer {key} for authorization.
--methods
What it doesAllowed methods, like GET,POST.
--deny
What it doesPath prefix to refuse. Repeatable.
--expires
What it doesWhen the blob stops working.
--pin
What it doesKey the upstream's TLS chain must contain, as sha256/<base64>. Pin a CA's key so renewals don't break it.
--sealed-only
What it doesReal TEE hardware only, no simulator.
--receipt-file
What it doesKeep a telemetry receipt.

The SDKs take the same options under matching names, like sealed_only in Python or seal.WithHeader in Go.

Seal refuses unencrypted destinations, a password inside the URL, and query strings. A key sent in a body or query string isn't supported. APIs and databases lists what each upstream allows.

A session can do anything the credential allows. Give each key the least permissions you can, and --deny the API's key-management paths so nobody mints a new key through the enclave.

Examples#

An HTTPS API with an x-api-key header, two methods, its key paths denied and an expiry:

Terminal
$seal secret --to https://api.example.com/v2 --app checkout --header x-api-key --methods GET,POST --deny /v2/keys --expires 2027-01-01 > example.blob

Postgres over TLS. The user, host and database go in --to. You type only the password.

Terminal
$seal secret --to postgres://[email protected]:5432/prod --app checkout > db.blob

Redis over TLS, port 6380, database 0:

Terminal
$seal secret --to rediss://cache.example.com:6380/0 --app checkout > redis.blob

Keeping blobs#

Put the blob where the secret used to be, under a name like OPENAI_API_BLOB. A database row, secrets manager or CI variable works too. Seal keeps no copy, so if you lose one, seal again.

To rotate, seal the new value, swap the blob and revoke the old key at the provider. No switch kills a single blob, so set --expires on blobs you hand out. See Rotating and revoking.

Secrets must be at least 16 bytes, so masking can't be used to test guesses. Postgres passwords must also be printable ASCII. See Limits.