CVE-2025-61926
Description
Allstar is a GitHub App to set and enforce security policies. In versions prior to 4.5, a vulnerability in Allstar’s Reviewbot component caused inbound webhook requests to be validated against a hard-coded, shared secret. The value used for the secret token was compiled into the Allstar binary and could not be configured at runtime. In practice, this meant that every deployment using Reviewbot would validate requests with the same secret unless the operator modified source code and rebuilt the component - an expectation that is not documented and is easy to miss. All Allstar releases prior to v4.5 that include the Reviewbot code path are affected. Deployments on v4.5 and later are not affected. Those who have not enabled or exposed the Reviewbot endpoint are not exposed to this issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/ossf/allstarGo | < 0.0.0-20250721181116-e004ecb540d6 | 0.0.0-20250721181116-e004ecb540d6 |
Affected products
1Patches
1e004ecb540d6Remove unused `reviewbot` code
3 files changed · +0 −408
cmd/reviewbot/main.go+0 −140 removed@@ -1,140 +0,0 @@ -// Copyright 2022 Allstar Authors - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "flag" - "os" - "strconv" - - "github.com/ossf/allstar/pkg/reviewbot" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" -) - -const defaultAppID = 169668 -const defaultMinReviewsRequired = 2 -const defaultPort = 8080 -const defaultSecretToken = "FooBar" - -func main() { - setupLog() - - config := reviewbot.Config{} - - if err := determineConfig(&config); err != nil { - log.Fatal().Err(err).Msg("Error determining configuration") - } - - if err := reviewbot.HandleWebhooks(&config); err != nil { - log.Fatal().Err(err).Msg("Error listening to webhooks") - } -} - -func determineConfigFromEnv(config *reviewbot.Config) error { - if envPort, ok := os.LookupEnv("PORT"); ok { - port, err := strconv.ParseUint(envPort, 10, 16) - - if err != nil { - return err - } - - config.Port = port - } - - if envAppId, ok := os.LookupEnv("APP_ID"); ok { - appId, err := strconv.ParseInt(envAppId, 10, 64) - - if err != nil { - return err - } - - config.GitHub.AppId = appId - } - - if envPrivateKeyPath, ok := os.LookupEnv("PRIVATE_KEY_PATH"); ok { - config.GitHub.PrivateKeyPath = envPrivateKeyPath - } - - if envSecretToken, ok := os.LookupEnv("SECRET_TOKEN"); ok { - config.GitHub.SecretToken = envSecretToken - } - - return nil -} - -func determineConfigFromFlags(config *reviewbot.Config) error { - flagAppID := flag.Int64("app-id", defaultAppID, "A GitHub App Id") - flagPrivateKeyPath := flag.String("private-key-path", "", "A path to a GitHub Private Key") - flagSecretToken := flag.String("secret-token", defaultSecretToken, "GitHub Private Key") - flagMinReviewsRequired := flag.Uint64("min-reviews-required", defaultMinReviewsRequired, "The global minimum number of reviews required") - flagPort := flag.Uint64("port", defaultPort, "A port to listen on") - - flag.Parse() - - if *flagAppID != defaultAppID { - config.GitHub.AppId = *flagAppID - } - - if *flagPrivateKeyPath != "" { - config.GitHub.PrivateKeyPath = *flagPrivateKeyPath - } - - if *flagSecretToken != defaultSecretToken { - config.GitHub.PrivateKeyPath = *flagSecretToken - } - - if *flagMinReviewsRequired != defaultMinReviewsRequired { - config.MinReviewsRequired = *flagMinReviewsRequired - } - - if *flagPort != defaultPort { - config.Port = *flagPort - } - - return nil -} - -func determineConfig(config *reviewbot.Config) error { - // Set defaults - config.GitHub.AppId = defaultAppID - config.GitHub.SecretToken = defaultSecretToken - config.MinReviewsRequired = defaultMinReviewsRequired - config.Port = defaultPort - - // Determine from environment variables - if err := determineConfigFromEnv(config); err != nil { - return err - } - - // Determine from flags - if err := determineConfigFromFlags(config); err != nil { - return err - } - - return nil -} - -func setupLog() { - // Match expected values in GCP - zerolog.LevelFieldName = "severity" - zerolog.LevelTraceValue = "DEFAULT" - zerolog.LevelDebugValue = "DEBUG" - zerolog.LevelInfoValue = "INFO" - zerolog.LevelWarnValue = "WARNING" - zerolog.LevelErrorValue = "ERROR" - zerolog.LevelFatalValue = "CRITICAL" - zerolog.LevelPanicValue = "CRITICAL" -}
pkg/reviewbot/checks.go+0 −154 removed@@ -1,154 +0,0 @@ -package reviewbot - -import ( - "context" - "fmt" - "net/http" - "time" - - "github.com/bradleyfalzon/ghinstallation/v2" - "github.com/google/go-github/v59/github" - "github.com/rs/zerolog/log" -) - -// re-requested reviews should remove last review -// - fire event - -type PullRequestInfo struct { - owner string - repo string - user string - installationId int64 - headSHA string - number int -} - -func runPRCheck(config Config, pr PullRequestInfo) error { - tr, err := ghinstallation.NewKeyFromFile(http.DefaultTransport, appID, pr.installationId, config.GitHub.PrivateKeyPath) - if err != nil { - log.Error().Interface("pr", pr).Err(err).Msg("Could not read key") - return err - } - - client := github.NewClient(&http.Client{Transport: tr}) - ctx := context.Background() - - // TODO: get repo-level overwrites, if available - minReviewsRequired := config.MinReviewsRequired - - // List of approvers to verify - var approvalCandidates = map[string]bool{ - // Add PR Creator as someone to check - pr.user: true, - } - - optListReviews := &github.ListOptions{PerPage: 100} - - // Check reviews - for { - reviews, resp, err := client.PullRequests.ListReviews(ctx, pr.owner, pr.repo, pr.number, optListReviews) - if err != nil { - log.Error().Interface("pr", pr).Err(err).Msg("Could not list reviews") - return err - } - - for _, review := range reviews { - login := review.GetUser().GetLogin() - association := review.GetAuthorAssociation() - state := review.GetState() - - // Ignore accounts without association with the repo and comments - if association == "NONE" || state == "COMMENTED" { - continue - } - - log.Debug().Interface("pr", pr).Str("login", login).Str("association", association).Str("state", state).Msg("Found a review candidate") - - if state == "APPROVED" { - approvalCandidates[login] = true - } else { - delete(approvalCandidates, login) - } - } - - if resp.NextPage == 0 { - break - } - - optListReviews.Page = resp.NextPage - } - - // Points for approval - var points uint64 = 0 - - for login := range approvalCandidates { - permissionLevel, _, err := client.Repositories.GetPermissionLevel(ctx, pr.owner, pr.repo, login) - if err != nil { - return err - } - - permission := permissionLevel.GetPermission() - isAuthorized := permission == "admin" || permission == "write" - - log.Debug().Interface("pr", pr).Str("login", login).Str("permission", permission).Msg("Approver Authorization") - - if isAuthorized { - points++ - - if points == minReviewsRequired { - // no need to waste resources - we have enough authorized approvers - break - } - } - } - - log.Info().Interface("pr", pr).Uint64("points", points).Msg("Check's State") - - statusComplete := "completed" - titlePrefix := "⭐️ Allstar Pull Request Review Bot - " - text := fmt.Sprintf("PR has %d authorized approvals, %d required", points, minReviewsRequired) - timestamp := github.Timestamp{ - Time: time.Now(), - } - - check := github.CreateCheckRunOptions{ - Name: "Allstar Review Bot", - Status: &statusComplete, - CompletedAt: ×tamp, - Output: &github.CheckRunOutput{ - Text: &text, - }, - HeadSHA: pr.headSHA, - } - - if points >= minReviewsRequired { - conclusion := "success" - title := titlePrefix + conclusion - summary := "Pull request has enough authorized approvals" - - check.Conclusion = &conclusion - check.Output.Title = &title - check.Output.Summary = &summary - } else { - conclusion := "failure" - title := titlePrefix + conclusion - - delta := minReviewsRequired - points - deltaMessage := fmt.Sprintf("need %d more approval(s)", delta) - - summary := "Pull request does not have enough authorized approvals - " + deltaMessage - - check.Conclusion = &conclusion - check.Output.Title = &title - check.Output.Summary = &summary - } - - checkRun, _, err := client.Checks.CreateCheckRun(ctx, pr.owner, pr.repo, check) - if err != nil { - return err - } - - log.Info().Interface("pr", pr).Interface("Check Run", checkRun).Msg("Created Check Run") - - return nil -}
pkg/reviewbot/reviewbot.go+0 −114 removed@@ -1,114 +0,0 @@ -package reviewbot - -import ( - "fmt" - "net/http" - - "github.com/google/go-github/v59/github" - "github.com/rs/zerolog/log" -) - -const secretToken = "FooBar" -const appID = 169668 - -type Config struct { - // Configuration for GitHub - // TODO: future: option to get the below values from Secret Manager. See: `setKeySecret` in pkg/config/operator/operator.go - GitHub struct { - // The GitHub App's id. - // See: https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#authenticating-as-a-github-app - AppId int64 - - // Path to private key - PrivateKeyPath string - - // See https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks - SecretToken string - } - - // The global minimum reviews required for approval - MinReviewsRequired uint64 - - // Port to listen on - Port uint64 -} - -type WebookHandler struct { - config Config -} - -// Handle GitHub Webhooks for Review Bot. -// -// Example: -// -// config := Config{...} -// reviewbot.HandleWebhooks(&config) -func HandleWebhooks(config *Config) error { - w := WebookHandler{*config} - - http.HandleFunc("/", w.HandleRoot) - - address := fmt.Sprintf(":%d", config.Port) - - return http.ListenAndServe(address, nil) -} - -// Handle the root path -func (h *WebookHandler) HandleRoot(w http.ResponseWriter, r *http.Request) { - // Validate payload - payload, err := github.ValidatePayload(r, []byte(secretToken)) - if err != nil { - log.Error().Interface("payload", payload).Err(err).Msg("Got an invalid payload") - w.WriteHeader(400) - if _, err := fmt.Fprintln(w, "Got an invalid payload"); err != nil { - log.Error().Err(err).Msg("Failed to write http response") - } - return - } - - // Parse the webhook to a GitHub event type - event, err := github.ParseWebHook(github.WebHookType(r), payload) - if err != nil { - log.Error().Interface("event", event).Err(err).Msg("Failed to parse the webhook payload") - w.WriteHeader(400) - if _, err := fmt.Fprintln(w, "Failed to parse the webhook payload"); err != nil { - log.Error().Err(err).Msg("Failed to write http response") - } - return - } - - var pr PullRequestInfo - - // Extract relevant PR information from event, if is a PR-related event - switch event := event.(type) { - case *github.PullRequestEvent: - pr = PullRequestInfo{ - owner: event.GetRepo().GetOwner().GetLogin(), - repo: event.GetRepo().GetName(), - user: event.GetPullRequest().GetUser().GetLogin(), - installationId: event.GetInstallation().GetID(), - headSHA: event.PullRequest.GetHead().GetSHA(), - number: event.GetPullRequest().GetNumber(), - } - default: - log.Warn().Interface("event", event).Msg("Unknown event") - w.WriteHeader(400) - if _, err := fmt.Fprintln(w, "Unknown GitHub Event"); err != nil { - log.Error().Err(err).Msg("Failed to write http response") - } - return - } - - log.Info().Interface("pr", pr).Msg("Handling Pull Request Review Event") - - // Run PR Check - err = runPRCheck(h.config, pr) - if err != nil { - log.Error().Interface("pr", pr).Err(err).Msg("Error handling webhook") - w.WriteHeader(500) - if _, err := fmt.Fprintln(w, "Error handling webhook"); err != nil { - log.Error().Err(err).Msg("Failed to write http response") - } - return - } -}
Vulnerability 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
7- github.com/advisories/GHSA-33f4-mjch-7fprghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-61926ghsaADVISORY
- github.com/ossf/allstar/blob/294ae985cc2facd0918e8d820e4196021aa0b914/pkg/reviewbot/reviewbot.gonvdWEB
- github.com/ossf/allstar/commit/e004ecb540d63ca6f5b1689b41af6c0040a82c73nvdWEB
- github.com/ossf/allstar/pull/713nvdWEB
- github.com/ossf/allstar/security/advisories/GHSA-33f4-mjch-7fprnvdWEB
- pkg.go.dev/vuln/GO-2025-4018ghsaWEB
News mentions
0No linked articles in our index yet.