From 860a61d6e8d541f4dbcba805f205750d7f98588a Mon Sep 17 00:00:00 2001 From: Arian Boukani Date: Fri, 25 Sep 2026 05:03:29 -0400 Subject: [PATCH] cli/context/store: don't treat .DS_Store files as TLS material Opening a context's TLS directory in Finder can create a ".DS_Store" file there. After that, every command that talks to the daemon through the context prints a warning: unknown file in context my-context TLS bundle: .DS_Store The file also shows up in "docker context inspect", and is included by "docker context export". None of the TLS files the CLI writes ("ca.pem", "cert.pem", "key.pem") are hidden, so skip hidden files when listing a context's TLS files. Besides ".DS_Store", this covers the "._*" AppleDouble files macOS creates on drives that can't store extended attributes (such as FAT-formatted USB drives), and temporary files left behind if writing a TLS file is interrupted. Windows Explorer's "Thumbs.db" and "desktop.ini" are skipped too, ignoring case. Other unexpected files, such as a certificate saved as "ca.crt", still produce a warning. Signed-off-by: Arian Boukani --- cli/context/store/store.go | 3 ++- cli/context/store/store_test.go | 5 +++++ cli/context/store/tlsstore.go | 13 ++++++++++++- cli/context/store/tlsstore_test.go | 31 ++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/cli/context/store/store.go b/cli/context/store/store.go index 4122e077927f..b36648ece7a6 100644 --- a/cli/context/store/store.go +++ b/cli/context/store/store.go @@ -192,7 +192,8 @@ func (s *ContextStore) ResetEndpointTLSMaterial(contextName string, endpointName } // ListTLSFiles returns the list of TLS files present for each endpoint in the -// context. +// context. Hidden files (such as ".DS_Store"), and the "Thumbs.db" and +// "desktop.ini" files created by Windows Explorer, are skipped. func (s *ContextStore) ListTLSFiles(name string) (map[string]EndpointFiles, error) { return s.tls.listContextData(name) } diff --git a/cli/context/store/store_test.go b/cli/context/store/store_test.go index 25bfd87c75c0..bec878d06245 100644 --- a/cli/context/store/store_test.go +++ b/cli/context/store/store_test.go @@ -57,6 +57,9 @@ func TestExportImport(t *testing.T) { }, }) assert.NilError(t, err) + // A .DS_Store left by Finder must not end up in the export. + err = os.WriteFile(filepath.Join(s.tls.endpointDir("source", "ep1"), ".DS_Store"), []byte("data"), 0o600) + assert.NilError(t, err) r := Export("source", s) defer r.Close() err = Import("dest", s, r) @@ -87,6 +90,8 @@ func TestExportImport(t *testing.T) { destData2, err := s.GetTLSData("dest", "ep1", "file2") assert.NilError(t, err) assert.DeepEqual(t, file2, destData2) + _, err = s.GetTLSData("dest", "ep1", ".DS_Store") + assert.Check(t, is.ErrorType(err, errdefs.IsNotFound)) } func TestRemove(t *testing.T) { diff --git a/cli/context/store/tlsstore.go b/cli/context/store/tlsstore.go index c1f5f8caa2cb..e956db820f23 100644 --- a/cli/context/store/tlsstore.go +++ b/cli/context/store/tlsstore.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/moby/sys/atomicwriter" ) @@ -81,7 +82,7 @@ func (s *tlsStore) listContextData(name string) (map[string]EndpointFiles, error } var files EndpointFiles for _, fs := range fss { - if !fs.IsDir() { + if !fs.IsDir() && !isIgnoredFile(fs.Name()) { files = append(files, fs.Name()) } } @@ -91,5 +92,15 @@ func (s *tlsStore) listContextData(name string) (map[string]EndpointFiles, error return r, nil } +// isIgnoredFile reports whether name should be skipped when listing TLS files. +// TLS files are never hidden, so all hidden files are skipped: ".DS_Store", +// "._*" AppleDouble files, and temporary files left by an interrupted write. +// Windows Explorer's "Thumbs.db" and "desktop.ini" are matched ignoring case. +func isIgnoredFile(name string) bool { + return strings.HasPrefix(name, ".") || + strings.EqualFold(name, "Thumbs.db") || + strings.EqualFold(name, "desktop.ini") +} + // EndpointFiles is a slice of strings representing file names type EndpointFiles []string diff --git a/cli/context/store/tlsstore_test.go b/cli/context/store/tlsstore_test.go index 799362d54e14..38d0707f14ca 100644 --- a/cli/context/store/tlsstore_test.go +++ b/cli/context/store/tlsstore_test.go @@ -1,6 +1,8 @@ package store import ( + "os" + "path/filepath" "testing" "github.com/containerd/errdefs" @@ -52,6 +54,11 @@ func TestTlsListAndBatchRemove(t *testing.T) { err := testee.createOrUpdate(contextName, name, file, []byte("data")) assert.NilError(t, err) } + // Files like these are created by Finder and Explorer, and must not be listed. + for _, file := range []string{".DS_Store", "Thumbs.db"} { + err := os.WriteFile(filepath.Join(testee.endpointDir(contextName, name), file), []byte("data"), 0o600) + assert.NilError(t, err) + } } resAll, err := testee.listContextData(contextName) @@ -70,3 +77,27 @@ func TestTlsListAndBatchRemove(t *testing.T) { assert.NilError(t, err) assert.DeepEqual(t, resEmpty, map[string]EndpointFiles{}) } + +func TestIsIgnoredFile(t *testing.T) { + tests := []struct { + fileName string + ignored bool + }{ + {fileName: "ca.pem"}, + {fileName: "cert.pem"}, + {fileName: "key.pem"}, + {fileName: "ca.crt"}, // unknown files must still produce a warning + {fileName: ".DS_Store", ignored: true}, + {fileName: "._ca.pem", ignored: true}, // AppleDouble file + {fileName: ".tmp-ca.pem1234567890", ignored: true}, // left behind by an interrupted write + {fileName: "Thumbs.db", ignored: true}, + {fileName: "thumbs.db", ignored: true}, + {fileName: "desktop.ini", ignored: true}, + {fileName: "Desktop.ini", ignored: true}, + } + for _, tc := range tests { + t.Run(tc.fileName, func(t *testing.T) { + assert.Equal(t, isIgnoredFile(tc.fileName), tc.ignored) + }) + } +}