Skip to content

Commit d90e2d4

Browse files
refactor: merge bounty command into root command with intelligent orchestrator
1 parent 4910084 commit d90e2d4

12 files changed

Lines changed: 2429 additions & 1076 deletions

File tree

cmd/bounty.go

Lines changed: 0 additions & 414 deletions
This file was deleted.

cmd/display_helpers.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// cmd/display_helpers.go - Shared display and formatting helpers
2+
package cmd
3+
4+
import (
5+
"fmt"
6+
7+
"github.com/CodeMonkeyCybersecurity/shells/pkg/types"
8+
"github.com/fatih/color"
9+
)
10+
11+
// Shared display helper functions for all commands
12+
13+
func colorStatus(status string) string {
14+
switch status {
15+
case "completed":
16+
return color.New(color.FgGreen).Sprint("✓ " + status)
17+
case "running":
18+
return color.New(color.FgYellow).Sprint("⟳ " + status)
19+
case "failed":
20+
return color.New(color.FgRed).Sprint("✗ " + status)
21+
default:
22+
return status
23+
}
24+
}
25+
26+
func colorPhaseStatus(status string) string {
27+
switch status {
28+
case "completed":
29+
return color.New(color.FgGreen).Sprint("✓")
30+
case "running":
31+
return color.New(color.FgYellow).Sprint("⟳")
32+
case "failed":
33+
return color.New(color.FgRed).Sprint("✗")
34+
default:
35+
return "○"
36+
}
37+
}
38+
39+
func colorSeverity(severity types.Severity) string {
40+
switch severity {
41+
case types.SeverityCritical:
42+
return color.New(color.FgRed, color.Bold).Sprint("CRITICAL")
43+
case types.SeverityHigh:
44+
return color.New(color.FgRed).Sprint("HIGH")
45+
case types.SeverityMedium:
46+
return color.New(color.FgYellow).Sprint("MEDIUM")
47+
case types.SeverityLow:
48+
return color.New(color.FgCyan).Sprint("LOW")
49+
case types.SeverityInfo:
50+
return color.New(color.FgWhite).Sprint("INFO")
51+
default:
52+
return string(severity)
53+
}
54+
}
55+
56+
func groupFindingsBySeverity(findings []types.Finding) map[types.Severity]int {
57+
counts := make(map[types.Severity]int)
58+
for _, finding := range findings {
59+
counts[finding.Severity]++
60+
}
61+
return counts
62+
}
63+
64+
func displayTopFindings(findings []types.Finding, limit int) {
65+
// Sort by severity (critical first)
66+
sortedFindings := make([]types.Finding, len(findings))
67+
copy(sortedFindings, findings)
68+
69+
// Simple sort: critical, high, medium, low, info
70+
severityOrder := map[types.Severity]int{
71+
types.SeverityCritical: 0,
72+
types.SeverityHigh: 1,
73+
types.SeverityMedium: 2,
74+
types.SeverityLow: 3,
75+
types.SeverityInfo: 4,
76+
}
77+
78+
// Bubble sort by severity
79+
for i := 0; i < len(sortedFindings); i++ {
80+
for j := i + 1; j < len(sortedFindings); j++ {
81+
if severityOrder[sortedFindings[i].Severity] > severityOrder[sortedFindings[j].Severity] {
82+
sortedFindings[i], sortedFindings[j] = sortedFindings[j], sortedFindings[i]
83+
}
84+
}
85+
}
86+
87+
count := 0
88+
for _, finding := range sortedFindings {
89+
if count >= limit {
90+
break
91+
}
92+
93+
fmt.Printf("\n%s - %s\n", colorSeverity(finding.Severity), finding.Title)
94+
fmt.Printf(" Tool: %s | Type: %s\n", finding.Tool, finding.Type)
95+
96+
if finding.Description != "" {
97+
// Truncate description if too long
98+
desc := finding.Description
99+
if len(desc) > 150 {
100+
desc = desc[:147] + "..."
101+
}
102+
fmt.Printf(" %s\n", desc)
103+
}
104+
105+
if finding.Evidence != "" {
106+
// Show first line of evidence
107+
evidence := finding.Evidence
108+
if len(evidence) > 100 {
109+
evidence = evidence[:97] + "..."
110+
}
111+
fmt.Printf(" Evidence: %s\n", evidence)
112+
}
113+
114+
count++
115+
}
116+
}
117+
118+
// noopTelemetry is a no-op implementation of core.Telemetry
119+
// Shared across commands that need telemetry but don't have a real implementation yet
120+
type noopTelemetry struct{}
121+
122+
func (n *noopTelemetry) RecordScan(scanType types.ScanType, duration float64, success bool) {}
123+
func (n *noopTelemetry) RecordFinding(severity types.Severity) {}
124+
func (n *noopTelemetry) RecordWorkerMetrics(status *types.WorkerStatus) {}
125+
func (n *noopTelemetry) Close() error { return nil }

