From cd8bbcc2b0028301afca5436a26c3b90e1787125 Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Wed, 24 Jun 2026 22:25:48 +0100 Subject: [PATCH 1/3] mz817: integration test --- .../dockerfiles/Dockerfile_test_issue_mz817 | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 integration/dockerfiles/Dockerfile_test_issue_mz817 diff --git a/integration/dockerfiles/Dockerfile_test_issue_mz817 b/integration/dockerfiles/Dockerfile_test_issue_mz817 new file mode 100644 index 000000000..0360601be --- /dev/null +++ b/integration/dockerfiles/Dockerfile_test_issue_mz817 @@ -0,0 +1,19 @@ +# mz817: on v1.27.6 COPY heredoc does not expand build variables. +# Docker applies Dockerfile word-expansion to a COPY heredoc body when the +# delimiter is unquoted: ${VAR} expands and \${VAR} keeps the literal text with +# the backslash stripped. A quoted delimiter (<<'EOF') leaves the body verbatim. +# kaniko writes the body verbatim in every case, so the expanded files diverge. +FROM scratch + +ENV DO_EXPAND=expanded + +# unquoted delimiter expands ${DO_EXPAND}, keeps \${DO_EXPAND} literal +COPY < Date: Wed, 24 Jun 2026 22:32:39 +0100 Subject: [PATCH 2/3] mz817: expand variables in COPY/ADD heredoc content --- pkg/commands/copy.go | 11 ++++++++++- pkg/util/command_util.go | 10 ++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/pkg/commands/copy.go b/pkg/commands/copy.go index 6f871e49f..18fa041f3 100644 --- a/pkg/commands/copy.go +++ b/pkg/commands/copy.go @@ -157,7 +157,16 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu return nil } - srcFile := strings.NewReader(src.Data) + data := src.Data + if src.Expand { + expanded, err := util.ResolveEnvironmentReplacementRaw(src.Data, replacementEnvs) + if err != nil { + return fmt.Errorf("expanding heredoc content: %w", err) + } + data = expanded + } + + srcFile := strings.NewReader(data) err = util.CreateFile(destPath, srcFile, chmod.Apply(0o644), uint32(uid), uint32(gid)) if err != nil { return fmt.Errorf("creating file: %w", err) diff --git a/pkg/util/command_util.go b/pkg/util/command_util.go index 84157ff27..54ff0ba6c 100644 --- a/pkg/util/command_util.go +++ b/pkg/util/command_util.go @@ -97,6 +97,16 @@ func ResolveEnvironmentReplacement(value string, envs []string, isFilepath bool) return fp, nil } +// ResolveEnvironmentReplacementRaw resolves env variables in raw content such as a +// heredoc body. Unlike ResolveEnvironmentReplacement it leaves quotes intact +// (SkipProcessQuotes), matching how docker expands COPY/ADD heredoc contents. +func ResolveEnvironmentReplacementRaw(value string, envs []string) (string, error) { + shlex := shell.NewLex(parser.DefaultEscapeToken) + shlex.SkipProcessQuotes = true + fp, _, err := shlex.ProcessWord(value, shell.EnvsFromSlice(envs)) + return fp, err +} + func ResolveEnvAndWildcards(sd instructions.SourcesAndDest, fileContext FileContext, envs []string) ([]string, string, error) { // First, resolve any environment replacement resolvedEnvs, err := ResolveEnvironmentReplacementList(sd.SourcePaths, envs, true) From 737f5d67be7d74a7283b57ee5703e57922171dad Mon Sep 17 00:00:00 2001 From: Martin Zihlmann Date: Thu, 25 Jun 2026 11:25:38 +0100 Subject: [PATCH 3/3] mz817: gate COPY/ADD heredoc expansion behind FF_KANIKO_EXPAND_HEREDOC --- README.md | 8 ++++++++ integration/images.go | 1 + pkg/commands/copy.go | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b3b18867..daa64d8a7 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,7 @@ expect - see [Known Issues](#known-issues). - [Flag `FF_KANIKO_SECUREJOIN_EXTRACTION`](#flag-ff_kaniko_securejoin_extraction) - [Flag `FF_KANIKO_RESOLVE_CACHE_KEY`](#flag-ff_kaniko_resolve_cache_key) - [Flag `FF_KANIKO_UNTAR_SKIP_ROOT`](#flag-ff_kaniko_untar_skip_root) + - [Flag `FF_KANIKO_EXPAND_HEREDOC`](#flag-ff_kaniko_expand_heredoc) - [Assertion Overrides](#assertion-overrides) - [Debug Image](#debug-image) - [Security](#security) @@ -1305,6 +1306,13 @@ Set this flag to `true` to skip the root `.` entry when untarring. Defaults to `false`. Becomes default in `v1.29.0`. +#### Flag `FF_KANIKO_EXPAND_HEREDOC` + +Docker applies Dockerfile word-expansion to a `COPY` or `ADD` heredoc body when the delimiter is unquoted, so `${VAR}` expands and `\${VAR}` keeps the literal text. A quoted delimiter (`<<'EOF'`) leaves the body verbatim. kaniko writes the body verbatim in every case, so the expanded files diverge from Docker. +Set this flag to `true` to expand build args and env in unquoted `COPY` and `ADD` heredoc bodies. +Defaults to `false`. +Becomes default in `v1.29.0`. + ### Assertion Overrides Kaniko checks internal invariants at runtime. If one is violated the build stops with a message like: diff --git a/integration/images.go b/integration/images.go index d562468ee..e9d3d2593 100644 --- a/integration/images.go +++ b/integration/images.go @@ -112,6 +112,7 @@ var KanikoEnv = []string{ "FF_KANIKO_RESOLVE_CACHE_KEY=1", "FF_KANIKO_UNTAR_SKIP_ROOT=1", "FF_KANIKO_REPRODUCIBLE_PRESERVE_BASE_LAYERS=1", + "FF_KANIKO_EXPAND_HEREDOC=1", "KANIKO_PRINT_PLAN=1", } diff --git a/pkg/commands/copy.go b/pkg/commands/copy.go index 18fa041f3..f6dce67a4 100644 --- a/pkg/commands/copy.go +++ b/pkg/commands/copy.go @@ -158,7 +158,7 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu } data := src.Data - if src.Expand { + if src.Expand && kConfig.EnvBool("FF_KANIKO_EXPAND_HEREDOC") { expanded, err := util.ResolveEnvironmentReplacementRaw(src.Data, replacementEnvs) if err != nil { return fmt.Errorf("expanding heredoc content: %w", err)