← research

1SEAL-2026-003: path traversal in Tekton Pipelines git resolver — from tenant to cluster-wide secret access

critical
CVSS 9.6 CWE-22 tektoncd/pipeline CVE-2026-33211 GHSA-j5q5-j9gm-2w5c 2026-03-18

summary

a path traversal vulnerability in the Tekton Pipelines git resolver allows a namespace-scoped tenant to read arbitrary files from the resolver pod's filesystem, including its ServiceAccount token. in the default RBAC configuration, this token grants get, list, and watch on Secrets cluster-wide — escalating from a single namespace to full cluster secret access.

the vulnerability exists because the pathInRepo parameter is passed directly to os.ReadFile() without validation. the initial vendor patch was found to be bypassable via symlinks committed inside attacker-controlled repositories; the final fix includes both path validation and filepath.EvalSymlinks() containment.

severity

CRITICAL — CVSS 3.1 Base Score: 9.6

Vector: AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N

CWE: CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)

affected versions

all releases of Tekton Pipelines from v1.0.0 through v1.10.0, including all patch releases (v1.3.1, v1.3.2, v1.9.1).

releases prior to v1.0.0 (e.g., v0.70.0 and earlier) are not affected — they used the go-git library's in-memory filesystem (memfs) where path traversal cannot escape the git worktree.

patched versions

branchpatched release
v1.0.xv1.0.1
v1.3.xv1.3.3
v1.6.xv1.6.1
v1.9.xv1.9.2
v1.10.xv1.10.2

root cause

the git resolver's getFileContent() function in pkg/resolution/resolver/git/repository.go constructs a file path by joining the repository clone directory with the user-supplied pathInRepo parameter:

fileContents, err := os.ReadFile(filepath.Join(repo.directory, path))

the pathInRepo parameter is accepted from the user without any validation. an attacker can supply ../../../../etc/passwd or any other traversal sequence to escape the cloned repository directory and read arbitrary files from the resolver pod's real filesystem.

the vulnerability was introduced in commit 318006c4e3a5 ("fix: resolve Git Anonymous Resolver excessive memory usage"), which switched the git resolver from the go-git library — using an in-memory filesystem that cannot be escaped — to shelling out to the git binary and reading files with os.ReadFile() from the real filesystem.

a TODO comment in the source code acknowledged that validation was never implemented:

// TODO(sbwsg): validate pathInRepo is valid relative pathInRepo

exploitation chain

the attack proceeds in two steps, each requiring only the ability to create a TaskRun, PipelineRun, or ResolutionRequest in a single namespace.

step 1 — arbitrary file read

the attacker creates a ResolutionRequest with a traversal path:

spec:
  params:
    - name: url
      value: https://github.com/tektoncd/catalog
    - name: revision
      value: main
    - name: pathInRepo
      value: "../../../../proc/self/mountinfo"

the resolver clones the repository, constructs the path filepath.Join(cloneDir, "../../../../proc/self/mountinfo"), and reads the file. the contents are returned base64-encoded in resolutionrequest.status.data. this reveals container overlay paths, host disk layout, and internal mount structure.

step 2 — ServiceAccount token exfiltration

the attacker targets the well-known token path:

    - name: pathInRepo
      value: "../../../../var/run/secrets/kubernetes.io/serviceaccount/token"

a valid JWT is returned for system:serviceaccount:tekton-pipelines-resolvers:tekton-pipelines-resolvers. in the default RBAC configuration, this ServiceAccount has the following permissions:

resourceverbs
secretsget, list, watch
serviceaccountsget, list, watch
resolutionrequestsget, list, watch, update, patch
tasks.tekton.devget, list
pipelines.tekton.devget, list

the stolen token grants cluster-wide read access to all Secrets across all namespaces — a full privilege escalation from namespace-scoped tenant to cluster-wide secret access.

symlink bypass in initial patch

the vendor prepared an initial fix that validated pathInRepo by rejecting .. components and absolute paths, then compared filepath.Abs(repo/path) against the repository root before calling os.ReadFile().

during review of this patch, 1seal identified that the fix was bypassable via symlinks committed inside an attacker-controlled repository:

the containment check uses filepath.Abs(), which is a lexical operation — it does not resolve symlinks. however, os.ReadFile() does follow symlinks. an attacker can commit a symlink inside their repository:

link -> /var/run/secrets/kubernetes.io/serviceaccount/token

then request pathInRepo=link. the lexical check sees the path as being inside the clone directory. os.ReadFile() follows the symlink and reads the target outside the directory. the containment is bypassed.

this was confirmed with a unit test that failed with: symlink escape still readable: "secret-token-value".

the final fix adds filepath.EvalSymlinks() to resolve the real path before the containment check, and includes regression tests for symlink escapes, .. traversal, and absolute paths.

fix

fixed in Tekton Pipelines releases: v1.0.1, v1.3.3, v1.6.1, v1.9.2, v1.10.2. the fix:

  1. validates pathInRepo in PopulateDefaultParams() to reject paths containing .. components and absolute paths at parameter validation time
  2. adds a containment check in getFileContent() using filepath.EvalSymlinks() to resolve the real path and verify it remains within repo.directory (defense in depth)
  3. includes regression tests for .. traversal, absolute paths, and symlink-based escapes from attacker-controlled repositories
branchcommit
main961388fcf337
v1.10.xcdb4e1e97a4f
v1.9.x3ca7bc6e6dd1
v1.6.xb1fee65b88aa
v1.3.x10fa538f9a2b
v1.0.xec7755031a18

users should update to the latest patch release on their branch immediately.

timeline

date event
2026-03-02 reported via GitHub Security Advisory (GHSA-j5q5-j9gm-2w5c)
2026-03-11 vulnerability confirmed and reproduced by Tekton maintainer
2026-03-11 initial patch prepared in private fork
2026-03-11 1seal review identifies symlink bypass in initial patch
2026-03-11 maintainer acknowledges bypass, prepares revised fix with EvalSymlinks
2026-03-12 final fix with symlink containment merged in private fork
2026-03-18 patched releases published (v1.0.1, v1.3.3, v1.6.1, v1.9.2, v1.10.2)
2026-03-18 GHSA-j5q5-j9gm-2w5c published

credit

discovered by Oleh Konko (1seal).

additionally credited: @KoreaSecurity (Finder, per GHSA).

symlink bypass in initial patch identified during fix review by Oleh Konko (1seal).

fix developed by Vincent Demeester (@vdemeester, Tekton maintainer). fix reviewed by Andrea Frittoli (@afrittoli, Tekton maintainer).

references