cmd/hunt.go

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
// cmd/hunt.go - Clean CLI wrapper for bug bounty orchestrator
2+
package cmd
3+
4+
import (
5+
"context"
6+
"fmt"
7+
"time"
8+
9+
"github.com/CodeMonkeyCybersecurity/shells/internal/config"
10+
"github.com/CodeMonkeyCybersecurity/shells/internal/database"
11+
"github.com/CodeMonkeyCybersecurity/shells/internal/logger"
12+
"github.com/CodeMonkeyCybersecurity/shells/internal/orchestrator"
13+
"github.com/CodeMonkeyCybersecurity/shells/pkg/types"
14+
"github.com/fatih/color"
15+
"github.com/spf13/cobra"
16+
)
17+
18+
var huntCmd = &cobra.Command{
19+
Use: "hunt [target]",
20+
Short: "[DEPRECATED] Run optimized bug bounty hunting pipeline - use 'shells [target]' instead",
21+
Long: `[DEPRECATED] This command is deprecated. Use the main command instead:
22+
shells example.com
23+
24+
The hunt command is now redundant with the main shells command, which provides
25+
the same intelligent orchestration pipeline.
26+
27+
RECOMMENDATION: Use 'shells [target]' directly for bug bounty hunting.
28+
29+
Legacy documentation:
30+
1. Smart asset discovery (time-boxed to 30s)
31+
2. Asset prioritization (auth endpoints, APIs, admin panels)
32+
3. Parallel vulnerability testing (SAML, OAuth2, WebAuthn, SCIM, etc.)
33+
4. Structured results storage and reporting`,
34+
Args: cobra.ExactArgs(1),
35+
RunE: runHuntCommand,
36+
Deprecated: "use the main command 'shells [target]' instead, which provides the same functionality",
37+
}
38+
39+
func init() {
40+
rootCmd.AddCommand(huntCmd)
41+
42+
// Timeout configurations
43+
huntCmd.Flags().Duration("discovery-timeout", 30*time.Second, "Max time for asset discovery")
44+
huntCmd.Flags().Duration("scan-timeout", 5*time.Minute, "Max time for vulnerability scanning")
45+
huntCmd.Flags().Duration("total-timeout", 10*time.Minute, "Max time for entire pipeline")
46+
47+
// Discovery settings
48+
huntCmd.Flags().Int("max-assets", 50, "Maximum number of assets to discover")
49+
huntCmd.Flags().Int("max-depth", 1, "Maximum crawl depth for discovery")
50+
huntCmd.Flags().Bool("enable-port-scan", true, "Enable port scanning during discovery")
51+
huntCmd.Flags().Bool("enable-web-crawl", true, "Enable web crawling during discovery")
52+
huntCmd.Flags().Bool("enable-dns", false, "Enable DNS enumeration (slow)")
53+
54+
// Testing toggles
55+
huntCmd.Flags().Bool("enable-auth-testing", true, "Enable authentication vulnerability testing")
56+
huntCmd.Flags().Bool("enable-api-testing", true, "Enable API security testing")
57+
huntCmd.Flags().Bool("enable-logic-testing", true, "Enable business logic testing")
58+
huntCmd.Flags().Bool("enable-ssrf-testing", true, "Enable SSRF testing")
59+
huntCmd.Flags().Bool("enable-access-control", true, "Enable access control testing")
60+
huntCmd.Flags().Bool("enable-scim-testing", true, "Enable SCIM vulnerability testing")
61+
62+
// Output settings
63+
huntCmd.Flags().Bool("show-progress", true, "Show real-time progress indicators")
64+
huntCmd.Flags().Bool("verbose", false, "Enable verbose output")
65+
huntCmd.Flags().String("output", "", "Save detailed report to file (JSON)")
66+
}
67+
68+
func runHuntCommand(cmd *cobra.Command, args []string) error {
69+
// Display deprecation warning
70+
color.Yellow("\n⚠️ DEPRECATION WARNING: The 'hunt' command is deprecated.\n")
71+
color.White(" Use 'shells %s' instead for the same functionality.\n\n", args[0])
72+
73+
target := args[0]
74+
75+
// Parse flags
76+
discoveryTimeout, _ := cmd.Flags().GetDuration("discovery-timeout")
77+
scanTimeout, _ := cmd.Flags().GetDuration("scan-timeout")
78+
totalTimeout, _ := cmd.Flags().GetDuration("total-timeout")
79+
maxAssets, _ := cmd.Flags().GetInt("max-assets")
80+
maxDepth, _ := cmd.Flags().GetInt("max-depth")
81+
enablePortScan, _ := cmd.Flags().GetBool("enable-port-scan")
82+
enableWebCrawl, _ := cmd.Flags().GetBool("enable-web-crawl")
83+
enableDNS, _ := cmd.Flags().GetBool("enable-dns")
84+
enableAuthTesting, _ := cmd.Flags().GetBool("enable-auth-testing")
85+
enableAPITesting, _ := cmd.Flags().GetBool("enable-api-testing")
86+
enableLogicTesting, _ := cmd.Flags().GetBool("enable-logic-testing")
87+
enableSSRFTesting, _ := cmd.Flags().GetBool("enable-ssrf-testing")
88+
enableAccessControl, _ := cmd.Flags().GetBool("enable-access-control")
89+
enableSCIMTesting, _ := cmd.Flags().GetBool("enable-scim-testing")
90+
showProgress, _ := cmd.Flags().GetBool("show-progress")
91+
verbose, _ := cmd.Flags().GetBool("verbose")
92+
outputFile, _ := cmd.Flags().GetString("output")
93+
94+
// Initialize logger
95+
logLevel := "error"
96+
if verbose {
97+
logLevel = "debug"
98+
}
99+
log, err := logger.New(config.LoggerConfig{
100+
Level: logLevel,
101+
Format: "console",
102+
})
103+
if err != nil {
104+
return fmt.Errorf("failed to initialize logger: %w", err)
105+
}
106+
107+
// Initialize database
108+
dbConfig := config.DatabaseConfig{
109+
Driver: "sqlite3",
110+
DSN: "shells.db",
111+
}
112+
store, err := database.NewStore(dbConfig)
113+
if err != nil {
114+
return fmt.Errorf("failed to initialize database: %w", err)
115+
}
116+
defer store.Close()
117+
118+
// Initialize telemetry (no-op for now)
119+
telemetry := &noopTelemetry{}
120+
121+
// Create bug bounty configuration
122+
bountyConfig := orchestrator.BugBountyConfig{
123+
DiscoveryTimeout: discoveryTimeout,
124+
ScanTimeout: scanTimeout,
125+
TotalTimeout: totalTimeout,
126+
MaxAssets: maxAssets,
127+
MaxDepth: maxDepth,
128+
EnablePortScan: enablePortScan,
129+
EnableWebCrawl: enableWebCrawl,
130+
EnableDNS: enableDNS,
131+
EnableAuthTesting: enableAuthTesting,
132+
EnableAPITesting: enableAPITesting,
133+
EnableLogicTesting: enableLogicTesting,
134+
EnableSSRFTesting: enableSSRFTesting,
135+
EnableAccessControl: enableAccessControl,
136+
EnableSCIMTesting: enableSCIMTesting,
137+
ShowProgress: showProgress,
138+
Verbose: verbose,
139+
}
140+
141+
// Initialize orchestrator
142+
engine, err := orchestrator.NewBugBountyEngine(store, telemetry, log, bountyConfig)
143+
if err != nil {
144+
return fmt.Errorf("failed to initialize bug bounty engine: %w", err)
145+
}
146+
147+
// Print banner
148+
printHuntBanner(target)
149+
150+
// Execute bug bounty pipeline
151+
ctx := context.Background()
152+
result, err := engine.Execute(ctx, target)
153+
if err != nil {
154+
return fmt.Errorf("bug bounty scan failed: %w", err)
155+
}
156+
157+
// Display results
158+
displayHuntResults(result)
159+
160+
// Save detailed output if requested
161+
if outputFile != "" {
162+
if err := saveHuntReport(result, outputFile); err != nil {
163+
log.Errorw("Failed to save report", "error", err, "file", outputFile)
164+
} else {
165+
fmt.Printf("\n✓ Detailed report saved to: %s\n", outputFile)
166+
}
167+
}
168+
169+
return nil
170+
}
171+
172+
func printHuntBanner(target string) {
173+
blue := color.New(color.FgCyan, color.Bold)
174+
fmt.Println()
175+
fmt.Println("══════════════════════════════════════════════════════════════════════")
176+
blue.Println("🎯 Shells Bug Bounty Hunter")
177+
fmt.Printf(" Target: %s\n", target)
178+
fmt.Printf(" Time: %s\n", time.Now().Format("15:04:05"))
179+
fmt.Println("══════════════════════════════════════════════════════════════════════")
180+
fmt.Println()
181+
}
182+
183+
func displayHuntResults(result *orchestrator.BugBountyResult) {
184+
fmt.Println()
185+
fmt.Println("═══ Scan Summary ═══")
186+
fmt.Printf("Status: %s\n", colorStatus(result.Status))
187+
fmt.Printf("Duration: %s\n", result.Duration.Round(time.Second))
188+
fmt.Printf("Assets Discovered: %d\n", result.DiscoveredAt)
189+
fmt.Printf("Assets Tested: %d\n", result.TestedAssets)
190+
fmt.Printf("Total Findings: %d\n", result.TotalFindings)
191+
fmt.Println()
192+
193+
// Display phase results
194+
if len(result.PhaseResults) > 0 {
195+
fmt.Println("═══ Phase Results ═══")
196+
for phase, pr := range result.PhaseResults {
197+
status := colorPhaseStatus(pr.Status)
198+
fmt.Printf("[%s] %s - %s (%d findings, %s)\n",
199+
status,
200+
phase,
201+
pr.Status,
202+
pr.Findings,
203+
pr.Duration.Round(time.Second),
204+
)
205+
if pr.Error != "" {
206+
color.New(color.FgRed).Printf(" Error: %s\n", pr.Error)
207+
}
208+
}
209+
fmt.Println()
210+
}
211+
212+
// Display findings by severity
213+
if len(result.Findings) > 0 {
214+
fmt.Println("═══ Findings by Severity ═══")
215+
bySeverity := groupFindingsBySeverity(result.Findings)
216+
for _, severity := range []types.Severity{
217+
types.SeverityCritical,
218+
types.SeverityHigh,
219+
types.SeverityMedium,
220+
types.SeverityLow,
221+
types.SeverityInfo,
222+
} {
223+
count := bySeverity[severity]
224+
if count > 0 {
225+
fmt.Printf("%s: %d\n", colorSeverity(severity), count)
226+
}
227+
}
228+
fmt.Println()
229+
230+
// Display top findings
231+
fmt.Println("═══ Top Findings ═══")
232+
displayTopFindings(result.Findings, 5)
233+
} else {
234+
color.New(color.FgGreen).Println("✓ No vulnerabilities found")
235+
}
236+
237+
fmt.Println()
238+
fmt.Printf("✓ Scan complete in %s\n", result.Duration.Round(time.Second))
239+
fmt.Printf(" Scan ID: %s\n", result.ScanID)
240+
}
241+
242+
243+
func saveHuntReport(result *orchestrator.BugBountyResult, filename string) error {
244+
// TODO: Implement JSON export
245+
return fmt.Errorf("report export not yet implemented")
246+
}
247+
248+
// Note: Helper functions (colorStatus, colorSeverity, displayTopFindings, noopTelemetry, etc.)
249+
// are now in display_helpers.go to avoid duplication across commands

0 commit comments

Comments
 (0)