CVE-2025-27616
Description
Vela is a Pipeline Automation (CI/CD) framework built on Linux container technology written in Golang. Prior to versions 0.25.3 and 0.26.3, by spoofing a webhook payload with a specific set of headers and body data, an attacker could transfer ownership of a repository and its repo level secrets to a separate repository. These secrets could be exfiltrated by follow up builds to the repository. Users with an enabled repository with access to repo level CI secrets in Vela are vulnerable to the exploit, and any user with access to the CI instance and the linked source control manager can perform the exploit. Versions 0.25.3 and 0.26.3 fix the issue. No known workarounds are available.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/go-vela/serverGo | < 0.25.3 | 0.25.3 |
github.com/go-vela/serverGo | >= 0.26.0, < 0.26.3 | 0.26.3 |
Patches
367c1892e2464fix(patch): add v26.3 fixes to v25.3 patch branch (#1266)
3 files changed · +95 −76
api/repo/repair.go+1 −1 modified@@ -156,7 +156,7 @@ func RepairRepo(c *gin.Context) { sourceRepo.SetPreviousName(r.GetName()) } - r, err = wh.RenameRepository(ctx, h, sourceRepo, c, m) + r, err = wh.RenameRepository(ctx, l, database.FromContext(c), h, sourceRepo, r, m) if err != nil { util.HandleError(c, http.StatusInternalServerError, err) return
api/webhook/post.go+79 −75 modified@@ -146,9 +146,72 @@ func PostWebhook(c *gin.Context) { l.Debugf("hook generated from SCM: %v", h) l.Debugf("repo generated from SCM: %v", r) + // check if build was parsed from webhook. + if b == nil && h.GetEvent() != constants.EventRepository { + // typically, this should only happen on a webhook + // "ping" which gets sent when the webhook is created + c.JSON(http.StatusOK, "no build to process") + + return + } + + // check if repo was parsed from webhook + if r == nil { + retErr := fmt.Errorf("%s: failed to parse repo from webhook", baseErr) + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + var repo *types.Repo + + if h.GetEvent() == constants.EventRepository && (h.GetEventAction() == constants.ActionRenamed || h.GetEventAction() == constants.ActionTransferred) { + // get any matching hook with the repo's unique webhook ID in the SCM + hook, err := database.FromContext(c).GetHookByWebhookID(ctx, h.GetWebhookID()) + if err != nil { + retErr := fmt.Errorf("%s: failed to get hook by webhook id for %s: %w", baseErr, r.GetFullName(), err) + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + // get the repo from the database using repo id of matching hook + repo, err = database.FromContext(c).GetRepo(ctx, hook.GetRepo().GetID()) + if err != nil { + retErr := fmt.Errorf("%s: failed to get repo by id: %w", baseErr, err) + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + } else { + repo, err = database.FromContext(c).GetRepoForOrg(ctx, r.GetOrg(), r.GetName()) + if err != nil { + retErr := fmt.Errorf("%s: failed to get repo %s: %w", baseErr, r.GetFullName(), err) + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + } + + // verify the webhook from the source control provider using DB repo hash + if c.Value("webhookvalidation").(bool) { + l.WithFields(logrus.Fields{ + "org": r.GetOrg(), + "repo": r.GetName(), + }).Tracef("verifying GitHub webhook for %s", r.GetFullName()) + + err = scm.FromContext(c).VerifyWebhook(ctx, dupRequest, repo) + if err != nil { + retErr := fmt.Errorf("unable to verify webhook: %w", err) + util.HandleError(c, http.StatusUnauthorized, retErr) + + return + } + } + // if event is repository event, handle separately and return if strings.EqualFold(h.GetEvent(), constants.EventRepository) { - r, err = handleRepositoryEvent(ctx, c, m, h, r) + r, err = handleRepositoryEvent(ctx, l, database.FromContext(c), m, h, r, repo) if err != nil { util.HandleError(c, http.StatusInternalServerError, err) return @@ -165,29 +228,12 @@ func PostWebhook(c *gin.Context) { return } - // check if build was parsed from webhook. - if b == nil { - // typically, this should only happen on a webhook - // "ping" which gets sent when the webhook is created - c.JSON(http.StatusOK, "no build to process") - - return - } - l.Debugf(`build author: %s, build branch: %s, build commit: %s, build ref: %s`, b.GetAuthor(), b.GetBranch(), b.GetCommit(), b.GetRef()) - // check if repo was parsed from webhook - if r == nil { - retErr := fmt.Errorf("%s: failed to parse repo from webhook", baseErr) - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - defer func() { // send API call to update the webhook _, err = database.FromContext(c).UpdateHook(ctx, h) @@ -204,18 +250,6 @@ func PostWebhook(c *gin.Context) { }).Info("hook updated") }() - // send API call to capture parsed repo from webhook - repo, err := database.FromContext(c).GetRepoForOrg(ctx, r.GetOrg(), r.GetName()) - if err != nil { - retErr := fmt.Errorf("%s: failed to get repo %s: %w", baseErr, r.GetFullName(), err) - util.HandleError(c, http.StatusBadRequest, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } - // attach a sender SCM id if the webhook payload from the SCM has no sender id // the code in ProcessWebhook implies that the sender may not always be present // fallbacks like pusher/commit_author do not have an id @@ -319,20 +353,6 @@ func PostWebhook(c *gin.Context) { "repo": repo.GetName(), }).Info("hook created") - // verify the webhook from the source control provider - if c.Value("webhookvalidation").(bool) { - err = scm.FromContext(c).VerifyWebhook(ctx, dupRequest, repo) - if err != nil { - retErr := fmt.Errorf("unable to verify webhook: %w", err) - util.HandleError(c, http.StatusUnauthorized, retErr) - - h.SetStatus(constants.StatusFailure) - h.SetError(retErr.Error()) - - return - } - } - // check if the repo is active if !repo.GetActive() { retErr := fmt.Errorf("%s: %s is not an active repo", baseErr, repo.GetFullName()) @@ -618,9 +638,7 @@ func PostWebhook(c *gin.Context) { // the database resources with any relevant changes resulting from the event, such as name changes, transfers, etc. // // the caller is responsible for returning errors to the client. -func handleRepositoryEvent(ctx context.Context, c *gin.Context, m *internal.Metadata, h *types.Hook, r *types.Repo) (*types.Repo, error) { - l := c.MustGet("logger").(*logrus.Entry) - +func handleRepositoryEvent(ctx context.Context, l *logrus.Entry, db database.Interface, m *internal.Metadata, h *types.Hook, r *types.Repo, dbRepo *types.Repo) (*types.Repo, error) { l = l.WithFields(logrus.Fields{ "event_type": h.GetEvent(), }) @@ -629,7 +647,7 @@ func handleRepositoryEvent(ctx context.Context, c *gin.Context, m *internal.Meta defer func() { // send API call to update the webhook - hr, err := database.FromContext(c).CreateHook(ctx, h) + hr, err := db.CreateHook(ctx, h) if err != nil { l.Errorf("unable to create webhook %s/%d: %v", r.GetFullName(), h.GetNumber(), err) } @@ -646,7 +664,7 @@ func handleRepositoryEvent(ctx context.Context, c *gin.Context, m *internal.Meta switch h.GetEventAction() { // if action is renamed or transferred, go through rename routine case constants.ActionRenamed, constants.ActionTransferred: - r, err := RenameRepository(ctx, h, r, c, m) + r, err := RenameRepository(ctx, l, db, h, r, dbRepo, m) if err != nil { h.SetStatus(constants.StatusFailure) h.SetError(err.Error()) @@ -659,7 +677,7 @@ func handleRepositoryEvent(ctx context.Context, c *gin.Context, m *internal.Meta case "archived", "unarchived", constants.ActionEdited: l.Debugf("repository action %s for %s", h.GetEventAction(), r.GetFullName()) // send call to get repository from database - dbRepo, err := database.FromContext(c).GetRepoForOrg(ctx, r.GetOrg(), r.GetName()) + dbRepo, err := db.GetRepoForOrg(ctx, r.GetOrg(), r.GetName()) if err != nil { retErr := fmt.Errorf("%s: failed to get repo %s: %w", baseErr, r.GetFullName(), err) @@ -670,7 +688,7 @@ func handleRepositoryEvent(ctx context.Context, c *gin.Context, m *internal.Meta } // send API call to capture the last hook for the repo - lastHook, err := database.FromContext(c).LastHookForRepo(ctx, dbRepo) + lastHook, err := db.LastHookForRepo(ctx, dbRepo) if err != nil { retErr := fmt.Errorf("unable to get last hook for repo %s: %w", r.GetFullName(), err) @@ -703,7 +721,7 @@ func handleRepositoryEvent(ctx context.Context, c *gin.Context, m *internal.Meta } // update repo object in the database after applying edits - dbRepo, err = database.FromContext(c).UpdateRepo(ctx, dbRepo) + dbRepo, err = db.UpdateRepo(ctx, dbRepo) if err != nil { retErr := fmt.Errorf("%s: failed to update repo %s: %w", baseErr, r.GetFullName(), err) @@ -732,32 +750,18 @@ func handleRepositoryEvent(ctx context.Context, c *gin.Context, m *internal.Meta // associated with that repo as well as build links for the UI. // // the caller is responsible for returning errors to the client. -func RenameRepository(ctx context.Context, h *types.Hook, r *types.Repo, c *gin.Context, m *internal.Metadata) (*types.Repo, error) { - l := c.MustGet("logger").(*logrus.Entry) - +func RenameRepository(ctx context.Context, l *logrus.Entry, db database.Interface, h *types.Hook, r *types.Repo, dbR *types.Repo, m *internal.Metadata) (*types.Repo, error) { l = l.WithFields(logrus.Fields{ "event_type": h.GetEvent(), }) l.Debugf("renaming repository from %s to %s", r.GetPreviousName(), r.GetName()) - // get any matching hook with the repo's unique webhook ID in the SCM - hook, err := database.FromContext(c).GetHookByWebhookID(ctx, h.GetWebhookID()) - if err != nil { - return nil, fmt.Errorf("%s: failed to get hook with webhook ID %d from database", baseErr, h.GetWebhookID()) - } - - // get the repo from the database using repo id of matching hook - dbR, err := database.FromContext(c).GetRepo(ctx, hook.GetRepo().GetID()) - if err != nil { - return nil, fmt.Errorf("%s: failed to get repo %d from database", baseErr, hook.GetRepo().GetID()) - } - // update hook object which will be added to DB upon reaching deferred function in PostWebhook h.SetRepo(r) // send API call to capture the last hook for the repo - lastHook, err := database.FromContext(c).LastHookForRepo(ctx, dbR) + lastHook, err := db.LastHookForRepo(ctx, dbR) if err != nil { retErr := fmt.Errorf("unable to get last hook for repo %s: %w", r.GetFullName(), err) @@ -775,7 +779,7 @@ func RenameRepository(ctx context.Context, h *types.Hook, r *types.Repo, c *gin. } // get total number of secrets associated with repository - t, err := database.FromContext(c).CountSecretsForRepo(ctx, dbR, map[string]interface{}{}) + t, err := db.CountSecretsForRepo(ctx, dbR, map[string]interface{}{}) if err != nil { return nil, fmt.Errorf("unable to get secret count for repo %s/%s: %w", dbR.GetOrg(), dbR.GetName(), err) } @@ -784,7 +788,7 @@ func RenameRepository(ctx context.Context, h *types.Hook, r *types.Repo, c *gin. page := 1 // capture all secrets belonging to certain repo in database for repoSecrets := int64(0); repoSecrets < t; repoSecrets += 100 { - s, _, err := database.FromContext(c).ListSecretsForRepo(ctx, dbR, map[string]interface{}{}, page, 100) + s, _, err := db.ListSecretsForRepo(ctx, dbR, map[string]interface{}{}, page, 100) if err != nil { return nil, fmt.Errorf("unable to get secret list for repo %s/%s: %w", dbR.GetOrg(), dbR.GetName(), err) } @@ -799,7 +803,7 @@ func RenameRepository(ctx context.Context, h *types.Hook, r *types.Repo, c *gin. secret.SetOrg(r.GetOrg()) secret.SetRepo(r.GetName()) - _, err = database.FromContext(c).UpdateSecret(ctx, secret) + _, err = db.UpdateSecret(ctx, secret) if err != nil { return nil, fmt.Errorf("unable to update secret for repo %s/%s: %w", dbR.GetOrg(), dbR.GetName(), err) } @@ -812,7 +816,7 @@ func RenameRepository(ctx context.Context, h *types.Hook, r *types.Repo, c *gin. } // get total number of builds associated with repository - t, err = database.FromContext(c).CountBuildsForRepo(ctx, dbR, nil) + t, err = db.CountBuildsForRepo(ctx, dbR, nil) if err != nil { return nil, fmt.Errorf("unable to get build count for repo %s: %w", dbR.GetFullName(), err) } @@ -821,7 +825,7 @@ func RenameRepository(ctx context.Context, h *types.Hook, r *types.Repo, c *gin. page = 1 // capture all builds belonging to repo in database for build := int64(0); build < t; build += 100 { - b, _, err := database.FromContext(c).ListBuildsForRepo(ctx, dbR, nil, time.Now().Unix(), 0, page, 100) + b, _, err := db.ListBuildsForRepo(ctx, dbR, nil, time.Now().Unix(), 0, page, 100) if err != nil { return nil, fmt.Errorf("unable to get build list for repo %s: %w", dbR.GetFullName(), err) } @@ -837,7 +841,7 @@ func RenameRepository(ctx context.Context, h *types.Hook, r *types.Repo, c *gin. fmt.Sprintf("%s/%s/%d", m.Vela.WebAddress, r.GetFullName(), build.GetNumber()), ) - _, err = database.FromContext(c).UpdateBuild(ctx, build) + _, err = db.UpdateBuild(ctx, build) if err != nil { return nil, fmt.Errorf("unable to update build for repo %s: %w", dbR.GetFullName(), err) } @@ -860,7 +864,7 @@ func RenameRepository(ctx context.Context, h *types.Hook, r *types.Repo, c *gin. dbR.SetPreviousName(r.GetPreviousName()) // update the repo in the database - dbR, err = database.FromContext(c).UpdateRepo(ctx, dbR) + dbR, err = db.UpdateRepo(ctx, dbR) if err != nil { retErr := fmt.Errorf("%s: failed to update repo %s/%s", baseErr, dbR.GetOrg(), dbR.GetName())
scm/github/webhook.go+15 −0 modified@@ -139,6 +139,9 @@ func (c *client) processPushEvent(ctx context.Context, h *api.Hook, payload *git }).Tracef("processing push GitHub webhook for %s", payload.GetRepo().GetFullName()) repo := payload.GetRepo() + if repo == nil { + return &internal.Webhook{Hook: h}, nil + } // convert payload to library repo r := new(api.Repo) @@ -268,6 +271,9 @@ func (c *client) processPREvent(h *api.Hook, payload *github.PullRequestEvent) ( // capture the repo from the payload repo := payload.GetRepo() + if repo == nil { + return &internal.Webhook{Hook: h}, nil + } // convert payload to library repo r := new(api.Repo) @@ -355,6 +361,9 @@ func (c *client) processDeploymentEvent(h *api.Hook, payload *github.DeploymentE // capture the repo from the payload repo := payload.GetRepo() + if repo == nil { + return &internal.Webhook{Hook: h}, nil + } // convert payload to library repo r := new(api.Repo) @@ -473,6 +482,9 @@ func (c *client) processIssueCommentEvent(h *api.Hook, payload *github.IssueComm // capture the repo from the payload repo := payload.GetRepo() + if repo == nil { + return &internal.Webhook{Hook: h}, nil + } // convert payload to library repo r := new(api.Repo) @@ -516,6 +528,9 @@ func (c *client) processRepositoryEvent(h *api.Hook, payload *github.RepositoryE logrus.Tracef("processing repository event GitHub webhook for %s", payload.GetRepo().GetFullName()) repo := payload.GetRepo() + if repo == nil { + return &internal.Webhook{Hook: h}, nil + } // convert payload to library repo r := new(api.Repo)
257886e5a3eefix: support list of cors origins (#1262)
5 files changed · +125 −40
cmd/vela-server/main.go+5 −0 modified@@ -75,6 +75,11 @@ func main() { Usage: "web ui oauth callback path", Value: "/account/authenticate", }, + &cli.StringSliceFlag{ + EnvVars: []string{"VELA_CORS_ALLOW_ORIGINS", "VELA_CORS_ALLOWED_ORIGINS"}, + Name: "cors-allow-origins", + Usage: "list of origins a cross-domain request can be executed from", + }, &cli.StringFlag{ EnvVars: []string{"VELA_SECRET"}, Name: "vela-secret",
cmd/vela-server/metadata.go+4 −0 modified@@ -109,6 +109,10 @@ func metadataVela(c *cli.Context) (*internal.Vela, error) { vela.WebAddress = c.String("webui-addr") } + if len(c.StringSlice("cors-allow-origins")) > 0 { + vela.CorsAllowOrigins = c.StringSlice("cors-allow-origins") + } + if len(c.String("webui-oauth-callback")) > 0 { vela.WebOauthCallbackPath = c.String("webui-oauth-callback") }
internal/metadata.go+1 −0 modified@@ -32,6 +32,7 @@ type ( AccessTokenDuration time.Duration `json:"access_token_duration"` RefreshTokenDuration time.Duration `json:"refresh_token_duration"` OpenIDIssuer string `json:"oidc_issuer"` + CorsAllowOrigins []string `json:"cors_allow_origins"` } // Metadata is the extra set of data passed to the compiler for
router/middleware/header.go+22 −4 modified@@ -32,8 +32,9 @@ func Options(c *gin.Context) { } else { c.Header("Access-Control-Allow-Origin", "*") - if len(m.Vela.WebAddress) > 0 { - c.Header("Access-Control-Allow-Origin", m.Vela.WebAddress) + origin := CorsAllowOrigin(c, m) + if len(origin) > 0 { + c.Header("Access-Control-Allow-Origin", origin) c.Header("Access-Control-Allow-Credentials", "true") } @@ -65,14 +66,31 @@ func Cors(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") - if len(m.Vela.WebAddress) > 0 { - c.Header("Access-Control-Allow-Origin", m.Vela.WebAddress) + origin := CorsAllowOrigin(c, m) + if len(origin) > 0 { + c.Header("Access-Control-Allow-Origin", origin) c.Header("Access-Control-Allow-Credentials", "true") } c.Header("Access-Control-Expose-Headers", "link, x-total-count") } +// CorsAllowOrigin is a helper function that returns the +// allowed origin for CORS requests by checking the +// request origin against the allowed origins in the +// Vela metadata. +func CorsAllowOrigin(c *gin.Context, m *internal.Metadata) string { + origin := c.Request.Header.Get("Origin") + for _, domain := range m.Vela.CorsAllowOrigins { + if domain == origin { + return domain + } + } + + // return the Vela web address as the default to preserve functionality + return m.Vela.WebAddress +} + // RequestVersion is a middleware function that injects the Vela API version // information into the request so it will be logged. This is // intended for debugging and troubleshooting.
router/middleware/header_test.go+93 −36 modified@@ -176,45 +176,102 @@ func TestMiddleware_Options_InvalidMethod(t *testing.T) { } func TestMiddleware_Cors(t *testing.T) { - // setup types - wantOrigin := "*" - wantExposeHeaders := "link, x-total-count" - m := &internal.Metadata{ - Vela: &internal.Vela{ - Address: "http://localhost:8080", + tests := []struct { + name string + m *internal.Metadata + origin string + expectedOrigin string + expectedCredentials string + expectedExposeHeaders string + }{ + { + name: "*", + m: &internal.Metadata{ + Vela: &internal.Vela{ + Address: "http://localhost:8080", + CorsAllowOrigins: []string{}, + }, + }, + origin: "http://localhost:8888", + expectedOrigin: "*", + expectedCredentials: "", + expectedExposeHeaders: "link, x-total-count", + }, + { + name: "WebAddress is origin", + m: &internal.Metadata{ + Vela: &internal.Vela{ + WebAddress: "http://localhost:8888", + CorsAllowOrigins: []string{}, + }, + }, + origin: "http://localhost:8888", + expectedOrigin: "http://localhost:8888", + expectedCredentials: "true", + expectedExposeHeaders: "link, x-total-count", + }, + { + name: "CORSAllowOrigins origin is web address", + m: &internal.Metadata{ + Vela: &internal.Vela{ + WebAddress: "http://localhost:8888", + CorsAllowOrigins: []string{"http://localhost:3000", "http://localhost:3001"}, + }, + }, + origin: "http://localhost:8888", + expectedOrigin: "http://localhost:8888", + expectedCredentials: "true", + expectedExposeHeaders: "link, x-total-count", + }, + { + name: "CORSAllowOrigins origin is in list", + m: &internal.Metadata{ + Vela: &internal.Vela{ + WebAddress: "", + CorsAllowOrigins: []string{"http://localhost:3000", "http://localhost:3001", "http://localhost:8888"}, + }, + }, + origin: "http://localhost:8888", + expectedOrigin: "http://localhost:8888", + expectedCredentials: "true", + expectedExposeHeaders: "link, x-total-count", }, } - // setup context - gin.SetMode(gin.TestMode) - - resp := httptest.NewRecorder() - context, engine := gin.CreateTestContext(resp) - context.Request, _ = http.NewRequest(http.MethodGet, "/health", nil) - - // setup mock server - engine.Use(Metadata(m)) - engine.Use(Cors) - engine.GET("/health", func(c *gin.Context) { - c.Status(http.StatusOK) - }) - - // run test - engine.ServeHTTP(context.Writer, context.Request) - - gotOrigin := context.Writer.Header().Get("Access-Control-Allow-Origin") - gotExposeHeaders := context.Writer.Header().Get("Access-Control-Expose-Headers") - - if resp.Code != http.StatusOK { - t.Errorf("CORS returned %v, want %v", resp.Code, http.StatusOK) - } - - if !reflect.DeepEqual(gotOrigin, wantOrigin) { - t.Errorf("CORS Access-Control-Allow-Origin is %v, want %v", gotOrigin, wantOrigin) - } - - if !reflect.DeepEqual(gotExposeHeaders, wantExposeHeaders) { - t.Errorf("CORS Access-Control-Expose-Headers is %v, want %v", gotExposeHeaders, wantExposeHeaders) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gin.SetMode(gin.TestMode) + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + context.Request, _ = http.NewRequest(http.MethodGet, "/health", nil) + context.Request.Header.Add("Origin", tt.origin) + + // inject metadata + engine.Use(func(c *gin.Context) { + c.Set("metadata", tt.m) + c.Next() + }) + engine.Use(Cors) + engine.GET("/health", func(c *gin.Context) { + c.Status(http.StatusOK) + }) + engine.ServeHTTP(context.Writer, context.Request) + + gotOrigin := context.Writer.Header().Get("Access-Control-Allow-Origin") + if gotOrigin != tt.expectedOrigin { + t.Errorf("Access-Control-Allow-Origin is %v; want %v", gotOrigin, tt.expectedOrigin) + } + + gotCredentials := context.Writer.Header().Get("Access-Control-Allow-Credentials") + if gotCredentials != tt.expectedCredentials { + t.Errorf("Access-Control-Allow-Credentials is %v; want %v", gotCredentials, tt.expectedCredentials) + } + + gotExposeHeaders := context.Writer.Header().Get("Access-Control-Expose-Headers") + if gotExposeHeaders != tt.expectedExposeHeaders { + t.Errorf("Access-Control-Expose-Headers is %v; want %v", gotExposeHeaders, tt.expectedExposeHeaders) + } + }) } }
15195c5a0eb8Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-9m63-33q3-xq5xghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-27616ghsaADVISORY
- github.com/go-vela/server/commit/257886e5a3eea518548387885894e239668584f5nvdWEB
- github.com/go-vela/server/commit/67c1892e2464dc54b8d2588815dfb7819222500bnvdWEB
- github.com/go-vela/server/releases/tag/v0.25.3nvdWEB
- github.com/go-vela/server/releases/tag/v0.26.3nvdWEB
- github.com/go-vela/server/security/advisories/GHSA-9m63-33q3-xq5xnvdWEB
- pkg.go.dev/vuln/GO-2025-3509ghsaWEB
News mentions
0No linked articles in our index yet.