Skip to content

[WIP] gmpctl fixes#1938

Draft
bwplotka wants to merge 14 commits into
mainfrom
fix-git-command-injection-20260520
Draft

[WIP] gmpctl fixes#1938
bwplotka wants to merge 14 commits into
mainfrom
fix-git-command-injection-20260520

Conversation

@bwplotka

@bwplotka bwplotka commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Preview of incoming fixes. We could remove disable interactive mode this should be running as a bot.

Notable alternative is renovatebot. I checked and it's AGPL, (so no CLI use for us) and we need to bake a lot of internal logic (e.g. semconv version sync, de-sync of go.mod vs Dockerfiles, etc). Using gmpctl could be a short-term help though.

bwplotka added 10 commits May 21, 2026 16:27
Best effort fixes thanks to Gemini security scanning.

Signed-off-by: bwplotka <bwplotka@google.com>
Signed-off-by: bwplotka <bwplotka@google.com>
Signed-off-by: bwplotka <bwplotka@google.com>
Signed-off-by: bwplotka <bwplotka@google.com>
Signed-off-by: bwplotka <bwplotka@google.com>
Signed-off-by: bwplotka <bwplotka@google.com>
Signed-off-by: bwplotka <bwplotka@google.com>
Signed-off-by: bwplotka <bwplotka@google.com>
Signed-off-by: bwplotka <bwplotka@google.com>
Signed-off-by: bwplotka <bwplotka@google.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enhances the gmpctl tool by automating Go version detection from Dockerfiles, resolving OpenTelemetry schema version conflicts, adding automated pull request creation via the GitHub CLI, and introducing a thread-safe cache for NVD API severity lookups. Feedback on these changes focuses on improving robustness and performance, such as handling non-interactive environments gracefully in TTY detection, utilizing filepath.WalkDir and find -prune for more efficient directory traversal, capturing standard error in command execution, using idiomatic string comparisons, and falling back to CVSS V3.0 metrics when V3.1 is missing.

Comment thread ops/gmpctl/git.go Outdated
Comment on lines +53 to +60
func mustGetTTY() string {
ttyCmd := exec.Command("tty")
ttyOut, err := ttyCmd.Output()
if err != nil {
panicf(err.Error())
}
return strings.TrimSpace(string(ttyOut))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the script is run in a non-interactive environment (such as a CI/CD bot or automated pipeline), the tty command will fail, causing mustGetTTY to panic and crash the entire program. Returning an empty string on error allows the program to handle the absence of a TTY gracefully.

Suggested change
func mustGetTTY() string {
ttyCmd := exec.Command("tty")
ttyOut, err := ttyCmd.Output()
if err != nil {
panicf(err.Error())
}
return strings.TrimSpace(string(ttyOut))
}
func mustGetTTY() string {
ttyCmd := exec.Command("tty")
ttyOut, err := ttyCmd.Output()
if err != nil {
return ""
}
return strings.TrimSpace(string(ttyOut))
}

Comment thread ops/gmpctl/git.go Outdated
Comment on lines +65 to +66
// Ensure TTY is set for GPG signing.
envs := []string{"GPG_TTY=" + mustGetTTY()}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Only set the GPG_TTY environment variable if a valid TTY was successfully detected, ensuring compatibility with non-interactive environments.

Suggested change
// Ensure TTY is set for GPG signing.
envs := []string{"GPG_TTY=" + mustGetTTY()}
// Ensure TTY is set for GPG signing if available.
var envs []string
if tty := mustGetTTY(); tty != "" {
envs = append(envs, "GPG_TTY="+tty)
}

Comment thread ops/gmpctl/cmd_vulnfix.go
Comment on lines +146 to +161
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
name := info.Name()
if name == "third_party" || name == "ui" || name == "vendor" || name == "node_modules" || name == ".git" {
return filepath.SkipDir
}
return nil
}
if strings.HasPrefix(info.Name(), "Dockerfile") {
dockerfiles = append(dockerfiles, path)
}
return nil
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using filepath.Walk is less efficient because it queries the file system for every file. Since Go 1.16, filepath.WalkDir is preferred as it avoids calling os.Lstat on every file, significantly improving performance.

Suggested change
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
name := info.Name()
if name == "third_party" || name == "ui" || name == "vendor" || name == "node_modules" || name == ".git" {
return filepath.SkipDir
}
return nil
}
if strings.HasPrefix(info.Name(), "Dockerfile") {
dockerfiles = append(dockerfiles, path)
}
return nil
})
err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
name := d.Name()
if name == "third_party" || name == "ui" || name == "vendor" || name == "node_modules" || name == ".git" {
return filepath.SkipDir
}
return nil
}
if strings.HasPrefix(d.Name(), "Dockerfile") {
dockerfiles = append(dockerfiles, path)
}
return nil
})

