GitHub Security Advisory: GHSA-94jr-7pqp-xhcq
Package: github.com/tektoncd/pipeline (GO)
Severity: HIGH (CVSS 7.5)
CWE: CWE-88: Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')
Vulnerable versions: >= 1.0.0, <= 1.11.0
Patched in: 1.11.1

Summary: Tekton Pipeline: Git Resolver Unsanitized Revision Parameter Enables git Argument Injection Leading to RCE

Details:
## Summary

The git resolver's `revision` parameter is passed directly as a positional argument to `git fetch` without any validation that it does not begin with a `-` character. Because git parses flags from mixed positional arguments, an attacker can inject arbitrary `git fetch` flags such as `--upload-pack=<binary>`. Combined with the `validateRepoURL` function explicitly permitting URLs that begin with `/` (local filesystem paths), a tenant who can submit `ResolutionRequest` objects can chain these two behaviors to execute an arbitrary binary on the resolver pod. The `tekton-pipelines-resolvers` ServiceAccount holds cluster-wide `get/list/watch` on all Secrets, so code execution on the resolver pod enables full cluster-wide secret exfiltration.

## Details

### Root Cause 1 — Unvalidated `revision` parameter passed to `git fetch`

`pkg/resolution/resolver/git/repository.go:85`:

```go
// pkg/resolution/resolver/git/repository.go lines 84-96
// 'revision' is the raw user-supplied string from the ResolutionRequest param.
// It is passed verbatim as a positional argument to git fetch:
func (repo *repository) checkout(ctx context.Context, revision string) error {
    _, err := repo.execGit(ctx, "fetch", "origin", revision, "--depth=1")
    // When revision == "--upload-pack=/usr/bin/curl", git parses it as the
    // --upload-pack flag, not as a refspec — executing the binary locally.
    if err != nil {
        return fmt.Errorf("fetch: %w", err)
    }
    _, err = repo.execGit(ctx, "checkout", "FETCH_HEAD")
    return err
}
```

`execGit` invokes `exec.CommandContext("git", ...)` — no shell is used, so shell metacharacters cannot be injected. However, git itself parses flags from mixed positional arguments. When `revision = "--upload-pack=/path/to/binary"`, git receives this as the flag `--upload-pack=/path/to/binary`, not as a refspec. `PopulateDefaultParams` (`resolver.go:418–424`) applies only a leading-slash strip and a `containsDotDot` check on the `pathInRepo` parameter; the `revision` parameter receives no validation at all.

### Root Cause 2 — `validateRepoURL` explicitly permits local filesystem paths

`pkg/resolution/resolver/git/resolver.go:154-158`:

```go
// validateRepoURL validates if the given URL is a valid git, http, https URL or
// starting with a / (a local repository).
func validateRepoURL(url string) bool {
    pattern := `^(/|[^@]+@[^:]+|(git|https?)://)`
    re := regexp.MustCompile(pattern)
    return re.MatchString(url)
}
```

Any URL beginning with `/` passes validation and is used directly as the argument to `git clone`. This means a local filesystem path such as `/tmp/some-repo` is a valid resolver URL.

### Exploit Chain

`--upload-pack=<binary>` causes git to execute the specified binary as the upload-pack server when communicating with the remote. For local-path remotes (`/path`), git invokes the binary on the resolver pod itself with the repository path as its sole argument. Because the argument is passed via `e