From 3c2df0f506b19d6af80dc99c41bc4d6a4df1b145 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 6 Jul 2026 11:20:03 -0700 Subject: [PATCH] dockerfile: fix CopyIgnoredFile for context root Avoid treating COPY . . as copying an ignored file named "." when .dockerignore excludes dotfiles with patterns such as .*. Keep the warning for catch-all root patterns like *, where copying the context root would copy only ignored entries. Signed-off-by: Tonis Tiigi --- .../dockerfile/dockerfile2llb/validations.go | 21 +++++++++++ frontend/dockerfile/dockerfile_lint_test.go | 36 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/frontend/dockerfile/dockerfile2llb/validations.go b/frontend/dockerfile/dockerfile2llb/validations.go index d69c2ba8f..9a3523a7e 100644 --- a/frontend/dockerfile/dockerfile2llb/validations.go +++ b/frontend/dockerfile/dockerfile2llb/validations.go @@ -15,6 +15,7 @@ import ( "github.com/moby/buildkit/frontend/dockerfile/parser" "github.com/moby/buildkit/frontend/dockerfile/shell" "github.com/moby/buildkit/util/suggest" + "github.com/moby/patternmatcher" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) @@ -46,6 +47,13 @@ func validateCopySourcePath(src string, cfg *copyConfig) error { } src = filepath.ToSlash(filepath.Clean(src)) + if src == "." || src == "/" { + // "." and "/" are context roots, not real paths that can be excluded. + // Only keep the warning for patterns that exclude all root entries. + if !copySourceRootIgnored(cfg.ignoreMatcher) { + return nil + } + } ok, err := cfg.ignoreMatcher.MatchesOrParentMatches(src) if err != nil { return err @@ -58,6 +66,19 @@ func validateCopySourcePath(src string, cfg *copyConfig) error { return nil } +func copySourceRootIgnored(matcher *patternmatcher.PatternMatcher) bool { + for _, pattern := range matcher.Patterns() { + if pattern.Exclusion() { + continue + } + switch pattern.String() { + case "*", "**", "**/*": + return true + } + } + return false +} + func validateCircularDependency(states []*dispatchState) error { var visit func(*dispatchState, []instructions.Command) []instructions.Command if states == nil { diff --git a/frontend/dockerfile/dockerfile_lint_test.go b/frontend/dockerfile/dockerfile_lint_test.go index a9db3faed..48ba7c59b 100644 --- a/frontend/dockerfile/dockerfile_lint_test.go +++ b/frontend/dockerfile/dockerfile_lint_test.go @@ -48,6 +48,7 @@ var lintTests = integration.TestFuncs( testInvalidDefaultArgInFrom, testFromPlatformFlagConstDisallowed, testCopyIgnoredFiles, + testCopyIgnoredFileContextRoot, testDefinitionDescription, testExposeProtoCasing, testExposeInvalidFormat, @@ -242,6 +243,41 @@ COPY . . }) } +func testCopyIgnoredFileContextRoot(t *testing.T, sb integration.Sandbox) { + dockerfile := []byte(` +FROM scratch +COPY . . +`) + + t.Run("dotfiles", func(t *testing.T) { + checkLinterWarnings(t, sb, &lintTestParams{ + Dockerfile: dockerfile, + DockerIgnore: []byte(` +.* +`), + }) + }) + + t.Run("wildcard", func(t *testing.T) { + checkLinterWarnings(t, sb, &lintTestParams{ + Dockerfile: dockerfile, + DockerIgnore: []byte(` +* +`), + Warnings: []expectedLintWarning{ + { + RuleName: "CopyIgnoredFile", + Description: "Attempting to Copy file that is excluded by .dockerignore", + Detail: `Attempting to Copy file "." that is excluded by .dockerignore`, + URL: "https://docs.docker.com/go/dockerfile/rule/copy-ignored-file/", + Level: 1, + Line: 3, + }, + }, + }) + }) +} + func testSecretsUsedInArgOrEnv(t *testing.T, sb integration.Sandbox) { dockerfile := []byte(`# check=skip=InvalidDefinitionDescription