Skip to content

GitLab: Renovate Self-Hosted Enumeration #197

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ You can tweak `--threads`, `--max-artifact-size` and `--job-limit` to obtain a c

`enum` command: Enumerate user permissions and accesss

`renovate` command: Enumerate self-hosted Renovate instances. More here on [Autodiscovery](https://blog.compass-security.com/2025/05/renovate-keeping-your-updates-secure/).

### GitLab Proxy Support

> **Note:** Proxying is currently supported only for GitLab commands.
Expand Down
2 changes: 1 addition & 1 deletion src/pipeleak/cmd/devops/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ func (a AzureDevOpsApiClient) ListBuildArtifacts(continuationToken string, organ
if err != nil {
log.Error().Err(err).Str("url", reqUrl).Str("organization", organization).Str("project", project).Msg("Failed to list build artifacts (network or client error)")
}

if res != nil && (res.StatusCode() == 404 || res.StatusCode() == 401) {
log.Error().Int("status", res.StatusCode()).Str("organization", organization).Str("project", project).Str("url", reqUrl).Str("response", res.String()).Msg("Build artifacts list does not exist or you do not have access (HTTP error)")
}
Expand Down
2 changes: 1 addition & 1 deletion src/pipeleak/cmd/gitlab/enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func enumCurrentToken(client resty.Client, baseUrl string, pat string) {
log.Error().Err(err).Str("url", u.String()).Msg("Failed fetching token details (network or client error)")
return
}

if res != nil && res.StatusCode() != 200 {
log.Error().Int("status", res.StatusCode()).Str("url", u.String()).Str("response", res.String()).Msg("Failed fetching token details (HTTP error)")
return
Expand Down
2 changes: 2 additions & 0 deletions src/pipeleak/cmd/gitlab/gitlab.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gitlab

import (
"github.com/CompassSecurity/pipeleak/cmd/gitlab/renovate"
"github.com/CompassSecurity/pipeleak/cmd/gitlab/runners"
"github.com/CompassSecurity/pipeleak/cmd/gitlab/scan"
"github.com/CompassSecurity/pipeleak/cmd/gitlab/secureFiles"
Expand Down Expand Up @@ -28,6 +29,7 @@ func NewGitLabRootCmd() *cobra.Command {
glCmd.AddCommand(NewVariablesCmd())
glCmd.AddCommand(securefiles.NewSecureFilesCmd())
glCmd.AddCommand(NewEnumCmd())
glCmd.AddCommand(renovate.NewRenovateCmd())

glCmd.PersistentFlags().StringVarP(&gitlabUrl, "gitlab", "g", "", "GitLab instance URL")
err := glCmd.MarkPersistentFlagRequired("gitlab")
Expand Down
2 changes: 1 addition & 1 deletion src/pipeleak/cmd/gitlab/register.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package gitlab

import (
"github.com/CompassSecurity/pipeleak/cmd/gitlab/util"
"github.com/CompassSecurity/pipeleak/helper"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/CompassSecurity/pipeleak/cmd/gitlab/util"
)

var (
Expand Down
150 changes: 150 additions & 0 deletions src/pipeleak/cmd/gitlab/renovate/renovate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package renovate

import (
"bytes"
"strings"

"github.com/CompassSecurity/pipeleak/cmd/gitlab/util"
"github.com/CompassSecurity/pipeleak/helper"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"gitlab.com/gitlab-org/api/client-go"
"gopkg.in/yaml.v3"
)

var (
gitlabApiToken string
gitlabUrl string
verbose bool
owned bool
member bool
projectSearchQuery string
)

func NewRenovateCmd() *cobra.Command {
renovateCmd := &cobra.Command{
Use: "renovate [no options!]",
Short: "Enumerate renovate runner projects",
Run: Enumerate,
}

renovateCmd.PersistentFlags().StringVarP(&gitlabUrl, "gitlab", "g", "", "GitLab instance URL")
err := renovateCmd.MarkPersistentFlagRequired("gitlab")
if err != nil {
log.Fatal().Stack().Err(err).Msg("Unable to require gitlab flag")
}

renovateCmd.PersistentFlags().StringVarP(&gitlabApiToken, "token", "t", "", "GitLab API Token")
err = renovateCmd.MarkPersistentFlagRequired("token")
if err != nil {
log.Error().Stack().Err(err).Msg("Unable to require token flag")
}
renovateCmd.MarkFlagsRequiredTogether("gitlab", "token")

renovateCmd.PersistentFlags().BoolVarP(&owned, "owned", "o", false, "Scan user onwed projects only")
renovateCmd.PersistentFlags().BoolVarP(&member, "member", "m", false, "Scan projects the user is member of")
renovateCmd.Flags().StringVarP(&projectSearchQuery, "search", "s", "", "Query string for searching projects")

renovateCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Verbose logging")

return renovateCmd
}

func Enumerate(cmd *cobra.Command, args []string) {
helper.SetLogLevel(verbose)
git, err := util.GetGitlabClient(gitlabApiToken, gitlabUrl)
if err != nil {
log.Fatal().Stack().Err(err).Msg("failed creating gitlab client")
}

fetchProjects(git)

log.Info().Msg("Done, Bye Bye 🏳️‍🌈🔥")
}

func fetchProjects(git *gitlab.Client) {
log.Info().Msg("Fetching projects")

projectOpts := &gitlab.ListProjectsOptions{
ListOptions: gitlab.ListOptions{
PerPage: 100,
Page: 1,
},
OrderBy: gitlab.Ptr("last_activity_at"),
Owned: gitlab.Ptr(owned),
Membership: gitlab.Ptr(member),
Search: gitlab.Ptr(projectSearchQuery),
}

for {
projects, resp, err := git.Projects.ListProjects(projectOpts)
if err != nil {
log.Error().Stack().Err(err).Msg("Failed fetching projects")
break
}

for _, project := range projects {
log.Debug().Str("url", project.WebURL).Msg("Fetch project jobs")
identifyRenovateBotJob(git, project)
}

if resp.NextPage == 0 {
break
}

projectOpts.Page = resp.NextPage
log.Info().Int("currentPage", projectOpts.Page).Msg("Fetched projects page")
}

log.Info().Msg("Fetched all projects")
}

func identifyRenovateBotJob(git *gitlab.Client, project *gitlab.Project) {

lintOpts := &gitlab.ProjectLintOptions{
IncludeJobs: gitlab.Ptr(true),
}
res, response, err := git.Validate.ProjectLint(project.ID, lintOpts)

if response.StatusCode == 404 || response.StatusCode == 403 {
return // Project does not have a CI/CD configuration or is not accessible
}

if err != nil {
log.Error().Stack().Err(err).Msg("Failed fetching project ci/cd yml")
return
}

if strings.Contains(res.MergedYaml, "renovate/renovate") || strings.Contains(res.MergedYaml, "--autodiscover=true") {
log.Info().Str("project", project.Name).Str("url", project.WebURL).Msg("Found renovate bot job image")
yml, err := prettyPrintYAML(res.MergedYaml)

if err != nil {
log.Error().Stack().Err(err).Msg("Failed pretty printing project ci/cd yml")
return
}

// make windows compatible
log.Info().Msg("\n" + yml)
}
}

func prettyPrintYAML(yamlStr string) (string, error) {
var node yaml.Node

err := yaml.Unmarshal([]byte(yamlStr), &node)
if err != nil {
return "", err
}

var buf bytes.Buffer
encoder := yaml.NewEncoder(&buf)
encoder.SetIndent(2)

err = encoder.Encode(&node)
if err != nil {
return "", err
}

return buf.String(), nil
}
2 changes: 1 addition & 1 deletion src/pipeleak/cmd/gitlab/variables.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package gitlab

import (
"github.com/CompassSecurity/pipeleak/cmd/gitlab/util"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"gitlab.com/gitlab-org/api/client-go"
"github.com/CompassSecurity/pipeleak/cmd/gitlab/util"
)

func NewVariablesCmd() *cobra.Command {
Expand Down
Loading