From 96d9f491db57a2a49e405de85443622145c820d8 Mon Sep 17 00:00:00 2001 From: bircni Date: Fri, 31 Jul 2026 15:11:44 +0000 Subject: [PATCH] fix: strip leading slash from mkdir tarball in CopyTarStream (#1129) `CopyTarStream` creates the destination directory by extracting a one-entry tarball with `DestinationPath: "/"`, but named that entry with the absolute `destPath`. Docker Engine 29.5+ tightened path validation on the copy API and rejects absolute entry names against a `/` destination with `statat var/run/act/actions/: path escapes from parent`, so the action directory never reached the job container and `actions/checkout` failed during "Set up job". Stripping the leading slash makes the entry relative, matching what the sibling `copyDir` already does and the upstream fix in nektos/act v0.2.89. Adds a regression test asserting the mkdir tarball entry is relative. Fixes #1128 Reviewed-on: https://gitea.com/gitea/runner/pulls/1129 Reviewed-by: Zettat123 <39446+zettat123@noreply.gitea.com> --- act/container/docker_run.go | 5 ++-- act/container/docker_run_test.go | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/act/container/docker_run.go b/act/container/docker_run.go index 502d4828..f3a279c8 100644 --- a/act/container/docker_run.go +++ b/act/container/docker_run.go @@ -868,11 +868,12 @@ func (cr *containerReference) CopyTarStream(ctx context.Context, destPath string if cr.id == "" { return cr.missingContainerError("copy to %s", destPath) } - // Mkdir + // Mkdir, with a path relative to the DestinationPath ("/") below. Docker 29.5+ + // rejects absolute tar entry names with "path escapes from parent". buf := &bytes.Buffer{} tw := tar.NewWriter(buf) _ = tw.WriteHeader(&tar.Header{ - Name: destPath, + Name: strings.TrimPrefix(destPath, "/"), Mode: 0o777, Typeflag: tar.TypeDir, }) diff --git a/act/container/docker_run_test.go b/act/container/docker_run_test.go index bc556509..43ea19fb 100644 --- a/act/container/docker_run_test.go +++ b/act/container/docker_run_test.go @@ -5,6 +5,7 @@ package container import ( + "archive/tar" "bufio" "bytes" "context" @@ -358,6 +359,44 @@ func TestDockerCopyTarStream(t *testing.T) { client.AssertExpectations(t) } +// Docker 29.5+ rejects absolute names in the mkdir tarball with +// "path escapes from parent", since it is extracted relative to "/". +func TestDockerCopyTarStreamMkdirEntryIsRelative(t *testing.T) { + ctx := context.Background() + + var mkdirNames []string + client := &mockDockerClient{} + client.On("CopyToContainer", ctx, "123", mock.MatchedBy(func(opts mobyclient.CopyToContainerOptions) bool { + if opts.DestinationPath != "/" || opts.Content == nil { + return false + } + tr := tar.NewReader(opts.Content) + for { + hdr, err := tr.Next() + if err != nil { + break + } + mkdirNames = append(mkdirNames, hdr.Name) + } + return true + })).Return(mobyclient.CopyToContainerResult{}, nil) + client.On("CopyToContainer", ctx, "123", mock.MatchedBy(func(opts mobyclient.CopyToContainerOptions) bool { + return opts.DestinationPath == "/var/run/act" && opts.Content != nil + })).Return(mobyclient.CopyToContainerResult{}, nil) + cr := &containerReference{ + id: "123", + cli: client, + input: &NewContainerInput{ + Image: "image", + }, + } + + require.NoError(t, cr.CopyTarStream(ctx, "/var/run/act", &bytes.Buffer{})) + assert.Equal(t, []string{"var/run/act"}, mkdirNames) + + client.AssertExpectations(t) +} + func TestDockerCopyTarStreamErrorInCopyFiles(t *testing.T) { ctx := context.Background()