{"record":{"id":"3299f0459ef8e840","repo":"getsops/sops","slug":"encrypted-ciphertext-cannot-be-cast-to-string","errorCode":null,"errorMessage":"encrypted ciphertext cannot be cast to string","messagePattern":"encrypted ciphertext cannot be cast to string","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hcvault/keysource.go","lineNumber":361,"sourceCode":"\tencoded := base64.StdEncoding.EncodeToString(dataKey)\n\treturn map[string]interface{}{\n\t\t\"plaintext\": encoded,\n\t}\n}\n\n// encryptedKeyFromSecret attempts to extract the encrypted key from the data\n// of the provided secret.\nfunc encryptedKeyFromSecret(secret *api.Secret) (string, error) {\n\tif secret == nil || secret.Data == nil {\n\t\treturn \"\", fmt.Errorf(\"transit backend is empty\")\n\t}\n\tencrypted, ok := secret.Data[\"ciphertext\"]\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"no encrypted data\")\n\t}\n\tencryptedKey, ok := encrypted.(string)\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"encrypted ciphertext cannot be cast to string\")\n\t}\n\treturn encryptedKey, nil\n}\n\n// decryptPayload returns the payload for a decrypt request of the\n// encryptedKey.\nfunc decryptPayload(encryptedKey string) map[string]interface{} {\n\treturn map[string]interface{}{\n\t\t\"ciphertext\": encryptedKey,\n\t}\n}\n\n// dataKeyFromSecret attempts to extract the data key from the data of the\n// provided secret.\nfunc dataKeyFromSecret(secret *api.Secret) ([]byte, error) {\n\tif secret == nil || secret.Data == nil {\n\t\treturn nil, fmt.Errorf(\"transit backend is empty\")\n\t}","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/getsops/sops/blob/13442bb98183887d7a9ac09ec8ab0564673a59d8/hcvault/keysource.go#L343-L379","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the raw Vault response (vault write -format=json transit/encrypt/<key> ...) and confirm data.ciphertext is a quoted string.","Remove or fix any mock/stub returning ciphertext as a non-string; update it to return {\"ciphertext\":\"vault:v1:...\"}.","If using a Vault-compatible server, upgrade to a version that matches the transit API response schema.","Rule out response-rewriting proxies/ingress that change JSON types between Vault and the client."],"exampleFix":"// before: mock returns wrong type\n{\"data\":{\"ciphertext\":12345}}\n// after: transit-compatible response\n{\"data\":{\"ciphertext\":\"vault:v1:8SDd3WHDO...\"}}","handlingStrategy":"type-guard","validationCode":"// Before trusting the field, narrow its type\nraw, ok := secret.Data[\"ciphertext\"]\nciphertext, isStr := raw.(string)\nif !ok || !isStr {\n    return fmt.Errorf(\"unexpected ciphertext type %T; transit response schema mismatch\", raw)\n}","typeGuard":"func asCiphertextString(secret *api.Secret) (string, bool) {\n    if secret == nil || secret.Data == nil { return \"\", false }\n    if v, ok := secret.Data[\"ciphertext\"]; ok {\n        if s, isStr := v.(string); isStr { return s, true }\n    }\n    return \"\", false\n}","tryCatchPattern":"key, err := encryptedKeyFromSecret(secret)\nif err != nil {\n    if strings.Contains(err.Error(), \"cannot be cast to string\") {\n        // log raw response type and fail with a clear schema-mismatch message\n        return fmt.Errorf(\"vault transit response schema mismatch (ciphertext not a string): %w\", err)\n    }\n    return err\n}","preventionTips":["Use the official hashicorp/vault api client instead of hand-rolled HTTP mocks","Keep test stubs' JSON schemas identical to real transit responses","Pin Vault-compatible server versions known to match the transit API","Log %T of unexpected fields to catch schema drift early"],"tags":["vault","transit","type-mismatch","go"],"backgroundTag":"vault-transit-response-type-mismatch","analyzedSha":"13442bb98183887d7a9ac09ec8ab0564673a59d8","analyzedAt":"2026-09-01T03:53:00.447Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}