Skip to content
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
4 changes: 4 additions & 0 deletions api/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 161 to +167
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential nil pointer dereference in IsDebug() and IsTrace().

Both methods access ctx.scrapeConfig.Spec without checking if scrapeConfig is nil. Other methods like ScraperID() (lines 144-149) perform this nil check. If IsDebug() or IsTrace() is called before WithScrapeConfig(), 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (ctx ScrapeContext) IsTrace() bool {
return ctx.scrapeConfig.Spec.IsTrace()
}
func (ctx ScrapeContext) IsDebug() bool {
return ctx.scrapeConfig.Spec.IsDebug()
}
func (ctx ScrapeContext) IsTrace() bool {
return ctx.scrapeConfig.Spec.IsTrace()
}
func (ctx ScrapeContext) IsDebug() bool {
if ctx.scrapeConfig == nil {
return false
}
return ctx.scrapeConfig.Spec.IsDebug()
}
🤖 Prompt for AI Agents
In `@api/context.go` around lines 161 - 167, IsDebug() and IsTrace() may
dereference a nil scrapeConfig; update both methods to guard against nil (and
nil Spec if Spec is a pointer) similarly to ScraperID(): check if
ctx.scrapeConfig == nil (and ctx.scrapeConfig.Spec == nil when applicable) and
return false if nil, otherwise call and return ctx.scrapeConfig.Spec.IsDebug() /
IsTrace(); this prevents panics when WithScrapeConfig() hasn't been called.

Comment on lines +165 to +167
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard against nil ScrapeConfig.
NewScrapeContext can produce a context without scrapeConfig; calling IsDebug() would panic. Return false when unset.

🐛 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (ctx ScrapeContext) IsDebug() bool {
return ctx.scrapeConfig.Spec.IsDebug()
}
func (ctx ScrapeContext) IsDebug() bool {
if ctx.scrapeConfig == nil {
return false
}
return ctx.scrapeConfig.Spec.IsDebug()
}
🤖 Prompt for AI Agents
In `@api/context.go` around lines 165 - 167, The IsDebug method on ScrapeContext
can panic if scrapeConfig is nil; update ScrapeContext.IsDebug() to guard
against a nil ctx.scrapeConfig and return false when unset, otherwise return
ctx.scrapeConfig.Spec.IsDebug(); reference the ScrapeContext type and IsDebug()
method and ensure compatibility with NewScrapeContext which may produce a
context without scrapeConfig.

47 changes: 47 additions & 0 deletions api/v1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import (
"regexp"
"strings"

"github.com/flanksource/clicky"
"github.com/flanksource/clicky/api"
"github.com/flanksource/duty"
"github.com/flanksource/duty/connection"
"github.com/flanksource/duty/models"
"github.com/flanksource/duty/types"
"github.com/flanksource/gomplate/v3"
"github.com/samber/lo"
)

// List of types which should not have scraper_id
Expand Down Expand Up @@ -63,6 +66,34 @@ func (s Script) String() string {
return ""
}

func (s Script) PrettyShort() api.Text {
t := clicky.Text("")
if s.GoTemplate != "" {
t = t.Append("go: ", "text-green-600").Append(clicky.CodeBlock("go", lo.Ellipsis(s.GoTemplate, 200)))
} else if s.JSONPath != "" {
t = t.Append("jsonpath: ", "text-blue-600").Append(clicky.CodeBlock("jsonpath", lo.Ellipsis(s.JSONPath, 200)))
} else if s.Expression != "" {
t = t.Append("expr: ", "text-yellow-600").Append(clicky.CodeBlock("cel", lo.Ellipsis(s.Expression, 200)))
} else if s.Javascript != "" {
t = t.Append("js: ", "text-purple-600").Append(clicky.CodeBlock("javascript", lo.Ellipsis(s.Javascript, 200)))
}
return t
}

func (s Script) Pretty() api.Text {
t := clicky.Text("")
if s.GoTemplate != "" {
t = t.Append("go: ", "text-green-600").Append(clicky.CodeBlock("go", s.GoTemplate))
} else if s.JSONPath != "" {
t = t.Append("jsonpath: ", "text-blue-600").Append(clicky.CodeBlock("jsonpath", s.JSONPath))
} else if s.Expression != "" {
t = t.Append("expr: ", "text-yellow-600").Append(clicky.CodeBlock("cel", s.Expression))
} else if s.Javascript != "" {
t = t.Append("js: ", "text-purple-600").Append(clicky.CodeBlock("javascript", s.Javascript))
}
return t
}

