From 35d9a74ef18c7308e83244cccd6b032d455227a3 Mon Sep 17 00:00:00 2001 From: Hermes Date: Sun, 16 Aug 2026 23:00:56 +0500 Subject: [PATCH] =?UTF-8?q?fix:=20=D1=80=D0=B0=D0=B7=D1=80=D0=B5=D1=88?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=20own?= =?UTF-8?q?er/repo=20=D0=B2=20=D0=B8=D0=BC=D0=B5=D0=BD=D0=B8=20=D1=80?= =?UTF-8?q?=D0=B5=D0=BF=D0=BE=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D1=8F=20?= =?UTF-8?q?(E3=20=D0=B1=D1=8B=D0=BB=20=D1=81=D0=BB=D0=B8=D1=88=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=20=D1=81=D1=82=D1=80=D0=BE=D0=B3,=20=D1=81=D0=BB=D1=8D?= =?UTF-8?q?=D1=88=20=E2=89=A0=20=D1=8D=D1=81=D0=BA=D0=B5=D0=B9=D0=BF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/worker/worker.go | 19 ++++++++++++++++++- internal/worker/worker_test.go | 4 ++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/internal/worker/worker.go b/internal/worker/worker.go index 360632f..d945a42 100644 --- a/internal/worker/worker.go +++ b/internal/worker/worker.go @@ -370,6 +370,15 @@ func (w *Worker) clone(ctx context.Context, repo string) error { url := buildCloneURL(base, repo) dst := filepath.Join(w.Worktree, repo) + // Для формата "owner/repo" git clone не создаёт промежуточный каталог {worktree}/owner + // до самого репозитория — поэтому создаём родителя явно. Worktree в целом гарантирован + // app.New, но вложенные сегменты owner здесь обязательны. + if parent := filepath.Dir(dst); parent != "." && parent != w.Worktree { + if err := os.MkdirAll(parent, 0o755); err != nil { + return fmt.Errorf("%w: создать %s: %v", ErrClone, parent, err) + } + } + args := []string{"clone"} if w.GitToken != "" { // https-базовый URL: кладём токен внутрь URL (для приватных репозиториев). @@ -391,12 +400,20 @@ func buildCloneURL(base, repo string) string { } // validateRepoName отклоняет имена с пути-эскейпом (E3). +// Допускается формат "owner/repo" (один слэш) — клон ляжет в {worktree}/owner/repo, +// промежуточный каталог создаёт clone(). Путь с сегментами "." , ".." , пустыми или +// абсолютный — ошибка. func validateRepoName(repo string) error { if repo == "" { return fmt.Errorf("%w: пустое имя", ErrRepoPathHint) } - if strings.Contains(repo, "/") || strings.Contains(repo, "..") { + if filepath.IsAbs(repo) { return fmt.Errorf("%w: %q", ErrRepoPathHint, repo) } + for _, seg := range strings.Split(repo, "/") { + if seg == "" || seg == "." || seg == ".." { + return fmt.Errorf("%w: %q", ErrRepoPathHint, repo) + } + } return nil } \ No newline at end of file diff --git a/internal/worker/worker_test.go b/internal/worker/worker_test.go index 368202e..6e48aee 100644 --- a/internal/worker/worker_test.go +++ b/internal/worker/worker_test.go @@ -416,14 +416,14 @@ func TestWorkerPromptRendered(t *testing.T) { } func TestValidateRepoName(t *testing.T) { - valid := []string{"calc", "proj-a", "my.repo", "node_2"} + valid := []string{"calc", "proj-a", "my.repo", "node_2", "kamelion/ratatoskr-go", "a/b/c"} for _, r := range valid { if err := validateRepoName(r); err != nil { t.Errorf("validateRepoName(%q) = %v, want nil", r, err) } } - invalid := []string{"", "../etc", "a/b", "a/../b", ".."} + invalid := []string{"", "../etc", "etc/..", "a/../b", "..", ".", "/etc", "a//b", "./x"} for _, r := range invalid { if err := validateRepoName(r); err == nil { t.Errorf("validateRepoName(%q) = nil, want E3", r)