Comment thread ops/gmpctl/cmd_vulnfix.go
Comment on lines +220 to +222
cmd := exec.Command("go", "run", "gmpctl_tmp_schema.go")
cmd.Dir = dir
out, err := cmd.Output()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When running the temporary schema detector, any compilation or runtime errors printed to stderr are currently lost, making debugging failures extremely difficult. Setting cmd.Stderr = os.Stderr ensures that these errors are printed to the terminal for easier troubleshooting.

	cmd := exec.Command("go", "run", "gmpctl_tmp_schema.go")
	cmd.Dir = dir
	cmd.Stderr = os.Stderr
	out, err := cmd.Output()

Comment thread ops/gmpctl/cmd_vulnfix.go
Comment on lines +251 to +264
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
name := info.Name()
if name == "vendor" || name == "third_party" || name == ".git" {
return filepath.SkipDir
}
return nil
}
if !strings.HasSuffix(info.Name(), ".go") {
return nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using filepath.Walk is less efficient because it queries the file system for every file. Since Go 1.16, filepath.WalkDir is preferred as it avoids calling os.Lstat on every file, significantly improving performance.

Suggested change
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
name := info.Name()
if name == "vendor" || name == "third_party" || name == ".git" {
return filepath.SkipDir
}
return nil
}
if !strings.HasSuffix(info.Name(), ".go") {
return nil
}
if err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
name := d.Name()
if name == "vendor" || name == "third_party" || name == ".git" {
return filepath.SkipDir
}
return nil
}
if !strings.HasSuffix(d.Name(), ".go") {
return nil
}

Comment thread ops/gmpctl/gmp.go
Comment on lines +64 to +65
case strings.Compare(branch, "main") == 0:
return PrometheusEngine, true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using strings.Compare for simple equality checks is a Go anti-pattern. It is clearer, more idiomatic, and faster to use the standard == operator.

Suggested change
case strings.Compare(branch, "main") == 0:
return PrometheusEngine, true
case branch == "main":
return PrometheusEngine, true

Comment thread ops/gmpctl/lib.sh
return 1
fi
find "${dir}" -name "Dockerfile*" | grep -v "${dir}/third_party/" | grep -v "${dir}/hack/" | grep -v "${dir}/ui/" | grep -v "vendor/" | grep -v "node_modules/"
find "${dir}" -name "Dockerfile*" | grep -v "${dir}/third_party/" | grep -v "${dir}/ui/" | grep -v "vendor/" | grep -v "node_modules/"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current find command traverses all directories (including large ones like node_modules, vendor, and .git) and then filters them out using grep. This is extremely slow. Using -prune allows find to skip these directories entirely, resulting in a massive performance improvement.

Suggested change
find "${dir}" -name "Dockerfile*" | grep -v "${dir}/third_party/" | grep -v "${dir}/ui/" | grep -v "vendor/" | grep -v "node_modules/"
find "${dir}" \( -name "third_party" -o -name "ui" -o -name "vendor" -o -name "node_modules" -o -name ".git" \) -prune -o -name "Dockerfile*" -print

Comment on lines 89 to 98
if len(nvdResponse.Vulnerabilities) > 0 {
metrics := nvdResponse.Vulnerabilities[0].CVE.Metrics
if len(metrics.CVSSMetricV31) > 0 {
return metrics.CVSSMetricV31[0].CVSSData.BaseSeverity, nil
sev := metrics.CVSSMetricV31[0].CVSSData.BaseSeverity
cacheMu.Lock()
severityCache[cveID] = sev
cacheMu.Unlock()
return sev, nil
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Fall back to checking CVSS V3.0 metrics if CVSS V3.1 metrics are not present, ensuring accurate severity classification for a wider range of CVEs.

Suggested change
if len(nvdResponse.Vulnerabilities) > 0 {
metrics := nvdResponse.Vulnerabilities[0].CVE.Metrics
if len(metrics.CVSSMetricV31) > 0 {
return metrics.CVSSMetricV31[0].CVSSData.BaseSeverity, nil
sev := metrics.CVSSMetricV31[0].CVSSData.BaseSeverity
cacheMu.Lock()
severityCache[cveID] = sev
cacheMu.Unlock()
return sev, nil
}
}
if len(nvdResponse.Vulnerabilities) > 0 {
metrics := nvdResponse.Vulnerabilities[0].CVE.Metrics
var sev string
if len(metrics.CVSSMetricV31) > 0 {
sev = metrics.CVSSMetricV31[0].CVSSData.BaseSeverity
} else if len(metrics.CVSSMetricV30) > 0 {
sev = metrics.CVSSMetricV30[0].CVSSData.BaseSeverity
}
if sev != "" {
cacheMu.Lock()
severityCache[cveID] = sev
cacheMu.Unlock()
return sev, nil
}
}

bwplotka added 4 commits June 2, 2026 17:26
Signed-off-by: bwplotka <bwplotka@google.com>
Signed-off-by: bwplotka <bwplotka@google.com>
Signed-off-by: bwplotka <bwplotka@google.com>
Signed-off-by: bwplotka <bwplotka@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant