Error Handling

ServiceError variants, HTTP status codes, and JSON response format

Error Design

All KMS errors use a centralized ServiceError enum that implements IntoResponse for Axum. Every error path produces a consistent JSON body with an HTTP status code, a snake_case error code, and an optional details string.

JSON Response Format

{
  "error": "snake_case_error_code",
  "details": "Optional human-readable description" // may be null
}

The error field is always present and uses a stable snake_case identifier suitable for programmatic matching. The details field provides context for debugging but may be omitted or null.

ServiceError Variants

StatusError CodeDescription
400
InvalidInput
The request body is malformed, missing required fields, or contains invalid data types. Returned by all endpoints when JSON parsing fails or required fields are absent.
400
InvalidChallenge
The challengeId in a /get-key request was not found (already consumed or never existed) or has expired past its TTL. Also returned as 401 when the challenge exists but the peerId doesn’t match the original challenger.
401
InvalidSignature
The Ed25519 signature in the /get-key request failed verification. Either the signature is corrupted, or the signing key doesn’t match the peerId’s public key.
403
PolicyViolation
The TDX quote’s measurements do not match the attestation policy. The details field indicates which field failed (e.g. "RTMR3 not in allowed values"). This is the primary defense against unauthorized or tampered nodes.
429
RateLimited
The requesting peerId has exceeded MAX_PENDING_CHALLENGES active challenges. The node must wait for existing challenges to expire or be consumed before requesting new ones.
503
PolicyNotReady
The KMS has not yet finished fetching or parsing the attestation policy from GitHub releases. This typically occurs during startup. The node should retry after a brief delay.
500
InternalError
An unexpected internal error occurred (dstack communication failure, challenge store error, etc.). The details field may contain a sanitized error message. Internal details are never leaked to the caller in production.

ServiceError Enum

The Rust enum that maps each variant to an HTTP status code and JSON body.

pub enum ServiceError {
  InvalidInput(String), // → 400
  InvalidChallenge(String), // → 400 or 401
  InvalidSignature(String), // → 401
  PolicyViolation(String), // → 403
  RateLimited, // → 429
  PolicyNotReady, // → 503
  InternalError(String), // → 500
}

impl IntoResponse for ServiceError {
  fn into_response(self) -> Response {
    // maps variant → (StatusCode, Json { error, details })
  }
}

Error Flows by Endpoint

POST /challenge

400 InvalidInput

Missing or malformed peerId in the request body.

429 RateLimited

The peerId already has MAX_PENDING_CHALLENGES active challenges.

503 PolicyNotReady

The KMS policy hasn’t been loaded yet. The node should retry.

500 InternalError

Challenge store write failure (e.g. Redis connection error).

POST /get-key

400 InvalidInput

Missing or malformed fields in the request body (challengeId, quote, signature).

400/401 InvalidChallenge

Challenge not found, expired, or peerId mismatch. 400 for not-found/expired, 401 for peerId mismatch.

401 InvalidSignature

Ed25519 signature verification failed against the peerId’s public key.

403 PolicyViolation

TDX quote measurements don’t match the attestation policy allowlists.

503 PolicyNotReady

Policy not yet loaded (startup race).

500 InternalError

dstack communication failure, quote parsing error, or key derivation failure.

POST /attest

400 InvalidInput

Missing or malformed nonce in the request body.

500 InternalError

dstack quote generation failure (socket error, TDX driver error).

GET /health

503 PolicyNotReady

Returns 503 until the attestation policy is successfully loaded. Load balancers should use this to determine readiness.

200 OK

Service is ready to accept requests. Policy is loaded and challenge store is operational.

Example Responses

400 InvalidInput
HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": "invalid_input",
  "details": "missing field: peerId"
}
401 InvalidSignature
HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "error": "invalid_signature",
  "details": "Ed25519 signature verification failed"
}
403 PolicyViolation
HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "error": "policy_violation",
  "details": "RTMR3 value 'a1b2c3...' not in allowed values for profile 'locked-read-only'"
}
429 RateLimited
HTTP/1.1 429 Too Many Requests
Content-Type: application/json

{
  "error": "rate_limited",
  "details": null
}
503 PolicyNotReady
HTTP/1.1 503 Service Unavailable
Content-Type: application/json

{
  "error": "policy_not_ready",
  "details": "attestation policy not yet loaded"
}
500 InternalError
HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{
  "error": "internal_error",
  "details": "failed to communicate with dstack"
}