From f19bb31f6285c0874693325bdcfe69d9d5844e6b Mon Sep 17 00:00:00 2001 From: Vladimir Saraikin Date: Mon, 13 Jul 2026 19:47:46 +0200 Subject: [PATCH] fix: escape regex metacharacters in WildcardMatch A task whose name contains a regex metacharacter breaks task matching for the whole Taskfile. WildcardMatch built a pattern from the raw task name, so a task named "c++" panicked in MustCompile and a task named "a.b" matched "axb". Escape the name with QuoteMeta before turning "*" back into the wildcard group, and skip the regex entirely when the name has no wildcard. --- task_test.go | 10 ++++++++++ taskfile/ast/task.go | 26 +++++++++++++------------- testdata/wildcards/Taskfile.yml | 9 +++++++++ 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/task_test.go b/task_test.go index 540e7ba8de..c3ceb86769 100644 --- a/task_test.go +++ b/task_test.go @@ -3417,6 +3417,16 @@ func TestWildcard(t *testing.T) { call: "wildcard-foo-bar", expectedOutput: "Hello foo-bar\n", }, + { + name: "regex metacharacters are matched literally", + call: "c++", + expectedOutput: "Building c++\n", + }, + { + name: "a dot is not a wildcard", + call: "deploy-prod", + wantErr: true, + }, } for _, test := range tests { diff --git a/taskfile/ast/task.go b/taskfile/ast/task.go index 9465c77770..e3bb0cf08b 100644 --- a/taskfile/ast/task.go +++ b/taskfile/ast/task.go @@ -1,7 +1,6 @@ package ast import ( - "fmt" "regexp" "strings" @@ -87,23 +86,24 @@ func (t *Task) WildcardMatch(name string) (bool, []string) { names := append([]string{t.Task}, t.Aliases...) for _, taskName := range names { - regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(taskName, "*", "(.*)")) - regex := regexp.MustCompile(regexStr) - wildcards := regex.FindStringSubmatch(name) - - if len(wildcards) == 0 { + // Without a wildcard the name is a plain string, so skip building a regex + if !strings.Contains(taskName, "*") { + if taskName == name { + return true, nil + } continue } - // Remove the first match, which is the full string - wildcards = wildcards[1:] - wildcardCount := strings.Count(taskName, "*") + // Escape the task name so a name like "c++" or "a.b" is matched literally + // and does not panic in MustCompile, then turn the escaped "*" back into + // the wildcard group + pattern := strings.ReplaceAll(regexp.QuoteMeta(taskName), `\*`, "(.*)") + regex := regexp.MustCompile("^" + pattern + "$") + wildcards := regex.FindStringSubmatch(name) - if len(wildcards) != wildcardCount { - continue + if len(wildcards) > 1 { + return true, wildcards[1:] } - - return true, wildcards } return false, nil diff --git a/testdata/wildcards/Taskfile.yml b/testdata/wildcards/Taskfile.yml index 0ec2ae2895..e152876c01 100644 --- a/testdata/wildcards/Taskfile.yml +++ b/testdata/wildcards/Taskfile.yml @@ -25,3 +25,12 @@ tasks: SERVICE: "{{index .MATCH 0}}" cmds: - echo "Starting {{.SERVICE}}" + + # Regex metacharacters in a task name must be matched literally + c++: + cmds: + - echo "Building c++" + + deploy.prod: + cmds: + - echo "Deploying prod"