package security import ( "slices" "dappco.re/go/core" "dappco.re/go/core/i18n" "dappco.re/go/core/io" "forge.lthn.ai/core/cli/pkg/cli" "forge.lthn.ai/core/go-scm/repos" ) var ( // Command flags securityRegistryPath string securityRepo string securitySeverity string securityJSON bool securityTarget string // External repo target (e.g. "wailsapp/wails") ) // AddSecurityCommands adds the 'security' command to the root. // // Example: // // AddSecurityCommands(root) func AddSecurityCommands(root *cli.Command) { secCmd := &cli.Command{ Use: "security", Short: i18n.T("cmd.security.short"), Long: i18n.T("cmd.security.long"), } addAlertsCommand(secCmd) addDepsCommand(secCmd) addScanCommand(secCmd) addSecretsCommand(secCmd) addJobsCommand(secCmd) root.AddCommand(secCmd) } // DependabotAlert represents a Dependabot vulnerability alert. // // Example: // // var alert DependabotAlert type DependabotAlert struct { Number int `json:"number"` State string `json:"state"` Advisory struct { Severity string `json:"severity"` CVEID string `json:"cve_id"` Summary string `json:"summary"` Description string `json:"description"` } `json:"security_advisory"` Dependency struct { Package struct { Name string `json:"name"` Ecosystem string `json:"ecosystem"` } `json:"package"` ManifestPath string `json:"manifest_path"` } `json:"dependency"` SecurityVulnerability struct { Package struct { Name string `json:"name"` Ecosystem string `json:"ecosystem"` } `json:"package"` FirstPatchedVersion struct { Identifier string `json:"identifier"` } `json:"first_patched_version"` VulnerableVersionRange string `json:"vulnerable_version_range"` } `json:"security_vulnerability"` } // CodeScanningAlert represents a code scanning alert. // // Example: // // var alert CodeScanningAlert type CodeScanningAlert struct { Number int `json:"number"` State string `json:"state"` DismissedReason string `json:"dismissed_reason"` Rule struct { ID string `json:"id"` Severity string `json:"severity"` Description string `json:"description"` Tags []string `json:"tags"` } `json:"rule"` Tool struct { Name string `json:"name"` Version string `json:"version"` } `json:"tool"` MostRecentInstance struct { Location struct { Path string `json:"path"` StartLine int `json:"start_line"` EndLine int `json:"end_line"` } `json:"location"` Message struct { Text string `json:"text"` } `json:"message"` } `json:"most_recent_instance"` } // SecretScanningAlert represents a secret scanning alert. // // Example: // // var alert SecretScanningAlert type SecretScanningAlert struct { Number int `json:"number"` State string `json:"state"` SecretType string `json:"secret_type"` Secret string `json:"secret"` PushProtection bool `json:"push_protection_bypassed"` Resolution string `json:"resolution"` } // loadRegistry loads the repository registry. func loadRegistry(registryPath string) (*repos.Registry, error) { if registryPath != "" { reg, err := repos.LoadRegistry(io.Local, registryPath) if err != nil { return nil, cli.Wrap(err, "load registry") } return reg, nil } path, err := repos.FindRegistry(io.Local) if err != nil { return nil, cli.Wrap(err, "find registry") } reg, err := repos.LoadRegistry(io.Local, path) if err != nil { return nil, cli.Wrap(err, "load registry") } return reg, nil } // severityStyle returns the appropriate style for a severity level. func severityStyle(severity string) *cli.AnsiStyle { switch core.Lower(severity) { case "critical": return cli.ErrorStyle case "high": return cli.WarningStyle case "medium": return cli.ValueStyle default: return cli.DimStyle } } // filterBySeverity checks if the severity matches the filter. func filterBySeverity(severity, filter string) bool { if filter == "" { return true } sev := core.Lower(severity) return slices.ContainsFunc(core.Split(core.Lower(filter), ","), func(s string) bool { return core.Trim(s) == sev }) } // getReposToCheck returns the list of repos to check based on flags. func getReposToCheck(reg *repos.Registry, repoFilter string) []*repos.Repo { if repoFilter != "" { if repo, ok := reg.Get(repoFilter); ok { return []*repos.Repo{repo} } return nil } return reg.List() } // buildTargetRepo creates a synthetic Repo entry for an external target (e.g. "wailsapp/wails"). func buildTargetRepo(target string) (*repos.Repo, string) { parts := core.SplitN(target, "/", 2) if len(parts) != 2 || parts[0] == "" || parts[1] == "" { return nil, "" } return &repos.Repo{Name: parts[1]}, target } // AlertSummary holds aggregated alert counts. // // Example: // // summary := &AlertSummary{} type AlertSummary struct { Critical int High int Medium int Low int Unknown int Total int } // Add increments summary counters for the provided severity. // // Example: // // summary.Add("high") func (s *AlertSummary) Add(severity string) { s.Total++ switch core.Lower(severity) { case "critical": s.Critical++ case "high": s.High++ case "medium": s.Medium++ case "low": s.Low++ default: s.Unknown++ } } // String renders a human-readable summary of alert counts. // // Example: // // text := summary.String() func (s *AlertSummary) String() string { return s.renderSummary(func(level, text string) string { switch level { case "critical": return cli.ErrorStyle.Render(text) case "high": return cli.WarningStyle.Render(text) case "medium": return cli.ValueStyle.Render(text) case "none": return cli.SuccessStyle.Render(text) default: return cli.DimStyle.Render(text) } }) } // PlainString renders an unstyled summary suitable for logs and issue bodies. func (s *AlertSummary) PlainString() string { return s.renderSummary(func(_ string, text string) string { return text }) } func (s *AlertSummary) renderSummary(render func(level, text string) string) string { parts := []string{} appendPart := func(level string, count int) { if count > 0 { parts = append(parts, render(level, core.Sprintf("%d %s", count, level))) } } appendPart("critical", s.Critical) appendPart("high", s.High) appendPart("medium", s.Medium) appendPart("low", s.Low) appendPart("unknown", s.Unknown) if len(parts) == 0 { return render("none", "No alerts") } return core.Join(" | ", parts...) }