Insecure Variable Substitution in Vela
Description
Vela CI/CD vulnerable to secret exposure via variable substitution in insensitive fields, bypassing log masking even with 'no commands' restriction.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Vela CI/CD vulnerable to secret exposure via variable substitution in insensitive fields, bypassing log masking even with 'no commands' restriction.
Vulnerability
Description Vela, a CI/CD framework written in Go, allows variable substitution in fields such as parameters, image, and entrypoint. This feature can be misused to inject secrets into a plugin or image. By employing common substitution string manipulation, an attacker can bypass log masking and expose secrets without using the commands block [1]. The issue primarily affects secrets restricted by the 'no commands' option, rendering that restriction ineffective [1][3].
Exploitation
Method To exploit this vulnerability, a pipeline author must supply secrets to a plugin that is designed to print those parameters in logs. Plugin parameters are not intended for sensitive values and are often printed for informational or debugging purposes [3]. Examples include using variable substitution in the parameters field, image tag, or entrypoint (e.g., steps: - name: example image: secrets: [ example_secret ] parameters: example: $${EXAMPLE_SECRET}) [3].
Impact
Successful exploitation can lead to unintended use of the secret value and increased risk of exposure during image execution. The log masking provided by Vela is not sufficient to prevent exposure when secrets are injected via substitution [1]. Users who rely on the 'no commands' option and image restriction may have a false sense of security, as these can be bypassed [1].
Mitigation
The issue has been addressed in Vela version 0.23.2 [2]. Users are advised to upgrade. As a workaround, users should not provide sensitive values to plugins that can potentially expose them, especially in parameters not intended for sensitive data, and ensure plugins follow best practices to avoid logging sensitive information [1]. The fix involves separating secrets that allow substitution from those that do not, as seen in the commit that introduces separate maps for secrets with and without substitution permission [2].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/go-vela/workerGo | < 0.23.2 | 0.23.2 |
Affected products
2- go-vela/workerv5Range: < 0.23.2
Patches
1e1572743b008Merge pull request from GHSA-pwx5-6wxg-px5h
7 files changed · +61 −19
executor/linux/build.go+20 −4 modified@@ -199,8 +199,12 @@ func (c *client) PlanBuild(ctx context.Context) error { _log.AppendData(append(sRaw, "\n"...)) - // add secret to the map - c.Secrets[secret.Name] = s + // add secret to the appropriate map + if s.GetAllowSubstitution() { + c.Secrets[secret.Name] = s + } else { + c.NoSubSecrets[secret.Name] = s + } } // escape newlines in secrets loaded on build_start @@ -699,6 +703,7 @@ func loadLazySecrets(c *client, _step *pipeline.Container) error { _log := new(library.Log) lazySecrets := make(map[string]*library.Secret) + lazyNoSubSecrets := make(map[string]*library.Secret) // this requires a small preface and brief description on // how normal secrets make it into a container: @@ -797,8 +802,12 @@ func loadLazySecrets(c *client, _step *pipeline.Container) error { return err } - // add secret to the temp map - lazySecrets[secret.Name] = s + // add secret to the appropriate temp map + if s.GetAllowSubstitution() { + lazySecrets[secret.Name] = s + } else { + lazyNoSubSecrets[secret.Name] = s + } } } @@ -836,6 +845,13 @@ func loadLazySecrets(c *client, _step *pipeline.Container) error { return err } + c.Logger.Debug("injecting no-sub lazy loaded secrets") + // inject secrets for container + err = injectSecrets(tmpStep, lazyNoSubSecrets) + if err != nil { + return err + } + c.Logger.Debug("merge lazy loaded secrets into container") // merge lazy load secrets into original container err = _step.MergeEnv(tmpStep.Environment)
executor/linux/linux.go+11 −6 modified@@ -19,12 +19,13 @@ type ( // client manages communication with the pipeline resources. client struct { // https://pkg.go.dev/github.com/sirupsen/logrus#Entry - Logger *logrus.Entry - Vela *vela.Client - Runtime runtime.Engine - Secrets map[string]*library.Secret - Hostname string - Version string + Logger *logrus.Entry + Vela *vela.Client + Runtime runtime.Engine + Secrets map[string]*library.Secret + NoSubSecrets map[string]*library.Secret + Hostname string + Version string // clients for build actions secret *secretSvc @@ -67,6 +68,7 @@ func Equal(a, b *client) bool { reflect.DeepEqual(a.Vela, b.Vela) && reflect.DeepEqual(a.Runtime, b.Runtime) && reflect.DeepEqual(a.Secrets, b.Secrets) && + reflect.DeepEqual(a.NoSubSecrets, b.NoSubSecrets) && a.Hostname == b.Hostname && a.Version == b.Version && reflect.DeepEqual(a.init, b.init) && @@ -118,6 +120,9 @@ func New(opts ...Opt) (*client, error) { // instantiate map for non-plugin secrets c.Secrets = make(map[string]*library.Secret) + // instantiate map for non-substituted secrets + c.NoSubSecrets = make(map[string]*library.Secret) + // instantiate all client services c.secret = &secretSvc{client: c}
executor/linux/secret.go+7 −0 modified@@ -67,6 +67,13 @@ func (s *secretSvc) create(ctx context.Context, ctn *pipeline.Container) error { return fmt.Errorf("unable to substitute container configuration") } + logger.Debug("injecting non-substituted secrets") + // inject no-substitution secrets for container + err = injectSecrets(ctn, s.client.NoSubSecrets) + if err != nil { + return err + } + return nil }
executor/linux/service.go+7 −0 modified@@ -55,6 +55,13 @@ func (c *client) CreateService(ctx context.Context, ctn *pipeline.Container) err return fmt.Errorf("unable to substitute container configuration") } + logger.Debug("injecting non-substituted secrets") + // inject no-substitution secrets for container + err = injectSecrets(ctn, c.NoSubSecrets) + if err != nil { + return err + } + return nil }
executor/linux/step.go+7 −0 modified@@ -65,6 +65,13 @@ func (c *client) CreateStep(ctx context.Context, ctn *pipeline.Container) error return fmt.Errorf("unable to substitute container configuration") } + logger.Debug("injecting non-substituted secrets") + // inject no-substitution secrets for container + err = injectSecrets(ctn, c.NoSubSecrets) + if err != nil { + return err + } + return nil }
go.mod+3 −3 modified@@ -8,9 +8,9 @@ require ( github.com/docker/docker v24.0.9+incompatible github.com/docker/go-units v0.5.0 github.com/gin-gonic/gin v1.9.1 - github.com/go-vela/sdk-go v0.23.1 - github.com/go-vela/server v0.23.1 - github.com/go-vela/types v0.23.1 + github.com/go-vela/sdk-go v0.23.2-0.20240312184917-e3a34719badf + github.com/go-vela/server v0.23.2-0.20240312184244-a645c822da1d + github.com/go-vela/types v0.23.2-0.20240312183632-2e046fceb8fe github.com/golang-jwt/jwt/v5 v5.2.0 github.com/google/go-cmp v0.6.0 github.com/joho/godotenv v1.5.1
go.sum+6 −6 modified@@ -94,12 +94,12 @@ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/go-vela/sdk-go v0.23.1 h1:4KxfAF1vR8DvtRraBoWQDIm8f8zxXP806lJR3MmTlC8= -github.com/go-vela/sdk-go v0.23.1/go.mod h1:zDsZIePtBdpCZwmwAWqGWuIch/oGliX1zd51PARTHBk= -github.com/go-vela/server v0.23.1 h1:Y+mGfB79RjIgQ3IEkPjGB6IneB2So3ZXE4XKY+Z02xc= -github.com/go-vela/server v0.23.1/go.mod h1:B+A5lRPOlAVYyXBMGCAJKhPQOlfJuWl3qaRcvhsUqSA= -github.com/go-vela/types v0.23.1 h1:st4BeDcYVyaaFqblU1YroztNvmYLBgmfZpWq0En0Sg0= -github.com/go-vela/types v0.23.1/go.mod h1:AAqgxIw1aRBgPkE/5juGuiwh/JZuOtL8fcPaEkjFWwQ= +github.com/go-vela/sdk-go v0.23.2-0.20240312184917-e3a34719badf h1:8Oka4tMHOdy/DsInyg7c/XPY5wqWWE7Yvzx/u67WBuw= +github.com/go-vela/sdk-go v0.23.2-0.20240312184917-e3a34719badf/go.mod h1:XjrVfIDw2SZDFBtJ5vqVse/GFj89MF542N20P8U5a3I= +github.com/go-vela/server v0.23.2-0.20240312184244-a645c822da1d h1:VynpkAIMt3KTh9BaICQdpu6c76/hHU3d4/Ab44bmFew= +github.com/go-vela/server v0.23.2-0.20240312184244-a645c822da1d/go.mod h1:EsDVTqQHQ9snXG2DhUl9uo4+Cf/b9nMiESCkxSjmP90= +github.com/go-vela/types v0.23.2-0.20240312183632-2e046fceb8fe h1:Fb28yre0nrX1GNeyPN8i8rruTlW8MnPVF3Fo5xTuOkg= +github.com/go-vela/types v0.23.2-0.20240312183632-2e046fceb8fe/go.mod h1:AAqgxIw1aRBgPkE/5juGuiwh/JZuOtL8fcPaEkjFWwQ= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-pwx5-6wxg-px5hghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-28236ghsaADVISORY
- github.com/go-vela/worker/commit/e1572743b008e4fbce31ebb1dcd23bf6a1a30297ghsax_refsource_MISCWEB
- github.com/go-vela/worker/security/advisories/GHSA-pwx5-6wxg-px5hghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.