diff --git a/pkg/app/load_opts.go b/pkg/app/load_opts.go index d108acbc..4a9772ef 100644 --- a/pkg/app/load_opts.go +++ b/pkg/app/load_opts.go @@ -30,7 +30,17 @@ func (o LoadOpts) DeepCopy() LoadOpts { panic(err) } - new.Environment.OverrideCLISetValues = o.Environment.OverrideCLISetValues + if src := o.Environment.OverrideCLISetValues; src != nil { + b, err := yaml.Marshal(src) + if err != nil { + panic(err) + } + var dst []any + if err := yaml.Unmarshal(b, &dst); err != nil { + panic(err) + } + new.Environment.OverrideCLISetValues = dst + } return new } diff --git a/pkg/app/load_opts_test.go b/pkg/app/load_opts_test.go index c7dd4f9d..a4eea50b 100644 --- a/pkg/app/load_opts_test.go +++ b/pkg/app/load_opts_test.go @@ -34,3 +34,20 @@ func TestLoadOptsDeepCopyPreservesOverrideCLISetValues(t *testing.T) { require.Equal(t, lOld.Environment.OverrideCLISetValues, lNew.Environment.OverrideCLISetValues, "DeepCopy should preserve OverrideCLISetValues field") } + +// TestLoadOptsDeepCopyOverrideCLISetValuesIsNotShallow verifies that mutating a +// map nested inside OverrideCLISetValues on the copy does not affect the +// original. +func TestLoadOptsDeepCopyOverrideCLISetValuesIsNotShallow(t *testing.T) { + lOld := LoadOpts{} + lOld.Environment.OverrideCLISetValues = []any{map[string]any{"key": "original"}} + + lNew := lOld.DeepCopy() + + // Mutate the map inside the copy. + lNew.Environment.OverrideCLISetValues[0].(map[string]any)["key"] = "mutated" + + // The original must be unaffected; this fails with a shallow copy. + require.Equal(t, "original", lOld.Environment.OverrideCLISetValues[0].(map[string]any)["key"], + "mutating the copy's OverrideCLISetValues map must not affect the original (aliasing bug)") +}