type Mask struct {
// Selector is a CEL expression that selects on what config items to apply the mask.
Selector string `json:"selector,omitempty"`
Expand Down Expand Up @@ -110,6 +141,13 @@ type ChangeMapping struct {
Action ChangeAction `json:"action,omitempty"`
// Summary replaces the existing change summary.
Summary string `json:"summary,omitempty"`
// ConfigID is a CEL expression that returns the target config's external ID
// for redirecting changes to a different config item.
ConfigID string `json:"config_id,omitempty"`
// ConfigType is the target config type for redirecting changes.
ConfigType string `json:"config_type,omitempty"`
// ScraperID is the scraper ID for the target config. Use "all" for cross-scraper lookups.
ScraperID string `json:"scraper_id,omitempty"`
}

type TransformChange struct {
Expand Down Expand Up @@ -389,6 +427,15 @@ func (c Connection) GetEndpoint() string {
return sanitizeEndpoints(c.Connection)
}

func (c Connection) Pretty() api.Text {
t := clicky.Text("")
if c.Connection != "" {
clicky.RedactSecretValues()
t = t.Append(sanitizeEndpoints(c.Connection))
}
return t
}

// Obfuscate passwords of the form ' password=xxxxx ' from connectionString since
// connectionStrings are used as metric labels and we don't want to leak passwords
// Returns the Connection string with the password replaced by '###'
Expand Down
53 changes: 53 additions & 0 deletions api/v1/github_security.go
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
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Go time.ParseDuration supported time units documentation

💡 Result:

In Go’s standard library, time.ParseDuration accepts these unit suffixes (as documented):

  • ns
  • us (or µs)
  • ms
  • s
  • m
  • h (hours)

Docs: time.ParseDuration on pkg.go.dev/time (see “Valid time units…”). [1]

Examples: 300ms, -1.5h, 2h45m, 1µs / 1us. [1]

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 -100

Repository: 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


ParseMaxAge cannot handle day units—time.ParseDuration only accepts ns, us, ms, s, m, h.

If the documentation or code examples suggest using "90d" or "30d", they will fail. Either update examples to hours (e.g., 2160h for 90 days) or implement custom day parsing.

🛠️ 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
In `@api/v1/github_security.go` around lines 43 - 52, ParseMaxAge currently calls
time.ParseDuration which doesn't accept the "d" day suffix; update
GitHubSecurityFilters.ParseMaxAge to detect a trailing "d" (case-insensitive),
parse the numeric portion as an integer or float representing days, convert to a
time.Duration via days * 24 * time.Hour, and return that; for all other inputs
keep using time.ParseDuration and propagate its error. Ensure the implementation
handles empty MaxAge (already returns 0,nil), returns a clear error on invalid
numeric parsing, and references the MaxAge field and ParseMaxAge method when
making the change.

}
Comment on lines +47 to +53
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the github_security.go file
find . -name "github_security.go" -type f

Repository: 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 security

Repository: flanksource/config-db

Length of output: 567


🏁 Script executed:

# Search for references to "90d" and "30d" in the codebase
rg "90d|30d" -i

Repository: 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 -60

Repository: 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/null

Repository: flanksource/config-db

Length of output: 237


🌐 Web query:

Go time.ParseDuration supported format "d" day suffix

💡 Result:

Go’s standard library time.ParseDuration does not support a d (day) suffix—the only valid unit suffixes are ns, us (or µs), ms, s, m, h. [1]

The time package also intentionally provides no Day constant (“to avoid confusion across daylight savings time zone transitions”). [1]

What to do instead

  • If you mean exact 24-hour blocks, use hours: 24h, 48h, 720h (for 30 days), etc. [1]
  • If you mean calendar days (variable-length due to DST), use date math like t.AddDate(0, 0, days) rather than time.Duration.

🏁 Script executed:

# Search for tests of ParseMaxAge or any usages of it
rg "ParseMaxAge" -i -A 3 -B 3

Repository: 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


ParseMaxAge rejects the documented 90d/30d inputs.

Line 43 documents examples "90d" and "30d", but line 52 uses time.ParseDuration, which doesn't accept a d suffix. The only supported unit suffixes are ns, us, µs, ms, s, m, h. Either update the documentation to reflect supported formats (e.g., "2160h" for 90 days, "720h" for 30 days) or implement custom parsing to support the d suffix.

🤖 Prompt for AI Agents
In `@api/v1/github_security.go` around lines 47 - 53, The ParseMaxAge method in
GitHubSecurityFilters currently calls time.ParseDuration which rejects
"90d"/"30d" as documented; change ParseMaxAge to accept a trailing 'd' by
detecting a suffix of 'd' (case-insensitive), parsing the numeric portion as an
integer/float, converting days to hours (days * 24h) and returning time.Duration
(or fall back to time.ParseDuration for other valid suffixes), and ensure errors
are returned for invalid inputs; update logic inside ParseMaxAge to handle both
"Nd" inputs and standard time.ParseDuration-compatible strings and reference the
GitHubSecurityFilters.ParseMaxAge function when locating the change.

158 changes: 158 additions & 0 deletions api/v1/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 -5

Repository: flanksource/config-db

Length of output: 84


🏁 Script executed:

# Read the specific section of the file
wc -l api/v1/interface.go

Repository: flanksource/config-db

Length of output: 89


🏁 Script executed:

# Get the exact content around lines 815-831
sed -n '810,835p' api/v1/interface.go

Repository: 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 go

Repository: 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 5

Repository: flanksource/config-db

Length of output: 1339


🏁 Script executed:

# Search for PrettyRow interface definition
rg "type PrettyRow" --type go -B 2 -A 5

Repository: 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 2

Repository: flanksource/config-db

Length of output: 47


🏁 Script executed:

# Search for other types that implement PrettyRow method
rg "func \(.+\) PrettyRow" --type go

Repository: flanksource/config-db

Length of output: 159


🏁 Script executed:

# Check if there are any tests for this method
rg "PrettyRow" --type go | grep -i test

Repository: flanksource/config-db

Length of output: 47


🏁 Script executed:

# Search for api.PrettyRow usage
rg "api\.PrettyRow" --type go -B 2 -A 2

Repository: 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/null

Repository: flanksource/config-db

Length of output: 49


🏁 Script executed:

# Search more broadly for what might call PrettyRow
rg "PrettyRow" --type go

Repository: 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 go

Repository: 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 -50

Repository: 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 5

Repository: 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 2

Repository: flanksource/config-db

Length of output: 47


🏁 Script executed:

# Look for any reflection or dynamic calls to PrettyRow
rg '"PrettyRow"' --type go

Repository: flanksource/config-db

Length of output: 47


🏁 Script executed:

# Search in the api package directly
fd -type f -path "*/api*" -name "*.go" | head -20

Repository: 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 3

Repository: 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 -10

Repository: 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 -50

Repository: 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 -20

Repository: 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 -50

Repository: 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 20

Repository: 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 5

Repository: 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 5

Repository: 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.go

Repository: 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}")
EOF

