diff --git a/pkg/service/secretfiles_test.go b/pkg/service/secretfiles_test.go index 81fc74b1..ad07c600 100644 --- a/pkg/service/secretfiles_test.go +++ b/pkg/service/secretfiles_test.go @@ -2,6 +2,7 @@ package service import ( "os" + "runtime" "testing" "github.com/stretchr/testify/require" @@ -43,4 +44,42 @@ func TestResolveSecretFileInputs(t *testing.T) { require.Equal(t, "app-secret", resolved[0].Name) require.Equal(t, "top-secret", resolved[0].Content) }) + + t.Run("reads the file whose name ends in whitespace, not its trimmed neighbor", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Windows does not support these literal filenames") + } + dir := t.TempDir() + require.NoError(t, os.WriteFile(dir+"/neighbor.txt", []byte("NEIGHBOR"), 0o600)) + for _, suffix := range []string{" ", "\t", "\n"} { + require.NoError(t, os.WriteFile(dir+"/neighbor.txt"+suffix, []byte("INTENDED"+suffix), 0o600)) + resolved, err := ResolveSecretFileInputs([]string{"app-secret:" + dir + "/neighbor.txt" + suffix}) + require.NoError(t, err) + require.Equal(t, "INTENDED"+suffix, resolved[0].Content, "suffix %q", suffix) + } + }) + + t.Run("a missing whitespace-suffixed file is not replaced by its neighbor", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Windows does not support these literal filenames") + } + dir := t.TempDir() + require.NoError(t, os.WriteFile(dir+"/standalone.txt", []byte("NEIGHBOR"), 0o600)) + _, err := ResolveSecretFileInputs([]string{"app-secret:" + dir + "/standalone.txt "}) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to read --secret-file") + }) + + t.Run("keeps interior spaces and colons in the path", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Windows does not support these literal filenames") + } + dir := t.TempDir() + path := dir + "/a b:c.txt" + require.NoError(t, os.WriteFile(path, []byte("INTERIOR"), 0o600)) + resolved, err := ResolveSecretFileInputs([]string{" app-secret :" + path}) + require.NoError(t, err) + require.Equal(t, "app-secret", resolved[0].Name) + require.Equal(t, "INTERIOR", resolved[0].Content) + }) } diff --git a/pkg/types/service/secretfile.go b/pkg/types/service/secretfile.go index c27589bc..d7942705 100644 --- a/pkg/types/service/secretfile.go +++ b/pkg/types/service/secretfile.go @@ -17,9 +17,11 @@ func ParseSecretFileRef(raw string) (SecretFileRef, error) { return SecretFileRef{}, fmt.Errorf("invalid --secret-file %q: expected NAME:LOCAL_PATH", raw) } + // The name is formatting, so surrounding whitespace is dropped. The path + // names a file: whitespace there is part of the name the shell passed, and + // trimming it would read a different file that happens to exist. name = strings.TrimSpace(name) - localPath = strings.TrimSpace(localPath) - if name == "" || localPath == "" { + if name == "" || strings.TrimSpace(localPath) == "" { return SecretFileRef{}, fmt.Errorf("invalid --secret-file %q: expected NAME:LOCAL_PATH", raw) } diff --git a/pkg/types/service/secretfile_test.go b/pkg/types/service/secretfile_test.go index c8b92739..f58d5ba1 100644 --- a/pkg/types/service/secretfile_test.go +++ b/pkg/types/service/secretfile_test.go @@ -33,11 +33,17 @@ func TestParseSecretFileRef(t *testing.T) { require.Contains(t, err.Error(), "expected NAME:LOCAL_PATH") }) - t.Run("trims whitespace from name and path", func(t *testing.T) { - parsed, err := servicetypes.ParseSecretFileRef(" my-secret : /tmp/secret.txt ") + t.Run("trims whitespace from the name but keeps the path exact", func(t *testing.T) { + parsed, err := servicetypes.ParseSecretFileRef(" my-secret :/tmp/secret.txt \t\n") require.NoError(t, err) require.Equal(t, "my-secret", parsed.Name) - require.Equal(t, "/tmp/secret.txt", parsed.Path) + require.Equal(t, "/tmp/secret.txt \t\n", parsed.Path) + }) + + t.Run("rejects a whitespace-only path", func(t *testing.T) { + _, err := servicetypes.ParseSecretFileRef("name: \t") + require.Error(t, err) + require.Contains(t, err.Error(), "expected NAME:LOCAL_PATH") }) t.Run("handles multiple colons by using first as delimiter", func(t *testing.T) {