@shakecodeslikecray/whiterose 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -644,14 +644,24 @@ var ClaudeCodeExecutor = class {
644
644
  }
645
645
  async runPrompt(prompt, options) {
646
646
  const claudeCommand = getProviderCommand("claude-code");
647
+ if (process.env.WHITEROSE_DEBUG) {
648
+ console.log("\n[DEBUG] Running Claude Code command:", claudeCommand);
649
+ console.log("[DEBUG] Prompt length:", prompt.length);
650
+ console.log("[DEBUG] CWD:", options.cwd);
651
+ console.log("[DEBUG] First 500 chars of prompt:");
652
+ console.log(prompt.substring(0, 500));
653
+ }
647
654
  try {
648
655
  const { stdout, stderr } = await execa(
649
656
  claudeCommand,
650
657
  [
651
658
  "-p",
652
659
  prompt,
653
- "--dangerously-skip-permissions"
660
+ "--dangerously-skip-permissions",
654
661
  // Allow file reads without prompts
662
+ "--output-format",
663
+ "text"
664
+ // Ensure non-interactive output
655
665
  ],
656
666
  {
657
667
  cwd: options.cwd,
@@ -660,7 +670,9 @@ var ClaudeCodeExecutor = class {
660
670
  ...process.env,
661
671
  NO_COLOR: "1"
662
672
  },
663
- reject: false
673
+ reject: false,
674
+ stdin: "ignore"
675
+ // Prevent waiting for stdin
664
676
  }
665
677
  );
666
678
  if (stderr) {
@@ -677,6 +689,13 @@ var ClaudeCodeExecutor = class {
677
689
  throw new Error(`Claude Code error: ${stderr.substring(0, 200)}`);
678
690
  }
679
691
  }
692
+ if (process.env.WHITEROSE_DEBUG) {
693
+ console.log("\n[DEBUG] Claude Code stdout length:", stdout?.length || 0);
694
+ console.log("[DEBUG] Claude Code stderr:", stderr?.substring(0, 300) || "(none)");
695
+ console.log("[DEBUG] First 1000 chars of stdout:");
696
+ console.log(stdout?.substring(0, 1e3) || "(empty)");
697
+ console.log("[DEBUG] End response\n");
698
+ }
680
699
  return {
681
700
  output: stdout || "",
682
701
  error: stderr || void 0
@@ -780,6 +799,17 @@ var GeminiExecutor = class {
780
799
  reject: false
781
800
  }
782
801
  );
802
+ if (stderr) {
803
+ if (stderr.includes("429") || stderr.includes("rate limit") || stderr.includes("quota exceeded")) {
804
+ throw new Error("Gemini API rate limit reached. Try again later.");
805
+ }
806
+ if (stderr.includes("401") || stderr.includes("403") || stderr.includes("unauthorized") || stderr.includes("invalid api key")) {
807
+ throw new Error("Gemini API authentication failed. Check your API key.");
808
+ }
809
+ if ((stderr.includes("Error") || stderr.includes("error")) && !stdout) {
810
+ throw new Error(`Gemini error: ${stderr.substring(0, 200)}`);
811
+ }
812
+ }
783
813
  return {
784
814
  output: stdout || "",
785
815
  error: stderr || void 0
@@ -820,6 +850,20 @@ var AiderExecutor = class {
820
850
  reject: false
821
851
  }
822
852
  );
853
+ if (stderr) {
854
+ if (stderr.includes("429") || stderr.includes("rate limit") || stderr.includes("RateLimitError")) {
855
+ throw new Error("Aider API rate limit reached. Try again later.");
856
+ }
857
+ if (stderr.includes("401") || stderr.includes("AuthenticationError") || stderr.includes("invalid api key")) {
858
+ throw new Error("Aider API authentication failed. Check your API key.");
859
+ }
860
+ if (stderr.includes("402") || stderr.includes("insufficient") || stderr.includes("billing")) {
861
+ throw new Error("Aider API billing error. Check your account credits.");
862
+ }
863
+ if ((stderr.includes("Error") || stderr.includes("error")) && !stdout) {
864
+ throw new Error(`Aider error: ${stderr.substring(0, 200)}`);
865
+ }
866
+ }
823
867
  return {
824
868
  output: stdout || "",
825
869
  error: stderr || void 0
@@ -859,6 +903,20 @@ var OllamaExecutor = class {
859
903
  reject: false
860
904
  }
861
905
  );
906
+ if (stderr) {
907
+ if (stderr.includes("connection refused") || stderr.includes("ECONNREFUSED")) {
908
+ throw new Error("Ollama server not running. Start it with: ollama serve");
909
+ }
910
+ if (stderr.includes("model") && (stderr.includes("not found") || stderr.includes("does not exist"))) {
911
+ throw new Error(`Ollama model '${this.model}' not found. Run: ollama pull ${this.model}`);
912
+ }
913
+ if (stderr.includes("out of memory") || stderr.includes("OOM")) {
914
+ throw new Error("Ollama out of memory. Try a smaller model or increase system memory.");
915
+ }
916
+ if ((stderr.includes("Error") || stderr.includes("error")) && !stdout) {
917
+ throw new Error(`Ollama error: ${stderr.substring(0, 200)}`);
918
+ }
919
+ }
862
920
  return {
863
921
  output: stdout || "",
864
922
  error: stderr || void 0
@@ -1503,23 +1561,18 @@ ${pass.falsePositiveHints.map((h) => `- ${h}`).join("\n")}
1503
1561
  ${cwePatterns ? `## KNOWN VULNERABILITY PATTERNS
1504
1562
  ${cwePatterns}` : ""}
1505
1563
 
1506
- ## CRITICAL RULES
1564
+ ## REPORTING GUIDELINES
1507
1565
 
1508
- 1. **ONLY ${pass.name.toUpperCase()} BUGS** - Ignore all other categories
1509
- 2. **MUST HAVE FIX** - No fix = not confirmed = don't report
1510
- 3. **TRACE THE DATA** - Follow user input to the vulnerable sink
1511
- 4. **CHECK GUARDS** - Verify there's no validation you missed
1512
- 5. **BE SPECIFIC** - Exact file, line, and triggering input
1513
- 6. **CHECK CONTROL FLOW** - CRITICAL: Verify the buggy line is actually REACHABLE:
1514
- - Look for early returns: \`if (x.length === 0) return;\` makes later \`x[0]\` SAFE
1515
- - Look for throws: \`if (!user) throw new Error()\` makes later \`user.name\` SAFE
1516
- - Look for guard clauses that exit before the buggy line
1517
- - If a condition causes function exit, code after is UNREACHABLE in that case
1518
- - Example FALSE POSITIVE: \`if (arr.length === 0) return false; ... arr[0].name\` - the access is SAFE because empty array caused early return
1566
+ 1. **ONLY ${pass.name.toUpperCase()} ISSUES** - Focus on this category
1567
+ 2. **REPORT POTENTIAL ISSUES** - If something looks suspicious, report it. Better to flag potential issues than miss real bugs.
1568
+ 3. **INCLUDE CODE SMELLS** - Report risky patterns even if not immediately exploitable (set kind: "smell")
1569
+ 4. **TRACE THE DATA** - Follow user input to potentially vulnerable sinks
1570
+ 5. **BE SPECIFIC** - Include exact file, line number, and what makes it suspicious
1571
+ 6. **SUGGESTED FIX IS OPTIONAL** - Nice to have but not required. Report the issue even without a fix.
1519
1572
 
1520
1573
  ## REPORTING FORMAT
1521
1574
 
1522
- When you find a CONFIRMED ${pass.name} bug:
1575
+ When you find a ${pass.name} issue (bug or code smell):
1523
1576
 
1524
1577
  <json>
1525
1578
  {
@@ -1528,25 +1581,23 @@ When you find a CONFIRMED ${pass.name} bug:
1528
1581
  "file": "src/api/users.ts",
1529
1582
  "line": 42,
1530
1583
  "endLine": 45,
1531
- "title": "Short description of the bug",
1532
- "description": "Detailed explanation of why this is a bug and how it can be exploited.",
1584
+ "title": "Short description of the issue",
1585
+ "description": "Explanation of why this is problematic and potential impact.",
1586
+ "kind": "bug|smell",
1533
1587
  "category": "${pass.category}",
1534
1588
  "severity": "critical|high|medium|low",
1535
1589
  "confidence": "high|medium|low",
1536
- "triggerInput": "Exact input that triggers the bug",
1537
- "codePath": [
1538
- {"step": 1, "file": "src/api/users.ts", "line": 38, "code": "const name = req.query.name", "explanation": "User input enters here"},
1539
- {"step": 2, "file": "src/api/users.ts", "line": 42, "code": "db.execute(query)", "explanation": "Used unsafely here"}
1540
- ],
1541
1590
  "evidence": [
1542
1591
  "Evidence point 1",
1543
1592
  "Evidence point 2"
1544
1593
  ],
1545
- "suggestedFix": "const result = await db.query('SELECT * FROM users WHERE name = $1', [req.query.name]);"
1594
+ "suggestedFix": "Optional: how to fix it"
1546
1595
  }
1547
1596
  }
1548
1597
  </json>
1549
1598
 
1599
+ Use kind="bug" for confirmed vulnerabilities, kind="smell" for risky patterns that need review.
1600
+
1550
1601
  Progress updates:
1551
1602
  ###SCANNING:path/to/file.ts
1552
1603
 
@@ -1555,9 +1606,13 @@ When done:
1555
1606
 
1556
1607
  ## BEGIN
1557
1608
 
1558
- Start by searching for ${pass.name} patterns using grep. Then read the files and trace the data flow. Report bugs as you confirm them.
1609
+ Start by searching for ${pass.name} patterns using grep. Read at least 10-15 files that match the patterns. Report issues as you find them.
1559
1610
 
1560
- REMEMBER: Only ${pass.name.toUpperCase()} bugs. Quality over quantity. Every bug must have an exact fix.`;
1611
+ IMPORTANT:
1612
+ - Report ANYTHING suspicious - we'll filter false positives later
1613
+ - Include code smells and risky patterns, not just confirmed exploits
1614
+ - If unsure, report it with confidence="low"
1615
+ - Aim for thoroughness - finding 10 potential issues is better than finding 0 confirmed bugs`;
1561
1616
  }
1562
1617
 
1563
1618
  // src/providers/prompts/constants.ts
@@ -2862,31 +2917,46 @@ var CoreScanner = class {
2862
2917
  return jobs;
2863
2918
  }
2864
2919
  buildQuickScanPrompt(context) {
2865
- const { understanding, staticResults } = context;
2920
+ const { understanding, staticResults, files } = context;
2866
2921
  const staticSignals = staticResults.length > 0 ? `
2867
2922
  Static analysis signals:
2868
2923
  ${staticResults.slice(0, 30).map((r) => `- ${r.file}:${r.line}: ${r.message}`).join("\n")}` : "";
2869
- return `You are a security auditor. Analyze this ${understanding.summary.type} codebase for bugs.
2924
+ const fileList = files.slice(0, 50).map((f) => `- ${f}`).join("\n");
2925
+ const moreFiles = files.length > 50 ? `
2926
+ ... and ${files.length - 50} more files` : "";
2927
+ return `You are a security auditor. Analyze this ${understanding.summary.type} codebase for bugs and code smells.
2870
2928
 
2871
2929
  Project: ${understanding.summary.description || "Unknown"}
2872
2930
  Framework: ${understanding.summary.framework || "None"}
2873
2931
  Language: ${understanding.summary.language}
2932
+
2933
+ ## FILES TO ANALYZE
2934
+ Read and analyze these files for security issues:
2935
+ ${fileList}${moreFiles}
2874
2936
  ${staticSignals}
2875
2937
 
2876
- Find bugs in these categories:
2877
- 1. Injection (SQL, command, XSS)
2878
- 2. Auth bypass
2879
- 3. Null/undefined dereference
2880
- 4. Logic errors
2881
- 5. Async/race conditions
2882
- 6. Resource leaks
2883
- 7. Data validation issues
2884
- 8. Secrets exposure
2938
+ ## INSTRUCTIONS
2939
+ 1. Read each file listed above
2940
+ 2. Look for security issues, bugs, and code smells
2941
+ 3. Report ALL suspicious patterns - false positives will be filtered later
2942
+ 4. Use kind="smell" for risky patterns, kind="bug" for confirmed issues
2885
2943
 
2886
- Output ONLY a JSON array:
2887
- [{"file": "path", "line": 42, "title": "Bug title", "description": "Details", "severity": "critical|high|medium|low", "category": "injection|auth-bypass|null-reference|logic-error|async-issue|resource-leak|data-validation|secrets-exposure", "evidence": ["evidence"], "suggestedFix": "fix"}]
2944
+ ## CATEGORIES TO CHECK
2945
+ - Injection (SQL, command, XSS) - unsanitized user input
2946
+ - Auth bypass - missing auth checks, IDOR
2947
+ - Null/undefined dereference - accessing properties on potentially null values
2948
+ - Logic errors - wrong conditions, off-by-one, inverted checks
2949
+ - Async/race conditions - unhandled promises, race conditions
2950
+ - Resource leaks - unclosed handles, missing cleanup
2951
+ - Data validation - missing input validation, type coercion issues
2952
+ - Secrets exposure - hardcoded keys, leaked credentials
2953
+ - Error handling - swallowed errors, missing try/catch
2888
2954
 
2889
- If no bugs, return: []`;
2955
+ ## OUTPUT FORMAT
2956
+ Output a JSON array (one object per issue found):
2957
+ [{"file": "path", "line": 42, "title": "Issue title", "description": "Details", "kind": "bug|smell", "severity": "critical|high|medium|low", "category": "logic-error", "evidence": ["evidence"]}]
2958
+
2959
+ If no issues found after reading all files, return: []`;
2890
2960
  }
2891
2961
  // ─────────────────────────────────────────────────────────────
2892
2962
  // Response Parsing
@@ -2907,6 +2977,23 @@ If no bugs, return: []`;
2907
2977
  } catch {
2908
2978
  }
2909
2979
  }
2980
+ if (bugs.length === 0) {
2981
+ const codeBlockMatches = output.matchAll(/```(?:json)?\s*([\s\S]*?)```/g);
2982
+ for (const match of codeBlockMatches) {
2983
+ try {
2984
+ const parsed = JSON.parse(match[1].trim());
2985
+ const items = Array.isArray(parsed) ? parsed : [parsed];
2986
+ for (const item of items) {
2987
+ const bug = this.parseBugData(item, startIndex + bugs.length, files, passName);
2988
+ if (bug) {
2989
+ bugs.push(bug);
2990
+ this.progress.onBugFound?.(bug);
2991
+ }
2992
+ }
2993
+ } catch {
2994
+ }
2995
+ }
2996
+ }
2910
2997
  if (bugs.length === 0) {
2911
2998
  const arrayMatch = output.match(/\[[\s\S]*\]/);
2912
2999
  if (arrayMatch) {
@@ -2925,6 +3012,20 @@ If no bugs, return: []`;
2925
3012
  }
2926
3013
  }
2927
3014
  }
3015
+ if (bugs.length === 0) {
3016
+ const objectMatches = output.matchAll(/\{[^{}]*"file"[^{}]*"line"[^{}]*\}/g);
3017
+ for (const match of objectMatches) {
3018
+ try {
3019
+ const parsed = JSON.parse(match[0]);
3020
+ const bug = this.parseBugData(parsed, startIndex + bugs.length, files, passName);
3021
+ if (bug) {
3022
+ bugs.push(bug);
3023
+ this.progress.onBugFound?.(bug);
3024
+ }
3025
+ } catch {
3026
+ }
3027
+ }
3028
+ }
2928
3029
  return bugs;
2929
3030
  }
2930
3031
  parseBugData(data, index, files, passName) {