Repository: 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)
EOF

Repository: flanksource/config-db

Length of output: 136


🏁 Script executed:

# Let's examine the complete MergeScrapeResults function
sed -n '846,900p' api/v1/interface.go

Repository: flanksource/config-db

Length of output: 1194


🏁 Script executed:

# Check for any explicit calls to PrettyRow method
rg "\.PrettyRow\(" --type go

Repository: 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.go

Repository: 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)
EOF

Repository: 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 60

Repository: 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])
EOF

Repository: 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 clicky

Repository: 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")
EOF

Repository: 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)
EOF

Repository: flanksource/config-db

Length of output: 93


Remove unconditional prints from PrettyRow method.

The two clicky.MustPrint calls on lines 827–828 execute whenever this method is invoked, causing duplicate output and preventing callers from controlling formatting. The options parameter is also unused. Let the caller decide when and how to print by returning only the row data.

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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
}
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("")
}
return row
}
🤖 Prompt for AI Agents
In `@api/v1/interface.go` around lines 815 - 831, The PrettyRow method
(ScrapeResult.PrettyRow) currently performs unconditional printing via
clicky.MustPrint and accepts an unused options parameter; remove the two
clicky.MustPrint calls so the method only constructs and returns the row map,
and drop the unused options parameter from the function signature (or if you
prefer to keep it, ensure it's used) so callers control printing/formatting.
Update any call sites of ScrapeResult.PrettyRow to handle printing/formatting
themselves (e.g., call clicky.MustPrint with desired FormatOptions where
needed).


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
Expand Down Expand Up @@ -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 {
Expand Down
18 changes: 18 additions & 0 deletions api/v1/openssf.go
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

View workflow job for this annotation

GitHub Actions / lint

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

Check failure on line 11 in api/v1/openssf.go

View workflow job for this annotation

GitHub Actions / test-prod

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

Check failure on line 11 in api/v1/openssf.go

View workflow job for this annotation

GitHub Actions / test

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
Comment on lines +10 to +11
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix float type to unblock CRD generation.

The pipeline is failing because controller-gen disallows float types by default due to cross-language serialization inconsistencies. Consider using a string type with validation, or add the // +kubebuilder:validation:Type=number marker with crd:allowDangerousTypes=true in the generator invocation.

🔧 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 controller-gen invocation to include crd:allowDangerousTypes=true.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 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"`
🧰 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:
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: test

[failure] 11-11:
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: test-prod

[failure] 11-11:
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

🤖 Prompt for AI Agents
In `@api/v1/openssf.go` around lines 10 - 11, The MinScore field currently uses
*float64 which breaks controller-gen; change MinScore to a string pointer (e.g.,
*string) and add validation/tagging as needed in the struct, then update the
scraper logic to parse the string into a float64 (handle nil/parse errors)
wherever MinScore is consumed; alternatively, if you must keep a float, add the
kubebuilder CRD marker // +kubebuilder:validation:Type=number and ensure
controller-gen is invoked with crd:allowDangerousTypes=true—locate and update
the MinScore field in the api/v1/openssf.go struct for these changes.

Comment on lines +10 to +11
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix float type to resolve pipeline failures.

The *float64 type causes CRD generation to fail. Kubernetes controller-gen discourages floats due to inconsistent serialization across languages.

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 // +kubebuilder:validation:Type=number marker and re-run controller-gen with crd:allowDangerousTypes=true, though this is not recommended for portability.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 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"`
🧰 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:
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: test

[failure] 11-11:
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: test-prod

[failure] 11-11:
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

🤖 Prompt for AI Agents
In `@api/v1/openssf.go` around lines 10 - 11, The MinScore field currently uses
*float64 which breaks CRD generation; change MinScore to a safer type (e.g.,
*string or *int) and update any callers/serialization accordingly: replace the
declaration of MinScore (*float64) with either *string (to store "7.5") or *int
representing a scaled value (e.g., 75 for 0.0–10.0 scaled to 0–100), keep the
existing yaml/json tags, and adjust validation/usage logic that reads MinScore
(parsing string to float or dividing the int by the scale) so consumers like
validation, defaults, and API docs remain correct; if you must keep a numeric
type, add the kubebuilder marker // +kubebuilder:validation:Type=number and
rerun controller-gen with crd:allowDangerousTypes=true (not recommended).

}

// OpenSSFRepository specifies a repository to assess
type OpenSSFRepository struct {
Owner string `yaml:"owner" json:"owner"`
Repo string `yaml:"repo" json:"repo"`
}
14 changes: 14 additions & 0 deletions api/v1/scrapeconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ func (t ScrapeConfig) GetContext() map[string]any {
}
}

