CVE-2026-61549: Woodpecker CI Kubernetes Backend Privilege Escalation via serviceAccountName
Any user with push access to a repository connected to Woodpecker's Kubernetes backend could set an arbitrary ServiceAccount on their pipeline pods, inheriting that account's full cluster RBAC permiss
The problem
Woodpecker's Kubernetes backend reads the `backend_options.kubernetes.serviceAccountName` field from a pipeline YAML file and passes it directly to the pod spec. There was no agent-side gate, so any repository contributor with push access could specify any ServiceAccount in the pipeline namespace.
If a privileged ServiceAccount existed in that namespace, the attacker's pipeline pod would receive its token at `/var/run/secrets/kubernetes.io/serviceaccount/token`. That token could then be used to read Kubernetes secrets, enumerate cluster resources, or escalate to cluster-admin.
Proof of concept
A working proof-of-concept for CVE-2026-61549 in go.woodpecker-ci.org/woodpecker/v3, with the exact payload below.
# .woodpecker.yml - push this to any repo connected to a vulnerable Woodpecker instance
steps:
- name: steal-token
image: alpine
commands:
- cat /var/run/secrets/kubernetes.io/serviceaccount/token
- env
backend_options:
kubernetes:
serviceAccountName: cluster-admin-sa # any privileged SA in the pipeline namespaceBefore v3.16.0, `podSpec()` in `pipeline/backend/kubernetes/pod.go` assigned `options.ServiceAccountName` to the pod spec unconditionally, with no check against the agent configuration. The fix in PR #6792 added a boolean gate (`ServiceAccountNameAllowFromStep`) to the agent config struct; the field is only forwarded to the pod spec when that flag is explicitly enabled by the operator.
The new default is deny. The controlling env var is `WOODPECKER_BACKEND_K8S_SERVICE_ACCOUNT_NAME_ALLOW_FROM_STEP`, which defaults to `false`. This mirrors the existing pattern already used for pod labels and annotations. CWE-269 (Improper Privilege Management) and CWE-862 (Missing Authorization) both apply: user-supplied data elevated cluster permissions with zero authorization check.
The fix
Upgrade to Woodpecker v3.16.0. After upgrading, `serviceAccountName` in step backend options is silently ignored unless the agent is explicitly started with `WOODPECKER_BACKEND_K8S_SERVICE_ACCOUNT_NAME_ALLOW_FROM_STEP=true`. Only set that flag if every user with push access to connected repositories is trusted.
As an immediate workaround on older versions: ensure no privileged ServiceAccount exists in the pipeline namespace, or enforce an admission policy (Kyverno, OPA/Gatekeeper, ValidatingAdmissionPolicy) that rejects pods with unexpected `serviceAccountName` values.
Related research
- high · 7.7nebula-mesh: Non-Admin SSRF via Unguarded allow_private Webhook Flag
- high · 7.5CVE-2026-54629CVE-2026-54629: Anyquery Local File Read via Unrestricted SQLite Virtual Table Modules
- high · 8.6CVE-2026-54628CVE-2026-54628: Anyquery SSRF via Unrestricted SQLite Virtual Table Modules in Server Mode
- critical · 9.1CVE-2026-50006CVE-2026-50006: Anyquery Arbitrary File Write via Unrestricted ATTACH DATABASE in Server Mode