diff --git a/plugins/pass/store/store.go b/plugins/pass/store/store.go index 2d0bf9cd..ee2bc9cc 100644 --- a/plugins/pass/store/store.go +++ b/plugins/pass/store/store.go @@ -37,7 +37,9 @@ func (m *PassValue) Marshal() ([]byte, error) { } func (m *PassValue) Unmarshal(data []byte) error { - m.value = data + // Copy: store backends zero the source buffer after Unmarshal returns + // (see store/keychain and store/posixage), so we must not alias it. + m.value = append([]byte(nil), data...) return nil } diff --git a/plugins/pass/store/store_test.go b/plugins/pass/store/store_test.go new file mode 100644 index 00000000..4c53c217 --- /dev/null +++ b/plugins/pass/store/store_test.go @@ -0,0 +1,36 @@ +// Copyright 2025-2026 Docker, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package store + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPassValueUnmarshalCopies guards the contract that store backends zero +// the source buffer after Unmarshal returns. PassValue must own its bytes. +func TestPassValueUnmarshalCopies(t *testing.T) { + src := []byte("hunter2") + pv := &PassValue{} + require.NoError(t, pv.Unmarshal(src)) + + clear(src) + + out, err := pv.Marshal() + require.NoError(t, err) + assert.Equal(t, []byte("hunter2"), out) +} diff --git a/store/store.go b/store/store.go index bafca00b..28c4b640 100644 --- a/store/store.go +++ b/store/store.go @@ -59,7 +59,9 @@ var ( type Secret interface { // Marshal the secret into a slice of bytes Marshal() ([]byte, error) - // Unmarshal the secret from a slice of bytes into its structured format + // Unmarshal the secret from a slice of bytes into its structured format. + // Implementations must copy any bytes they retain: store backends zero + // the source buffer after Unmarshal returns to avoid leaking plaintext. Unmarshal(data []byte) error // Metadata returns a key-value pair of non-sensitive data about the secret Metadata() map[string]string