func (t ScrapeConfig) IsDebug() bool {
return strings.EqualFold(t.Annotations["debug"], "true")
}

func (t ScrapeConfig) IsTrace() bool {
return strings.EqualFold(t.Annotations["trace"], "true")
}

func (t ScrapeConfig) NamespaceScope() string {
return t.Namespace
}
Expand All @@ -93,6 +101,12 @@ func (t *ScrapeConfig) Type() string {
if len(t.Spec.GithubActions) != 0 {
return "githubactions"
}
if len(t.Spec.GitHubSecurity) != 0 {
return "githubsecurity"
}
if len(t.Spec.OpenSSFScorecard) != 0 {
return "openssf"
}
if len(t.Spec.Azure) != 0 {
return "azure"
}
Expand Down
6 changes: 4 additions & 2 deletions api/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing entries in AllScraperConfigs map and ApplyPlugin method.

The new GitHubSecurity and OpenSSFScorecard fields are added to ScraperSpec, but the corresponding entries are missing from:

  1. AllScraperConfigs map (lines 15-31) - should include "githubsecurity": GitHubSecurity{} and "openssfscorecard": OpenSSFScorecard{}
  2. ApplyPlugin method (lines 97-167) - should include plugin application loops for both new types, similar to other scrapers
Proposed additions

Add to AllScraperConfigs map:

 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 ApplyPlugin method (after line 132):

 	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
In `@api/v1/types.go` around lines 71 - 74, The ScraperSpec additions (fields
GitHubSecurity and OpenSSFScorecard) are not wired into AllScraperConfigs and
ApplyPlugin; update the AllScraperConfigs map to include entries
"githubsecurity": GitHubSecurity{} and "openssfscorecard": OpenSSFScorecard{},
and modify the ApplyPlugin method to iterate over s.GitHubSecurity and
s.OpenSSFScorecard and call ApplyPlugin on each item (mirroring the existing
loops for GithubActions and Azure) so those plugin configs are applied;
reference the AllScraperConfigs map and the ApplyPlugin method and the types
GitHubSecurity and OpenSSFScorecard when locating code to change.

SQL []SQL `json:"sql,omitempty" yaml:"sql,omitempty"`
Slack []Slack `json:"slack,omitempty" yaml:"slack,omitempty"`
Trivy []Trivy `json:"trivy,omitempty" yaml:"trivy,omitempty"`
Expand Down
Loading
Loading