Skip to content

fix: escape regex metacharacters in WildcardMatch - #2916

Open
vsaraikin wants to merge 1 commit into
go-task:mainfrom
vsaraikin:fix/wildcard-match-escape
Open

fix: escape regex metacharacters in WildcardMatch#2916
vsaraikin wants to merge 1 commit into
go-task:mainfrom
vsaraikin:fix/wildcard-match-escape

Conversation

@vsaraikin

Copy link
Copy Markdown
Contributor

What

A task whose name contains a regex metacharacter breaks task matching for the whole Taskfile — either with a hard panic or a silent mis-match.

(*Task).WildcardMatch builds a regex from the task name, translating only *, and calls regexp.MustCompile on it:

regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(taskName, "*", "(.*)"))
regex := regexp.MustCompile(regexStr)

The raw task name is injected into the pattern, so any other metacharacter is interpreted as regex syntax:

  • Panic / crash — a task named c++ yields ^c++$, and regexp.MustCompile panics with invalid nested repetition operator: ++.
  • False positive — a task named a.b matches the call axb (the . acts as a wildcard). A realistic footgun: deploy.prod gets run by a mistyped task deploy-prod.

FindMatchingTasks calls WildcardMatch(call.Task) on every task whenever the requested name isn't a direct/alias match, so a single task with such a name breaks matching for the entire Taskfile.

Fix

Escape the task name with regexp.QuoteMeta before building the pattern, then turn the (now escaped) \* back into the wildcard group:

regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(regexp.QuoteMeta(taskName), `\*`, "(.*)"))

* remains the only wildcard; everything else is matched literally.

Testing

Added TestTaskWildcardMatch covering the existing build-* wildcard behavior plus the metacharacter cases (c++, a.b, deploy.prod). On the current code the test panics (invalid nested repetition operator); with the fix it passes. The full taskfile/ast package suite passes and the module builds clean.

WildcardMatch built a regex from the task name translating only "*", then
called regexp.MustCompile on it. A task name containing a regex metacharacter
either panicked (e.g. "c++" -> "invalid nested repetition operator") or
matched too loosely ("a.b" matched "axb"). Since FindMatchingTasks calls
WildcardMatch on every task, one such name breaks matching for the whole
Taskfile. Escape the name with regexp.QuoteMeta, keeping "*" as the only
wildcard.
@vmaerten

Copy link
Copy Markdown
Member

Did you read our contribution guide? https://taskfile.dev/docs/contributing#ai-usage-policy
Any AI usage should be disclosed.
The PR description and the PR itself both look AI-generated.
Your comment also looks AI-generated to me.
We want to communicate with humans, not bot.

@vsaraikin

Copy link
Copy Markdown
Contributor Author

Did you read our contribution guide? https://taskfile.dev/docs/contributing#ai-usage-policy Any AI usage should be disclosed. The PR description and the PR itself both look AI-generated. Your comment also looks AI-generated to me. We want to communicate with humans, not bot.

Fair point, sorry about that. Yeah I used an AI tool to clean up the writeup and I should've flagged the description – my bad. But the comments are mine.

@trulede trulede left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would request that you probably should write a normal Task test to capture the behaviour from end-2-end. Similar table driven approach, which is good. Then drop the unit test from the PR if you are happy with that.

This code (AI generated, but I suspected as much) shows a faster algorithm which I think should be considered in the PR.

func (t *Task) WildcardMatch(name string) (bool, []string) {
	names := append([]string{t.Task}, t.Aliases...)

	for _, taskName := range names {
		// First, quick check without regex if there are no wildcards
		if !strings.Contains(taskName, "*") {
			if taskName == name {
				return true, nil
			}
			continue
		}

		pattern := regexp.QuoteMeta(taskName)
		pattern = strings.ReplaceAll(pattern, `\*`, "(.*)")
		regex := regexp.MustCompile("^" + pattern + "$")
		wildcards := regex.FindStringSubmatch(name)
		if len(wildcards) > 1 {
			return true, wildcards[1:]
		}
	}

	return false, nil
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants