# 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.

Source: https://seal.zone/docs/sealing

## 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.

```shell
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.
```python
import os, seal

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

```typescript
import { Client } from "@sealzone/sdk";

const seal = await Client.fromEnv();
const blob = await seal.seal(process.env.OPENAI_API_KEY!, { to: "https://api.openai.com/v1", apps: ["checkout"] });
```

```go
client, err := seal.FromEnv(ctx)
if err != nil {
	return err
}
blob, err := client.Seal(ctx, []byte(os.Getenv("OPENAI_API_KEY")), "https://api.openai.com/v1", []string{"checkout"})
```

```rust
let seal = seal::Client::from_env().await?;
let key = std::env::var("OPENAI_API_KEY")?;
let blob = seal.seal(key.as_bytes(), "https://api.openai.com/v1", ["checkout"]).await?;
```
**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.

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.

| Option | What it does |
|---|---|
| `--to` | Destination URL. Required. |
| `--app` | Apps that may use it. Required, repeatable. |
| `--header` | Header for the secret. Default `authorization`. |
| `--format` | Header value with `{key}`. Default `Bearer {key}` for `authorization`. |
| `--methods` | Allowed methods, like `GET,POST`. |
| `--deny` | Path prefix to refuse. Repeatable. |
| `--expires` | When the blob stops working. |
| `--pin` | Key the upstream's TLS chain must contain, as `sha256/<base64>`. Pin a CA's key so renewals don't break it. |
| <span className="whitespace-nowrap">`--sealed-only`</span> | Real TEE hardware only, no simulator. |
| <span className="whitespace-nowrap">`--receipt-file`</span> | Keep a [telemetry](https://seal.zone/docs/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](https://seal.zone/docs/upstreams) 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:

```shell
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.

```shell
seal secret --to postgres://app@db.example.com:5432/prod --app checkout > db.blob
```

Redis over TLS, port 6380, database 0:

```shell
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](https://seal.zone/docs/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](https://seal.zone/docs/limits).
