getsops/sops · error

no decrypted data

Error message

no decrypted data

What it means

This error is returned by dataKeyFromSecret when the secret and its Data map exist, but Data contains no "plaintext" key. The library throws it because a successful transit decrypt response must include base64 plaintext; without it the data key cannot be recovered in DecryptContext.

Source

Thrown at hcvault/keysource.go:382

}

// 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")
	}
	decrypted, ok := secret.Data["plaintext"]
	if !ok {
		return nil, fmt.Errorf("no decrypted data")
	}
	plaintext, ok := decrypted.(string)
	if !ok {
		return nil, fmt.Errorf("decrypted plaintext data cannot be cast to string")
	}
	dataKey, err := base64.StdEncoding.DecodeString(plaintext)
	if err != nil {
		return nil, fmt.Errorf("cannot decode base64 plaintext into data key bytes")
	}
	return dataKey, nil
}

// vaultClient returns a new Vault client, configured with the given address
// and token.
func vaultClient(address, token string, hc *http.Client) (*api.Client, error) {
	cfg := api.DefaultConfig()
	cfg.Address = address

View on GitHub (pinned to 13442bb981)

Solutions

  1. Verify the decrypt uses the same transit mount and key that produced the ciphertext (key names are not interchangeable across mounts).
  2. Test manually: `vault write transit/decrypt/<key> ciphertext=<blob>` and confirm the response contains data.plaintext.
  3. Check the token's policy for update on transit/decrypt/<key> and that no response wrapping/filtering removes the field.
  4. Ensure the encrypted blob format is a valid transit ciphertext (vault:v1:...) and not, e.g., an AWS/GCP ciphertext pasted by mistake.

Example fix

// before: decrypting a blob made by another key
keyservice: hcvault://vault.example.com:8200/transit/keys/otherkey
// after: use the original key that encrypted the data
keyservice: hcvault://vault.example.com:8200/transit/keys/mykey
Defensive patterns

Strategy: validation

Validate before calling

// Verify the key exists and can decrypt before relying on it
resp, err := client.Logical().Write("transit/decrypt/"+keyName, map[string]interface{}{"ciphertext": blob})
if err != nil { log.Fatal(err) }
if resp == nil || resp.Data == nil {
    log.Fatal("decrypt response empty; check key name and mount")
}
if _, ok := resp.Data["plaintext"]; !ok {
    log.Fatal("decrypt response missing plaintext; ciphertext may be from another key")
}

Type guard

func hasPlaintext(secret *api.Secret) bool {
    if secret == nil || secret.Data == nil { return false }
    v, ok := secret.Data["plaintext"]
    if !ok { return false }
    _, isStr := v.(string)
    return isStr
}

Prevention

When it happens

Trigger: Calling DecryptContext (or TestMasterKey_Encrypt) when the Vault transit decrypt response's Data lacks "plaintext" — e.g. ciphertext decrypted against the wrong key/mount, batch responses, or a policy-filtered response.

Common situations: Ciphertext created by a different Vault key or environment than the one decrypting; ACLs that strip the plaintext field; using a KV read instead of the transit decrypt endpoint; Vault-compatible servers with different response keys.

Related errors


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