seal

Reference

Markdown

Errors

Every error code Seal uses, in the SDKs and inside a session: what it means and what to do about it.

SDK calls raise a Seal error. Requests inside a session get refused in the protocol's own terms: an HTTP status, a Postgres error or a Redis reply. Both use the same thirteen codes. The caller gets only the code and a fixed message; the full reason goes to your telemetry.

Every error#

Retrying won't help with a "No" until something changes. Only Unavailable and SessionExpired are retryable.

Expired
MeaningThe blob is past its expires time, or its tenant key version was retired.
RetryNo
What to doSeal again and replace the blob. In Python, Node and Rust, reload_with (reloadWith in Node) picks up the new blob without a restart. See Rotating and revoking.
NotAllowed
MeaningNo grant for the blob's apps, outside the network rule, grant removed mid-session, or no identity found.
RetryNo
What to doCheck grants with seal status and the reason in telemetry. See Apps and identities.
TierTooLow
MeaningThe session is below the minimum tier the code or blob asked for. Higher tiers aren't available yet.
RetryNo
What to doYou only see this if you set a minimum tier.
IntegrityFailed
MeaningThe executable hash missed the artifact check, or a signal suspended the identity.
RetryNo
What to doFind the signal in telemetry. Wait out a timed suspension, or clear it with seal settings integrity clear.
Unavailable
MeaningSomething couldn't be reached: a hosted replica starting from idle, a replica with no fresh state (fails closed), or the upstream. The SDK already tried every replica for up to two minutes.
RetryYes
What to doRetry with backoff. If it lasts, check SEAL_ENDPOINT and telemetry.
PolicyDenied
MeaningA request is outside the blob's policy: path, method, --deny, method override, CONNECT or WebSocket, compressed response, Redis admin command. Usually an HTTP 403 inside the session.
RetryNo
What to doFix the request. Limits lists every rule.
BudgetExceeded
MeaningA per-source rate limit. Plan budgets aren't enforced yet.
RetryNo
What to doSlow down. A new session per request is the usual cause; open one and reuse it.
SessionExpired
MeaningThe session's token ran out.
RetryYes
What to doNothing. The SDK restarts the session itself.
SessionEnded
MeaningYour close() or the integrity policy ended the session early. An HTTP connection gets a 401 and closes.
RetryNo
What to doStart a new session.
VerificationFailed
MeaningThe enclave's hardware report, release, guardian certificate or TLS key didn't check out. It can mean impersonation, so the SDK never retries quietly.
RetryNo
What to doCheck SEAL_ENDPOINT and any TLS-inspecting proxy. Otherwise, stop and look. See Enclaves and attestation.
BadRequest
MeaningThe call is malformed: a bad to, app name, header or pin, a secret that breaks the secret rules, or a damaged blob.
RetryNo
What to doFix the calling code.
Internal
MeaningSeal itself failed. Details go only to telemetry.
RetryOnce
What to doIf it repeats, send the time and your tenant id to [email protected].
ResealRequired
MeaningThe blob predates the platform this replica runs on, so it has no key for it.
RetryNo
What to doSeal again. The new blob covers every enabled platform.

Catching errors#

Every SDK error carries a code, a retryable flag and a message.

import os, seal

try:
    with seal.session(os.environ["OPENAI_API_BLOB"]) as s:
        ...
except seal.Expired:
    # Seal the secret again and deploy the new blob
    ...
except seal.SealError as e:
    print(e.code, e.retryable, e.message)
    raise
Python
ShapeOne subclass of seal.SealError per code (seal.Expired, seal.NotAllowed, …) with .code, .retryable, .message. seal.BadRequest is also a ValueError.
Node
ShapeClasses from @sealzone/sdk extending SealError, with .code (the wire name, such as "not_allowed"), .retryable, .message.
Go
Shape*seal.Error with Code, Message, Retryable(). Match with errors.Is against seal.ErrExpired through seal.ErrResealRequired.
Rust
Shapeseal::ClientError, with e.code() returning a seal::ErrorCode and ErrorCode::retryable().

