JuliusBrussee/caveman · error

envelope: tenant scope mismatch

Error message

envelope: tenant scope mismatch

What it means

OpenForScope computed the scope hash for the caller-supplied tenant/object Scope and it does not match the ScopeHash authenticated in the ciphertext metadata. The AAD binding proves the ciphertext was sealed for a different tenant or object — copying rows between tenants fails decryption by design, even with storage and KMS access.

Source

Thrown at shared/platform/envelope/envelope.go:134

// OpenForScope opens v2 ciphertext only for its authenticated scope. It also
// reads v1 ciphertext during migration; all new tenant-object writes use v2.
func OpenForScope(ciphertext []byte, metaJSON []byte, scope Scope) ([]byte, error) {
	var meta Metadata
	if err := json.Unmarshal(metaJSON, &meta); err != nil {
		return nil, fmt.Errorf("envelope: parse metadata: %w", err)
	}
	if meta.Scheme == schemeV1 {
		return open(ciphertext, meta, nil)
	}
	if meta.Scheme != schemeV2 {
		return nil, fmt.Errorf("envelope: unknown scheme %q", meta.Scheme)
	}
	aad, scopeHash, err := scopeAAD(scope)
	if err != nil {
		return nil, err
	}
	if meta.ScopeHash != scopeHash {
		return nil, fmt.Errorf("envelope: tenant scope mismatch")
	}
	return open(ciphertext, meta, aad)
}

func open(ciphertext []byte, meta Metadata, aad []byte) ([]byte, error) {
	wrapped, err := base64.StdEncoding.DecodeString(meta.WrappedDataKey)
	if err != nil {
		return nil, fmt.Errorf("envelope: decode wrapped key: %w", err)
	}
	dataKey, err := secretbox.DecryptPayloadKey(wrapped)
	if err != nil {
		return nil, fmt.Errorf("envelope: unwrap data key: %w", err)
	}
	block, err := aes.NewCipher(dataKey)
	if err != nil {
		return nil, fmt.Errorf("envelope: aes: %w", err)
	}
	gcm, err := cipher.NewGCM(block)

View on GitHub (pinned to 766dce6b13)

Solutions

  1. Pass the scope the ciphertext was originally sealed for (correct tenant/project/object)
  2. If objects legitimately move scopes, re-seal the plaintext under the new scope instead of copying ciphertext
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at shared/platform/envelope/envelope.go:134 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/c41d22b07042c9b9. Report an issue: GitHub.