getsops/sops · error

encrypted ciphertext cannot be cast to string

Error message

encrypted ciphertext cannot be cast to string

What it means

This error is returned by encryptedKeyFromSecret when secret.Data["ciphertext"] exists but is not a Go string. Vault normally returns ciphertext as a JSON string; a non-string value means the response shape deviates from the transit API contract, so the helper refuses to unsafe-cast it.

Source

Thrown at hcvault/keysource.go:361

	encoded := base64.StdEncoding.EncodeToString(dataKey)
	return map[string]interface{}{
		"plaintext": encoded,
	}
}

// encryptedKeyFromSecret attempts to extract the encrypted key from the data
// of the provided secret.
func encryptedKeyFromSecret(secret *api.Secret) (string, error) {
	if secret == nil || secret.Data == nil {
		return "", fmt.Errorf("transit backend is empty")
	}
	encrypted, ok := secret.Data["ciphertext"]
	if !ok {
		return "", fmt.Errorf("no encrypted data")
	}
	encryptedKey, ok := encrypted.(string)
	if !ok {
		return "", fmt.Errorf("encrypted ciphertext cannot be cast to string")
	}
	return encryptedKey, nil
}

// decryptPayload returns the payload for a decrypt request of the
// encryptedKey.
func decryptPayload(encryptedKey string) map[string]interface{} {
	return map[string]interface{}{
		"ciphertext": encryptedKey,
	}
}

// dataKeyFromSecret attempts to extract the data key from the data of the
// provided secret.
func dataKeyFromSecret(secret *api.Secret) ([]byte, error) {
	if secret == nil || secret.Data == nil {
		return nil, fmt.Errorf("transit backend is empty")
	}

View on GitHub (pinned to 13442bb981)

Solutions

  1. Inspect the raw Vault response (vault write -format=json transit/encrypt/<key> ...) and confirm data.ciphertext is a quoted string.
  2. Remove or fix any mock/stub returning ciphertext as a non-string; update it to return {"ciphertext":"vault:v1:..."}.
  3. If using a Vault-compatible server, upgrade to a version that matches the transit API response schema.
  4. Rule out response-rewriting proxies/ingress that change JSON types between Vault and the client.

Example fix

// before: mock returns wrong type
{"data":{"ciphertext":12345}}
// after: transit-compatible response
{"data":{"ciphertext":"vault:v1:8SDd3WHDO..."}}
Defensive patterns

Strategy: type-guard

Validate before calling

// Before trusting the field, narrow its type
raw, ok := secret.Data["ciphertext"]
ciphertext, isStr := raw.(string)
if !ok || !isStr {
    return fmt.Errorf("unexpected ciphertext type %T; transit response schema mismatch", raw)
}

Type guard

func asCiphertextString(secret *api.Secret) (string, bool) {
    if secret == nil || secret.Data == nil { return "", false }
    if v, ok := secret.Data["ciphertext"]; ok {
        if s, isStr := v.(string); isStr { return s, true }
    }
    return "", false
}

Try / catch

key, err := encryptedKeyFromSecret(secret)
if err != nil {
    if strings.Contains(err.Error(), "cannot be cast to string") {
        // log raw response type and fail with a clear schema-mismatch message
        return fmt.Errorf("vault transit response schema mismatch (ciphertext not a string): %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: EncryptContext (or TestMasterKey_Decrypt) receives an api.Secret whose Data["ciphertext"] holds a non-string type — e.g. a map or bool — because a proxy/mock returned malformed JSON, or a custom Vault-like service returned a differently typed field.

Common situations: Running against Vault dev-mode mocks or stubs in tests with wrong field types; third-partyVault-compatible servers (e.g. OpenBao forks) with divergent response encoding; HTTP middlewares mangling JSON types.

Related errors


AI-assisted analysis of getsops/sops@13442bb981 (2026-09-01). Data as JSON: /api/errors/3299f0459ef8e840. Report an issue: GitHub.