Found during the 2026-08 FerrVault operator audit.
Problem
internal/controller/ferrvaultsecret_sync.go:49-67 — the CreateOrUpdate mutate sets secret.StringData = data; secret.Data = nil. The API server never returns StringData (it's write-only, folded into Data), so the live object always has Data != nil, StringData == nil while the mutated object has the reverse. equality.Semantic.DeepEqual therefore never matches and CreateOrUpdate issues an Update on every reconcile even when content is unchanged.
Because the controller Owns(&corev1.Secret{}) (ferrvaultsecret_controller.go:210), each self-write re-enqueues the owner — a second independent driver of the hot loop (see the GenerationChangedPredicate issue), plus constant resourceVersion churn on every managed Secret.
Fix
Write secret.Data (raw []byte) instead of StringData so the round-tripped object is byte-stable and CreateOrUpdate becomes a no-op when nothing changed. Alternatively short-circuit when the stored content-hash annotation already equals the new hash.
Found during the 2026-08 FerrVault operator audit.
Problem
internal/controller/ferrvaultsecret_sync.go:49-67— theCreateOrUpdatemutate setssecret.StringData = data; secret.Data = nil. The API server never returnsStringData(it's write-only, folded intoData), so the live object always hasData != nil, StringData == nilwhile the mutated object has the reverse.equality.Semantic.DeepEqualtherefore never matches andCreateOrUpdateissues anUpdateon every reconcile even when content is unchanged.Because the controller
Owns(&corev1.Secret{})(ferrvaultsecret_controller.go:210), each self-write re-enqueues the owner — a second independent driver of the hot loop (see the GenerationChangedPredicate issue), plus constantresourceVersionchurn on every managed Secret.Fix
Write
secret.Data(raw[]byte) instead ofStringDataso the round-tripped object is byte-stable andCreateOrUpdatebecomes a no-op when nothing changed. Alternatively short-circuit when the storedcontent-hashannotation already equals the new hash.