Refusals inside a session#

Your app talks to its usual client library, so refusals arrive in that protocol's format. Anything the upstream sends, its own errors included, passes through unchanged apart from key masking.

HTTP#

The enclave answers with a status and a JSON body. The seal_error field marks the refusal as Seal's, not the upstream's.

JSON
{
  "seal_error": "policy_denied",
  "message": "the request is outside the blob's policy"
}
400
Codesbad_request
401
Codessession_expired, session_ended
403
Codesnot_allowed, tier_too_low, integrity_failed, policy_denied
409
Codesreseal_required
410
Codesexpired
429
Codesbudget_exceeded
495
Codesverification_failed
500
Codesinternal
503
Codesunavailable

The message is one of a fixed set:

the request is outside the blob's policy
Codepolicy_denied
ConnectionStays open
the request carries no Seal credential
Codenot_allowed
ConnectionCloses
the Seal credential is not this connection's
Codenot_allowed
ConnectionCloses
the Seal credential was refused
Codevaries
ConnectionCloses
the blob is not for an HTTP destination
Codepolicy_denied
ConnectionCloses
the request body is too large to check
Codepolicy_denied
ConnectionCloses
the upstream sent an encoded response, which cannot be scrubbed
Codepolicy_denied
ConnectionStays open
the request was refused by the tenant's integrity policy
Codevaries
ConnectionStays open
the session was ended by the tenant's integrity policy
Codesession_ended
ConnectionCloses
the session has ended
Codesession_ended
ConnectionCloses
the upstream could not be reached
Codeunavailable
ConnectionStays open
the upstream request failed
Codeunavailable
ConnectionStays open

"No Seal credential" almost always means the library didn't send the stand-in key. Pass s.key where it expects its API key.

Postgres#

The error text starts with seal: and ends with the code, such as seal: … (policy_denied).

42501
Codespolicy_denied, not_allowed, integrity_failed, tier_too_low
28000
Codesexpired, session_expired, session_ended, reseal_required, verification_failed
57P01
CodesThe session ended while the connection was open
08006
Codesunavailable
53400
Codesbudget_exceeded
08P01
Codesbad_request
XX000
Codesinternal

Redis#

A command the blob doesn't allow gets -NOPERM seal policy_denied: …, and the connection stays open. When the session ends, the client gets -ERR seal <code>: … and the connection closes.

Finding the reason in telemetry#

A refused session shows up as a Refused entry. A refused request shows up as a denied Request entry with the code. Filter the console's Telemetry page by app, identity, blob or event, or run seal telemetry --since 1h. Entries never hold the secret, bodies or upstream text.

no_grant
What happenedThe identity matches no grant for the blob's apps. The entry shows the identity seen.
Caller gotNotAllowed
unknown_app_key
What happenedAn app key that no grant names.
Caller gotNotAllowed
outside_networks
What happenedConnected from outside the app's networks. The entry shows the address.
Caller gotNotAllowed
grant_lost
What happenedA live session's grant was removed.
Caller gotNotAllowed
bad_proof, token_expired
What happenedThe identity proof didn't verify, or its token was too old.
Caller gotNotAllowed
suspended
What happenedA signal suspended the identity. The entry shows the code and until when.
Caller gotIntegrityFailed
artifact_mismatch
What happenedThe executable hash isn't on the artifact list.
Caller gotIntegrityFailed
blob_expired
What happenedPast the blob's expires time.
Caller gotExpired
key_version_retired
What happenedThe blob's tenant key version is retired.
Caller gotExpired
not_sealed_for_platform
What happenedThe blob predates this replica's platform.
Caller gotResealRequired
tier_too_low
What happenedThe session's tier was below what was required.
Caller gotTierTooLow
stale_state, no_state
What happenedThe replica had no fresh tenant state.
Caller gotUnavailable
unavailable
What happenedA service didn't answer: the control plane, the key guardian or an identity issuer.
Caller gotUnavailable
malformed
What happenedThe request was malformed. The entry says how.
Caller gotBadRequest