-
Notifications
You must be signed in to change notification settings - Fork 19
feat: add github/openssf scrapers #1875
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
base: main
Are you sure you want to change the base?
Changes from all commits
13a638f
89fff5b
562a0f4
6c2c1c2
d808826
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -161,3 +161,7 @@ func (ctx ScrapeContext) Namespace() string { | |||||||||||||||||||
| func (ctx ScrapeContext) IsTrace() bool { | ||||||||||||||||||||
| return ctx.scrapeConfig.Spec.IsTrace() | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| func (ctx ScrapeContext) IsDebug() bool { | ||||||||||||||||||||
| return ctx.scrapeConfig.Spec.IsDebug() | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
Comment on lines
+165
to
+167
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against nil ScrapeConfig. 🐛 Proposed fix func (ctx ScrapeContext) IsDebug() bool {
- return ctx.scrapeConfig.Spec.IsDebug()
+ if ctx.scrapeConfig == nil {
+ return false
+ }
+ return ctx.scrapeConfig.Spec.IsDebug()
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package v1 | ||
|
|
||
| import ( | ||
| "time" | ||
|
|
||
| "github.com/flanksource/duty/types" | ||
| ) | ||
|
|
||
| // GitHubSecurity scraper fetches security alerts from GitHub repositories | ||
| // including Dependabot alerts, code scanning alerts, secret scanning alerts, | ||
| // and security advisories. | ||
| type GitHubSecurity struct { | ||
| BaseScraper `json:",inline" yaml:",inline"` | ||
|
|
||
| // Repositories is the list of repositories to scan | ||
| Repositories []GitHubSecurityRepository `yaml:"repositories" json:"repositories"` | ||
|
|
||
| // PersonalAccessToken for GitHub API authentication | ||
| // Required scopes: repo (full) or security_events (read) | ||
| PersonalAccessToken types.EnvVar `yaml:"personalAccessToken,omitempty" json:"personalAccessToken,omitempty"` | ||
|
|
||
| // ConnectionName, if provided, will be used to populate personalAccessToken | ||
| ConnectionName string `yaml:"connection,omitempty" json:"connection,omitempty"` | ||
|
|
||
| // Filters for security alerts | ||
| Filters GitHubSecurityFilters `yaml:"filters,omitempty" json:"filters,omitempty"` | ||
| } | ||
|
|
||
| // GitHubSecurityRepository specifies a repository to scan | ||
| type GitHubSecurityRepository struct { | ||
| Owner string `yaml:"owner" json:"owner"` | ||
| Repo string `yaml:"repo" json:"repo"` | ||
| } | ||
|
|
||
| // GitHubSecurityFilters defines filtering options for security alerts | ||
| type GitHubSecurityFilters struct { | ||
| // Severity filters: critical, high, medium, low | ||
| Severity []string `yaml:"severity,omitempty" json:"severity,omitempty"` | ||
|
|
||
| // State filters: open, closed, dismissed, fixed | ||
| State []string `yaml:"state,omitempty" json:"state,omitempty"` | ||
|
|
||
| // MaxAge filters alerts by age (e.g., "90d", "30d") | ||
| MaxAge string `yaml:"maxAge,omitempty" json:"maxAge,omitempty"` | ||
| } | ||
|
|
||
| // ParseMaxAge converts the MaxAge string to a time.Duration | ||
| func (f GitHubSecurityFilters) ParseMaxAge() (time.Duration, error) { | ||
| if f.MaxAge == "" { | ||
| return 0, nil | ||
| } | ||
| return time.ParseDuration(f.MaxAge) | ||
|
Comment on lines
+43
to
+52
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: In Go’s standard library,
Docs: Examples: Sources [1] https://pkg.go.dev/time#ParseDuration 🏁 Script executed: # Search for MaxAge documentation examples in the codebase
rg -A 5 -B 5 "MaxAge" --type go | head -100Repository: flanksource/config-db Length of output: 186 🏁 Script executed: # Specifically look for "90d" or "30d" examples in comments or strings
rg "(90d|30d)"Repository: flanksource/config-db Length of output: 186 🏁 Script executed: fd github_security.go --exec cat {}Repository: flanksource/config-db Length of output: 186
If the documentation or code examples suggest using 🛠️ Proposed fix (support "d" suffix) import (
+ "strconv"
+ "strings"
"time"
"github.com/flanksource/duty/types"
)
@@
func (f GitHubSecurityFilters) ParseMaxAge() (time.Duration, error) {
if f.MaxAge == "" {
return 0, nil
}
+ if strings.HasSuffix(f.MaxAge, "d") {
+ daysStr := strings.TrimSuffix(f.MaxAge, "d")
+ days, err := strconv.Atoi(daysStr)
+ if err != nil {
+ return 0, err
+ }
+ return time.Hour * 24 * time.Duration(days), nil
+ }
return time.ParseDuration(f.MaxAge)
}🤖 Prompt for AI Agents |
||
| } | ||
|
Comment on lines
+47
to
+53
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, locate and examine the github_security.go file
find . -name "github_security.go" -type fRepository: flanksource/config-db Length of output: 186 🏁 Script executed: # Check if there are any documentation files or tests related to ParseMaxAge
fd -e md -e txt | head -20
git ls-files | grep -i securityRepository: flanksource/config-db Length of output: 567 🏁 Script executed: # Search for references to "90d" and "30d" in the codebase
rg "90d|30d" -iRepository: flanksource/config-db Length of output: 15027 🏁 Script executed: # Read the github_security.go file to see the actual implementation
cat -n api/v1/github_security.go | head -60Repository: flanksource/config-db Length of output: 2261 🏁 Script executed: # Search for tests related to ParseMaxAge
fd -e go -type f | xargs grep -l "ParseMaxAge" 2>/dev/nullRepository: flanksource/config-db Length of output: 237 🌐 Web query:
💡 Result: Go’s standard library The What to do instead
🏁 Script executed: # Search for tests of ParseMaxAge or any usages of it
rg "ParseMaxAge" -i -A 3 -B 3Repository: flanksource/config-db Length of output: 511 🏁 Script executed: # Check if there are any existing tests for github_security
fd -name "*github*security*test*" -o -name "*test*github*security*"Repository: flanksource/config-db Length of output: 295 🏁 Script executed: # Also check fixtures that might use MaxAge
grep -r "maxAge\|MaxAge" --include="*.yaml" --include="*.yml" --include="*.json"Repository: flanksource/config-db Length of output: 458
Line 43 documents examples "90d" and "30d", but line 52 uses 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,6 +6,8 @@ import ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/flanksource/clicky" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/flanksource/clicky/api" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/flanksource/commons/collections/set" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/flanksource/commons/logger" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/flanksource/duty" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -741,6 +743,110 @@ type ScrapeResult struct { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| GCPStructPB *structpb.Struct `json:"-"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (s ScrapeResult) Debug() api.Text { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t := clicky.Text("") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.Append("ID: ", "text-muted").Append(s.ID) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.Append(" Name: ", "text-muted").Append(s.Name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.Append(" Type: ", "text-muted").Append(s.Type) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.Append(" Status: ", "text-muted").Append(s.Status) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.Health != "" && s.Health != models.HealthUnknown { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.Append(" Health: ", "text-muted").Append(string(s.Health)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.Source != "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append("Source: ", "text-muted").Append(s.Source) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.ScraperLess { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append("Scraper Less: ", "text-muted").Append("true") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.Icon != "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append("Icon: ", "text-muted").Append(s.Icon) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.Error != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.Append(" Error: ", "text-red-500").Append(s.Error.Error()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.Aliases != nil && len(s.Aliases) > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.Append(" Aliases: ", "text-muted").Append(strings.Join(s.Aliases, ", ")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.Description != "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append("Description: ", "text-muted").Append(s.Description) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.Locations != nil && len(s.Locations) > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append("Locations: ", "text-muted").Append(strings.Join(s.Locations, ", ")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.Labels != nil && len(s.Labels) > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append("Labels: ", "text-muted").Append(clicky.Map(s.Labels)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.Tags != nil && len(s.Tags) > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append("Tags: ", "text-muted").Append(clicky.Map(s.Tags.AsMap())) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.Properties != nil && len(s.Properties) > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append("Properties: ", "text-muted").Append(clicky.Map(s.Properties.AsMap())) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.Changes != nil && len(s.Changes) > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append("Changes: ", "text-muted") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, change := range s.Changes { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append(fmt.Sprintf(" - %s: %s", change.ChangeType, change.Summary)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch v := s.Config.(type) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case string: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.Format == "json" || s.Format == "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append(clicky.CodeBlock("json", v)).NewLine() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append(clicky.CodeBlock(s.Format, v)).NewLine() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case map[string]any: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append(clicky.Map(v), "max-w-[100ch]").NewLine() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case map[string]string: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append(clicky.Map(v), "max-w-[100ch]").NewLine() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append(fmt.Sprintf("%v", v)).NewLine() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return t | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (s ScrapeResult) PrettyRow(options interface{}) map[string]api.Text { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| row := make(map[string]api.Text) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| row["ID"] = clicky.Text(s.ID) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| row["Name"] = clicky.Text(s.Name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| row["Type"] = clicky.Text(s.Type) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| row["Health"] = clicky.Text(string(s.Health)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| row["Status"] = clicky.Text(s.Status) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.Error != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| row["Error"] = clicky.Text(s.Error.Error()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| row["Error"] = clicky.Text("") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clicky.MustPrint([]map[string]api.Text{row}, clicky.FormatOptions{JSON: true}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clicky.MustPrint([]map[string]api.Text{row}, clicky.FormatOptions{Pretty: true}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return row | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+815
to
+831
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, find and read the file to confirm the current state
fd -t f "interface.go" api/v1/ 2>/dev/null | head -5Repository: flanksource/config-db Length of output: 84 🏁 Script executed: # Read the specific section of the file
wc -l api/v1/interface.goRepository: flanksource/config-db Length of output: 89 🏁 Script executed: # Get the exact content around lines 815-831
sed -n '810,835p' api/v1/interface.goRepository: flanksource/config-db Length of output: 740 🏁 Script executed: # Search for all usages of PrettyRow in the codebase
rg "PrettyRow" -B 2 -A 2 --type goRepository: flanksource/config-db Length of output: 706 🏁 Script executed: # Check the ScrapeResult type definition and its interface
ast-grep --pattern 'type ScrapeResult struct { $$$ }'Repository: flanksource/config-db Length of output: 5467 🏁 Script executed: # Look for any interface that PrettyRow might be implementing
rg "PrettyRow" --type go -B 5 -A 5Repository: flanksource/config-db Length of output: 1339 🏁 Script executed: # Search for PrettyRow interface definition
rg "type PrettyRow" --type go -B 2 -A 5Repository: flanksource/config-db Length of output: 47 🏁 Script executed: # Search for all places where PrettyRow method is called (not defined)
rg "\.PrettyRow\(" --type go -B 2 -A 2Repository: flanksource/config-db Length of output: 47 🏁 Script executed: # Search for other types that implement PrettyRow method
rg "func \(.+\) PrettyRow" --type goRepository: flanksource/config-db Length of output: 159 🏁 Script executed: # Check if there are any tests for this method
rg "PrettyRow" --type go | grep -i testRepository: flanksource/config-db Length of output: 47 🏁 Script executed: # Search for api.PrettyRow usage
rg "api\.PrettyRow" --type go -B 2 -A 2Repository: flanksource/config-db Length of output: 460 🏁 Script executed: # Search for PrettyRow in the api package definition
fd -t f "\.go" | xargs grep -l "type PrettyRow" 2>/dev/nullRepository: flanksource/config-db Length of output: 49 🏁 Script executed: # Search more broadly for what might call PrettyRow
rg "PrettyRow" --type goRepository: flanksource/config-db Length of output: 254 🏁 Script executed: # Check the api package imports and see what PrettyRow is
rg "PrettyRow" -B 5 -A 5 --type goRepository: flanksource/config-db Length of output: 1339 🏁 Script executed: # Look for the clicky package to understand MustPrint behavior
rg "clicky\.MustPrint" --type go -B 2 -A 2 | head -50Repository: flanksource/config-db Length of output: 636 🏁 Script executed: # Look for interface PrettyRow definition in vendor or api packages
rg "interface.*PrettyRow\|type.*PrettyRow" --type go -B 2 -A 5Repository: flanksource/config-db Length of output: 47 🏁 Script executed: # Check if ScrapeResult is used as api.PrettyRow (interface check)
rg "var.*api\.PrettyRow" --type go -B 2 -A 2Repository: flanksource/config-db Length of output: 47 🏁 Script executed: # Look for any reflection or dynamic calls to PrettyRow
rg '"PrettyRow"' --type goRepository: flanksource/config-db Length of output: 47 🏁 Script executed: # Search in the api package directly
fd -type f -path "*/api*" -name "*.go" | head -20Repository: flanksource/config-db Length of output: 235 🏁 Script executed: # Look for where Configs field is populated/used from FullScrapeResults
rg "FullScrapeResults" --type go -B 3 -A 3Repository: flanksource/config-db Length of output: 934 🏁 Script executed: # Check imports in interface.go to understand the api package better
head -50 api/v1/interface.go | grep -E "^import|^package"Repository: flanksource/config-db Length of output: 84 🏁 Script executed: # Find where api.PrettyRow is defined - check if it's in a different package
find . -name "*.go" -type f -exec grep -l "type PrettyRow" {} \;Repository: flanksource/config-db Length of output: 47 🏁 Script executed: # Check the full api/v1 directory structure
ls -la api/v1/Repository: flanksource/config-db Length of output: 2174 🏁 Script executed: # Look for api package definition more broadly
find . -path "*/api/*" -name "*.go" -type f | xargs grep -l "PrettyRow" 2>/dev/null | head -10Repository: flanksource/config-db Length of output: 86 🏁 Script executed: # Check if api refers to external package by looking at imports
rg "^import" -A 30 api/v1/interface.go | head -50Repository: flanksource/config-db Length of output: 855 🏁 Script executed: # Search for where ScrapeResult might be assigned to api.PrettyRow
rg "api\.PrettyRow\|ScrapeResult" --type go | grep -E "(Configs|api\.PrettyRow)" | head -20Repository: flanksource/config-db Length of output: 47 🏁 Script executed: # Search for how Configs from FullScrapeResults is used
rg "\.Configs" --type go -B 2 -A 2 | head -50Repository: flanksource/config-db Length of output: 408 🏁 Script executed: # Check for MergeScrapeResults function to see if it converts to api.PrettyRow
rg "MergeScrapeResults" --type go -B 3 -A 20Repository: flanksource/config-db Length of output: 2156 🏁 Script executed: # Check if there's a conversion or assignment from ScrapeResult to api.PrettyRow
rg "api\.PrettyRow" --type go -B 5 -A 5Repository: flanksource/config-db Length of output: 842 🏁 Script executed: # Look for range over Configs to see how api.PrettyRow items are used
rg "for.*Configs" --type go -B 2 -A 5Repository: flanksource/config-db Length of output: 6010 🏁 Script executed: # Look at the full MergeScrapeResults function to understand how Configs is populated
sed -n '850,950p' api/v1/interface.goRepository: flanksource/config-db Length of output: 2299 🏁 Script executed: # Check how clicky package uses PrettyRow interface
python3 << 'EOF'
import subprocess
result = subprocess.run(['rg', 'api.PrettyRow', '-B 5', '-A 5', '--type', 'go'],
capture_output=True, text=True)
print(result.stdout)
if result.returncode != 0:
print(f"Error: {result.stderr}")
EOFRepository: flanksource/config-db Length of output: 155 🏁 Script executed: # Search for the clicky api.PrettyRow interface definition
python3 << 'EOF'
# Let's look at what happens when clicky.MustPrint gets Configs
# and understand the actual flow
import subprocess
# First find usage
result = subprocess.run(['rg', 'MustPrint.*Configs', '-B 3', '-A 3', '--type', 'go'],
capture_output=True, text=True)
print("=== MustPrint with Configs ===")
print(result.stdout)
# Now search for other PrettyRow implementations
result2 = subprocess.run(['rg', 'func.*PrettyRow', '--type', 'go'],
capture_output=True, text=True)
print("\n=== All PrettyRow implementations ===")
print(result2.stdout)
EOFRepository: flanksource/config-db Length of output: 136 🏁 Script executed: # Let's examine the complete MergeScrapeResults function
sed -n '846,900p' api/v1/interface.goRepository: flanksource/config-db Length of output: 1194 🏁 Script executed: # Check for any explicit calls to PrettyRow method
rg "\.PrettyRow\(" --type goRepository: flanksource/config-db Length of output: 47 🏁 Script executed: # Look at the actual line where r is appended to full.Configs
sed -n '875,895p' api/v1/interface.goRepository: flanksource/config-db Length of output: 572 🏁 Script executed: # Check if api.PrettyRow is an interface
python3 << 'EOF'
# Search for interface definition in the repository
import subprocess
result = subprocess.run(['rg', 'interface.*{', '--type', 'go', '-A', '5'],
capture_output=True, text=True)
lines = result.stdout.split('\n')
# Filter for PrettyRow related interfaces
for i, line in enumerate(lines):
if 'PrettyRow' in line or (i > 0 and 'PrettyRow' in '\n'.join(lines[max(0,i-5):min(len(lines),i+5)])):
print(line)
EOFRepository: flanksource/config-db Length of output: 47 🏁 Script executed: # Let's look more carefully at the complete flow - from MergeScrapeResults function
cat api/v1/interface.go | grep -n "func MergeScrapeResults" -A 60Repository: flanksource/config-db Length of output: 2127 🏁 Script executed: # Search for where api.PrettyRow interface might be defined
python3 << 'EOF'
import subprocess
# Search for PrettyRow in all contexts
result = subprocess.run(['rg', 'PrettyRow', '--type', 'go', '-C', '3'],
capture_output=True, text=True)
print(result.stdout[:3000])
EOFRepository: flanksource/config-db Length of output: 47 🏁 Script executed: # Check if there's any explicit call to PrettyRow method with debugging
grep -rn "\.PrettyRow(" . --include="*.go" 2>/dev/null || echo "No explicit calls found"Repository: flanksource/config-db Length of output: 88 🏁 Script executed: # Let me check the actual behavior - look at cmd/run.go to understand the flow
cat cmd/run.go | grep -B 10 -A 10 "clicky.MustPrint(all.Configs)"Repository: flanksource/config-db Length of output: 615 🏁 Script executed: # Look for what happens in clicky when MustPrint is called on PrettyRow items
# Check vendor or go.mod for clicky version
cat go.mod | grep clickyRepository: flanksource/config-db Length of output: 115 🏁 Script executed: # Let's check what api.PrettyRow actually is - look for interface definition in imported packages
python3 << 'EOF'
import subprocess
# Search for any interface that might define PrettyRow
result = subprocess.run(['rg', 'type.*PrettyRow.*interface', '--type', 'go'],
capture_output=True, text=True)
if result.stdout:
print("Found interface definition:")
print(result.stdout)
else:
print("No interface definition found in local code")
# Check if it's imported from clicky
result2 = subprocess.run(['rg', 'from.*clicky', '--type', 'go'],
capture_output=True, text=True)
print("\nClicky imports:")
print(result2.stdout[:1000] if result2.stdout else "None")
EOFRepository: flanksource/config-db Length of output: 130 🏁 Script executed: # Let me search the go.mod to see if there's a way to understand the clicky interface better
# First check if we can find any documentation or usage patterns
rg "clicky" -B 2 -A 2 --type go | grep -A 5 -B 5 "PrettyRow\|interface"Repository: flanksource/config-db Length of output: 3480 🏁 Script executed: # Let's look at what the review is actually saying - check if PrettyRow is being called implicitly
# by clicky when it encounters api.PrettyRow types
python3 << 'EOF'
# Since ScrapeResult is assigned to api.PrettyRow slice and no explicit calls found,
# PrettyRow must be a method that clicky calls automatically.
# Let's verify if clicky.MustPrint triggers the PrettyRow method
# Check the actual flow: when does the PrettyRow method get called?
import subprocess
# Look for usage of clicky.MustPrint with different types
result = subprocess.run(['rg', 'clicky.MustPrint', '-B 2', '-A 2', '--type', 'go'],
capture_output=True, text=True)
print("All clicky.MustPrint calls:")
print(result.stdout)
EOFRepository: flanksource/config-db Length of output: 93 Remove unconditional prints from The two Suggested fix func (s ScrapeResult) PrettyRow(options interface{}) map[string]api.Text {
row := make(map[string]api.Text)
row["ID"] = clicky.Text(s.ID)
row["Name"] = clicky.Text(s.Name)
row["Type"] = clicky.Text(s.Type)
row["Health"] = clicky.Text(string(s.Health))
row["Status"] = clicky.Text(s.Status)
if s.Error != nil {
row["Error"] = clicky.Text(s.Error.Error())
} else {
row["Error"] = clicky.Text("")
}
- clicky.MustPrint([]map[string]api.Text{row}, clicky.FormatOptions{JSON: true})
- clicky.MustPrint([]map[string]api.Text{row}, clicky.FormatOptions{Pretty: true})
-
return row
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (s ScrapeResult) IsMetadataOnly() bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return s.Config == nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (s ScrapeResult) Pretty() api.Text { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t := clicky.Text("") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.Append("ID: ", "text-muted").Append(s.ID) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.Append(" Name: ", "text-muted").Append(s.Name) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.Append(" Type: ", "text-muted").Append(s.Type) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if s.Tags != nil && len(s.Tags) > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t = t.NewLine().Append("Tags: ", "text-muted").Append(clicky.Map(s.Tags.AsMap())) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return t | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // +kubebuilder:object:generate=false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type ExternalConfigAccessLog struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| models.ConfigAccessLog | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -885,6 +991,58 @@ func (s ScrapeResult) Clone(config interface{}) ScrapeResult { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return clone | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // +kubebuilder:object:generate=false | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type FullScrapeResults struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Configs []api.PrettyRow `json:"configs,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Analysis []models.ConfigAnalysis `json:"analysis,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Changes []models.ConfigChange `json:"changes,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Relationships []models.ConfigRelationship `json:"relationships,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ExternalRoles []models.ExternalRole `json:"external_roles,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ExternalUsers []models.ExternalUser `json:"external_users,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ExternalGroups []models.ExternalGroup `json:"external_groups,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ExternalUserGroups []models.ExternalUserGroup `json:"external_user_groups,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ConfigAccess []ExternalConfigAccess `json:"config_access,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ConfigAccessLogs []ExternalConfigAccessLog `json:"config_access_logs,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func MergeScrapeResults(results ...ScrapeResults) FullScrapeResults { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| full := FullScrapeResults{} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, res := range results { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, r := range res { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if r.Error != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if r.AnalysisResult != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| full.Analysis = append(full.Analysis, r.AnalysisResult.ToConfigAnalysis()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, change := range r.Changes { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| configChange := models.ConfigChange{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ChangeType: change.ChangeType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Severity: models.Severity(change.Severity), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Source: change.Source, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Summary: change.Summary, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CreatedAt: change.CreatedAt, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| full.Changes = append(full.Changes, configChange) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !r.IsMetadataOnly() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| full.Configs = append(full.Configs, r) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| full.ExternalRoles = append(full.ExternalRoles, r.ExternalRoles...) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| full.ExternalUsers = append(full.ExternalUsers, r.ExternalUsers...) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| full.ExternalGroups = append(full.ExternalGroups, r.ExternalGroups...) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| full.ExternalUserGroups = append(full.ExternalUserGroups, r.ExternalUserGroups...) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| full.ConfigAccess = append(full.ConfigAccess, r.ConfigAccess...) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| full.ConfigAccessLogs = append(full.ConfigAccessLogs, r.ConfigAccessLogs...) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return full | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func Ellipses(str string, length int) string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(str) > length { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(str) < 3 || length < 3 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,18 @@ | ||||||||||||||||||||
| package v1 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // OpenSSFScorecard scraper fetches security scorecard data from the OpenSSF Scorecard API | ||||||||||||||||||||
| type OpenSSFScorecard struct { | ||||||||||||||||||||
| BaseScraper `json:",inline" yaml:",inline"` | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Repositories is the list of repositories to assess | ||||||||||||||||||||
| Repositories []OpenSSFRepository `yaml:"repositories" json:"repositories"` | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // MinScore optionally filters repositories by minimum score (0-10) | ||||||||||||||||||||
| MinScore *float64 `yaml:"minScore,omitempty" json:"minScore,omitempty"` | ||||||||||||||||||||
|
Check failure on line 11 in api/v1/openssf.go
|
||||||||||||||||||||
|
Comment on lines
+10
to
+11
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix float type to unblock CRD generation. The pipeline is failing because 🔧 Recommended fix: Use string type with custom unmarshaling // OpenSSFScorecard scraper fetches security scorecard data from the OpenSSF Scorecard API
type OpenSSFScorecard struct {
BaseScraper `json:",inline" yaml:",inline"`
// Repositories is the list of repositories to assess
Repositories []OpenSSFRepository `yaml:"repositories" json:"repositories"`
- // MinScore optionally filters repositories by minimum score (0-10)
- MinScore *float64 `yaml:"minScore,omitempty" json:"minScore,omitempty"`
+ // MinScore optionally filters repositories by minimum score (0-10)
+ // +kubebuilder:validation:Pattern=`^([0-9]|10)(\.[0-9]+)?$`
+ MinScore string `yaml:"minScore,omitempty" json:"minScore,omitempty"`
}Then parse the string to float64 in the scraper logic. Alternatively, if floats are truly needed, update the 📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Actions: Test[error] 11-11: controller-gen failed: CRD generation error for api/v1/openssf.go: found float, the usage of which is highly discouraged, as support for them varies across languages. Please consider serializing your float as string. If you are really sure you want to use them, re-run with crd:allowDangerousTypes=true. (Step: controller-gen crd paths="./api/..." output:crd:artifacts:config=chart/crds) 🪛 GitHub Check: lint[failure] 11-11: 🪛 GitHub Check: test[failure] 11-11: 🪛 GitHub Check: test-prod[failure] 11-11: 🤖 Prompt for AI Agents
Comment on lines
+10
to
+11
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix float type to resolve pipeline failures. The Consider using a string type or an integer representing a scaled value (e.g., 0-100 instead of 0.0-10.0): 🔧 Proposed fix using string type- // MinScore optionally filters repositories by minimum score (0-10)
- MinScore *float64 `yaml:"minScore,omitempty" json:"minScore,omitempty"`
+ // MinScore optionally filters repositories by minimum score (0-10), specified as a string (e.g., "7.5")
+ MinScore *string `yaml:"minScore,omitempty" json:"minScore,omitempty"`Alternatively, if you prefer to keep the float type, add 📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Actions: Lint[error] 11-11: controller-gen CRD generation failed: found float at line 11:12 in api/v1/openssf.go. Serializing floats as strings is recommended; or re-run with crd:allowDangerousTypes=true. 🪛 GitHub Actions: Test[error] 11-11: controller-gen CRD generation failed: found float, the usage of which is highly discouraged, as support for them varies across languages. Please consider serializing your float as string instead. If you are really sure you want to use them, re-run with crd:allowDangerousTypes=true 🪛 GitHub Check: lint[failure] 11-11: 🪛 GitHub Check: test[failure] 11-11: 🪛 GitHub Check: test-prod[failure] 11-11: 🤖 Prompt for AI Agents |
||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // OpenSSFRepository specifies a repository to assess | ||||||||||||||||||||
| type OpenSSFRepository struct { | ||||||||||||||||||||
| Owner string `yaml:"owner" json:"owner"` | ||||||||||||||||||||
| Repo string `yaml:"repo" json:"repo"` | ||||||||||||||||||||
| } | ||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,8 +68,10 @@ type ScraperSpec struct { | |
| Kubernetes []Kubernetes `json:"kubernetes,omitempty" yaml:"kubernetes,omitempty"` | ||
| KubernetesFile []KubernetesFile `json:"kubernetesFile,omitempty" yaml:"kubernetesFile,omitempty"` | ||
| AzureDevops []AzureDevops `json:"azureDevops,omitempty" yaml:"azureDevops,omitempty"` | ||
| GithubActions []GitHubActions `json:"githubActions,omitempty" yaml:"githubActions,omitempty"` | ||
| Azure []Azure `json:"azure,omitempty" yaml:"azure,omitempty"` | ||
| GithubActions []GitHubActions `json:"githubActions,omitempty" yaml:"githubActions,omitempty"` | ||
| GitHubSecurity []GitHubSecurity `json:"githubSecurity,omitempty" yaml:"githubSecurity,omitempty"` | ||
| OpenSSFScorecard []OpenSSFScorecard `json:"openssfScorecard,omitempty" yaml:"openssfScorecard,omitempty"` | ||
| Azure []Azure `json:"azure,omitempty" yaml:"azure,omitempty"` | ||
|
Comment on lines
+71
to
+74
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing entries in The new
Proposed additionsAdd to var AllScraperConfigs = map[string]any{
"aws": AWS{},
"azure": Azure{},
"azuredevops": AzureDevops{},
"exec": Exec{},
"file": File{},
"gcp": GCP{},
"githubactions": GitHubActions{},
+ "githubsecurity": GitHubSecurity{},
"http": HTTP{},
"kubernetes": Kubernetes{},
"kubernetesfile": KubernetesFile{},
"logs": Logs{},
+ "openssfscorecard": OpenSSFScorecard{},
"slack": Slack{},
"sql": SQL{},
"terraform": Terraform{},
"trivy": Trivy{},
}Add to for i := range spec.GithubActions {
spec.GithubActions[i].BaseScraper = spec.GithubActions[i].BaseScraper.ApplyPlugins(plugins...)
}
+
+ for i := range spec.GitHubSecurity {
+ spec.GitHubSecurity[i].BaseScraper = spec.GitHubSecurity[i].BaseScraper.ApplyPlugins(plugins...)
+ }
+
+ for i := range spec.OpenSSFScorecard {
+ spec.OpenSSFScorecard[i].BaseScraper = spec.OpenSSFScorecard[i].BaseScraper.ApplyPlugins(plugins...)
+ }🤖 Prompt for AI Agents |
||
| SQL []SQL `json:"sql,omitempty" yaml:"sql,omitempty"` | ||
| Slack []Slack `json:"slack,omitempty" yaml:"slack,omitempty"` | ||
| Trivy []Trivy `json:"trivy,omitempty" yaml:"trivy,omitempty"` | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential nil pointer dereference in
IsDebug()andIsTrace().Both methods access
ctx.scrapeConfig.Specwithout checking ifscrapeConfigis nil. Other methods likeScraperID()(lines 144-149) perform this nil check. IfIsDebug()orIsTrace()is called beforeWithScrapeConfig(), this will panic.Suggested fix for IsDebug (same pattern should be applied to IsTrace)
func (ctx ScrapeContext) IsDebug() bool { + if ctx.scrapeConfig == nil { + return false + } return ctx.scrapeConfig.Spec.IsDebug() }📝 Committable suggestion
🤖 Prompt for AI Agents