@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/cli/index.js CHANGED
@@ -307,14 +307,24 @@ var ClaudeCodeExecutor = class {
307
307
  }
308
308
  async runPrompt(prompt, options) {
309
309
  const claudeCommand = getProviderCommand("claude-code");
310
+ if (process.env.WHITEROSE_DEBUG) {
311
+ console.log("\n[DEBUG] Running Claude Code command:", claudeCommand);
312
+ console.log("[DEBUG] Prompt length:", prompt.length);
313
+ console.log("[DEBUG] CWD:", options.cwd);
314
+ console.log("[DEBUG] First 500 chars of prompt:");
315
+ console.log(prompt.substring(0, 500));
316
+ }
310
317
  try {
311
318
  const { stdout, stderr } = await execa(
312
319
  claudeCommand,
313
320
  [
314
321
  "-p",
315
322
  prompt,
316
- "--dangerously-skip-permissions"
323
+ "--dangerously-skip-permissions",
317
324
  // Allow file reads without prompts
325
+ "--output-format",
326
+ "text"
327
+ // Ensure non-interactive output
318
328
  ],
319
329
  {
320
330
  cwd: options.cwd,
@@ -323,7 +333,9 @@ var ClaudeCodeExecutor = class {
323
333
  ...process.env,
324
334
  NO_COLOR: "1"
325
335
  },
326
- reject: false
336
+ reject: false,
337
+ stdin: "ignore"
338
+ // Prevent waiting for stdin
327
339
  }
328
340
  );
329
341
  if (stderr) {
@@ -340,6 +352,13 @@ var ClaudeCodeExecutor = class {
340
352
  throw new Error(`Claude Code error: ${stderr.substring(0, 200)}`);
341
353
  }
342
354
  }
355
+ if (process.env.WHITEROSE_DEBUG) {
356
+ console.log("\n[DEBUG] Claude Code stdout length:", stdout?.length || 0);
357
+ console.log("[DEBUG] Claude Code stderr:", stderr?.substring(0, 300) || "(none)");
358
+ console.log("[DEBUG] First 1000 chars of stdout:");
359
+ console.log(stdout?.substring(0, 1e3) || "(empty)");
360
+ console.log("[DEBUG] End response\n");
361
+ }
343
362
  return {
344
363
  output: stdout || "",
345
364
  error: stderr || void 0
@@ -443,6 +462,17 @@ var GeminiExecutor = class {
443
462
  reject: false
444
463
  }
445
464
  );
465
+ if (stderr) {
466
+ if (stderr.includes("429") || stderr.includes("rate limit") || stderr.includes("quota exceeded")) {
467
+ throw new Error("Gemini API rate limit reached. Try again later.");
468
+ }
469
+ if (stderr.includes("401") || stderr.includes("403") || stderr.includes("unauthorized") || stderr.includes("invalid api key")) {
470
+ throw new Error("Gemini API authentication failed. Check your API key.");
471
+ }
472
+ if ((stderr.includes("Error") || stderr.includes("error")) && !stdout) {
473
+ throw new Error(`Gemini error: ${stderr.substring(0, 200)}`);
474
+ }
475
+ }
446
476
  return {
447
477
  output: stdout || "",
448
478
  error: stderr || void 0
@@ -483,6 +513,20 @@ var AiderExecutor = class {
483
513
  reject: false
484
514
  }
485
515
  );
516
+ if (stderr) {
517
+ if (stderr.includes("429") || stderr.includes("rate limit") || stderr.includes("RateLimitError")) {
518
+ throw new Error("Aider API rate limit reached. Try again later.");
519
+ }
520
+ if (stderr.includes("401") || stderr.includes("AuthenticationError") || stderr.includes("invalid api key")) {
521
+ throw new Error("Aider API authentication failed. Check your API key.");
522
+ }
523
+ if (stderr.includes("402") || stderr.includes("insufficient") || stderr.includes("billing")) {
524
+ throw new Error("Aider API billing error. Check your account credits.");
525
+ }
526
+ if ((stderr.includes("Error") || stderr.includes("error")) && !stdout) {
527
+ throw new Error(`Aider error: ${stderr.substring(0, 200)}`);
528
+ }
529
+ }
486
530
  return {
487
531
  output: stdout || "",
488
532
  error: stderr || void 0
@@ -522,6 +566,20 @@ var OllamaExecutor = class {
522
566
  reject: false
523
567
  }
524
568
  );
569
+ if (stderr) {
570
+ if (stderr.includes("connection refused") || stderr.includes("ECONNREFUSED")) {
571
+ throw new Error("Ollama server not running. Start it with: ollama serve");
572
+ }
573
+ if (stderr.includes("model") && (stderr.includes("not found") || stderr.includes("does not exist"))) {
574
+ throw new Error(`Ollama model '${this.model}' not found. Run: ollama pull ${this.model}`);
575
+ }
576
+ if (stderr.includes("out of memory") || stderr.includes("OOM")) {
577
+ throw new Error("Ollama out of memory. Try a smaller model or increase system memory.");
578
+ }
579
+ if ((stderr.includes("Error") || stderr.includes("error")) && !stdout) {
580
+ throw new Error(`Ollama error: ${stderr.substring(0, 200)}`);
581
+ }
582
+ }
525
583
  return {
526
584
  output: stdout || "",
527
585
  error: stderr || void 0
@@ -1156,23 +1214,18 @@ ${pass.falsePositiveHints.map((h) => `- ${h}`).join("\n")}
1156
1214
  ${cwePatterns ? `## KNOWN VULNERABILITY PATTERNS
1157
1215
  ${cwePatterns}` : ""}
1158
1216
 
1159
- ## CRITICAL RULES
1217
+ ## REPORTING GUIDELINES
1160
1218
 
1161
- 1. **ONLY ${pass.name.toUpperCase()} BUGS** - Ignore all other categories
1162
- 2. **MUST HAVE FIX** - No fix = not confirmed = don't report
1163
- 3. **TRACE THE DATA** - Follow user input to the vulnerable sink
1164
- 4. **CHECK GUARDS** - Verify there's no validation you missed
1165
- 5. **BE SPECIFIC** - Exact file, line, and triggering input
1166
- 6. **CHECK CONTROL FLOW** - CRITICAL: Verify the buggy line is actually REACHABLE:
1167
- - Look for early returns: \`if (x.length === 0) return;\` makes later \`x[0]\` SAFE
1168
- - Look for throws: \`if (!user) throw new Error()\` makes later \`user.name\` SAFE
1169
- - Look for guard clauses that exit before the buggy line
1170
- - If a condition causes function exit, code after is UNREACHABLE in that case
1171
- - Example FALSE POSITIVE: \`if (arr.length === 0) return false; ... arr[0].name\` - the access is SAFE because empty array caused early return
1219
+ 1. **ONLY ${pass.name.toUpperCase()} ISSUES** - Focus on this category
1220
+ 2. **REPORT POTENTIAL ISSUES** - If something looks suspicious, report it. Better to flag potential issues than miss real bugs.
1221
+ 3. **INCLUDE CODE SMELLS** - Report risky patterns even if not immediately exploitable (set kind: "smell")
1222
+ 4. **TRACE THE DATA** - Follow user input to potentially vulnerable sinks
1223
+ 5. **BE SPECIFIC** - Include exact file, line number, and what makes it suspicious
1224
+ 6. **SUGGESTED FIX IS OPTIONAL** - Nice to have but not required. Report the issue even without a fix.
1172
1225
 
1173
1226
  ## REPORTING FORMAT
1174
1227
 
1175
- When you find a CONFIRMED ${pass.name} bug:
1228
+ When you find a ${pass.name} issue (bug or code smell):
1176
1229
 
1177
1230
  <json>
1178
1231
  {
@@ -1181,25 +1234,23 @@ When you find a CONFIRMED ${pass.name} bug:
1181
1234
  "file": "src/api/users.ts",
1182
1235
  "line": 42,
1183
1236
  "endLine": 45,
1184
- "title": "Short description of the bug",
1185
- "description": "Detailed explanation of why this is a bug and how it can be exploited.",
1237
+ "title": "Short description of the issue",
1238
+ "description": "Explanation of why this is problematic and potential impact.",
1239
+ "kind": "bug|smell",
1186
1240
  "category": "${pass.category}",
1187
1241
  "severity": "critical|high|medium|low",
1188
1242
  "confidence": "high|medium|low",
1189
- "triggerInput": "Exact input that triggers the bug",
1190
- "codePath": [
1191
- {"step": 1, "file": "src/api/users.ts", "line": 38, "code": "const name = req.query.name", "explanation": "User input enters here"},
1192
- {"step": 2, "file": "src/api/users.ts", "line": 42, "code": "db.execute(query)", "explanation": "Used unsafely here"}
1193
- ],
1194
1243
  "evidence": [
1195
1244
  "Evidence point 1",
1196
1245
  "Evidence point 2"
1197
1246
  ],
1198
- "suggestedFix": "const result = await db.query('SELECT * FROM users WHERE name = $1', [req.query.name]);"
1247
+ "suggestedFix": "Optional: how to fix it"
1199
1248
  }
1200
1249
  }
1201
1250
  </json>
1202
1251
 
1252
+ Use kind="bug" for confirmed vulnerabilities, kind="smell" for risky patterns that need review.
1253
+
1203
1254
  Progress updates:
1204
1255
  ###SCANNING:path/to/file.ts
1205
1256
 
@@ -1208,9 +1259,13 @@ When done:
1208
1259
 
1209
1260
  ## BEGIN
1210
1261
 
1211
- Start by searching for ${pass.name} patterns using grep. Then read the files and trace the data flow. Report bugs as you confirm them.
1262
+ 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.
1212
1263
 
1213
- REMEMBER: Only ${pass.name.toUpperCase()} bugs. Quality over quantity. Every bug must have an exact fix.`;
1264
+ IMPORTANT:
1265
+ - Report ANYTHING suspicious - we'll filter false positives later
1266
+ - Include code smells and risky patterns, not just confirmed exploits
1267
+ - If unsure, report it with confidence="low"
1268
+ - Aim for thoroughness - finding 10 potential issues is better than finding 0 confirmed bugs`;
1214
1269
  }
1215
1270
 
1216
1271
  // src/providers/prompts/constants.ts
@@ -2515,31 +2570,46 @@ var CoreScanner = class {
2515
2570
  return jobs;
2516
2571
  }
2517
2572
  buildQuickScanPrompt(context) {
2518
- const { understanding, staticResults } = context;
2573
+ const { understanding, staticResults, files } = context;
2519
2574
  const staticSignals = staticResults.length > 0 ? `
2520
2575
  Static analysis signals:
2521
2576
  ${staticResults.slice(0, 30).map((r) => `- ${r.file}:${r.line}: ${r.message}`).join("\n")}` : "";
2522
- return `You are a security auditor. Analyze this ${understanding.summary.type} codebase for bugs.
2577
+ const fileList = files.slice(0, 50).map((f) => `- ${f}`).join("\n");
2578
+ const moreFiles = files.length > 50 ? `
2579
+ ... and ${files.length - 50} more files` : "";
2580
+ return `You are a security auditor. Analyze this ${understanding.summary.type} codebase for bugs and code smells.
2523
2581
 
2524
2582
  Project: ${understanding.summary.description || "Unknown"}
2525
2583
  Framework: ${understanding.summary.framework || "None"}
2526
2584
  Language: ${understanding.summary.language}
2527
- ${staticSignals}
2528
-
2529
- Find bugs in these categories:
2530
- 1. Injection (SQL, command, XSS)
2531
- 2. Auth bypass
2532
- 3. Null/undefined dereference
2533
- 4. Logic errors
2534
- 5. Async/race conditions
2535
- 6. Resource leaks
2536
- 7. Data validation issues
2537
- 8. Secrets exposure
2538
2585
 
2539
- Output ONLY a JSON array:
2540
- [{"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"}]
2586
+ ## FILES TO ANALYZE
2587
+ Read and analyze these files for security issues:
2588
+ ${fileList}${moreFiles}
2589
+ ${staticSignals}
2541
2590
 
2542
- If no bugs, return: []`;
2591
+ ## INSTRUCTIONS
2592
+ 1. Read each file listed above
2593
+ 2. Look for security issues, bugs, and code smells
2594
+ 3. Report ALL suspicious patterns - false positives will be filtered later
2595
+ 4. Use kind="smell" for risky patterns, kind="bug" for confirmed issues
2596
+
2597
+ ## CATEGORIES TO CHECK
2598
+ - Injection (SQL, command, XSS) - unsanitized user input
2599
+ - Auth bypass - missing auth checks, IDOR
2600
+ - Null/undefined dereference - accessing properties on potentially null values
2601
+ - Logic errors - wrong conditions, off-by-one, inverted checks
2602
+ - Async/race conditions - unhandled promises, race conditions
2603
+ - Resource leaks - unclosed handles, missing cleanup
2604
+ - Data validation - missing input validation, type coercion issues
2605
+ - Secrets exposure - hardcoded keys, leaked credentials
2606
+ - Error handling - swallowed errors, missing try/catch
2607
+
2608
+ ## OUTPUT FORMAT
2609
+ Output a JSON array (one object per issue found):
2610
+ [{"file": "path", "line": 42, "title": "Issue title", "description": "Details", "kind": "bug|smell", "severity": "critical|high|medium|low", "category": "logic-error", "evidence": ["evidence"]}]
2611
+
2612
+ If no issues found after reading all files, return: []`;
2543
2613
  }
2544
2614
  // ─────────────────────────────────────────────────────────────
2545
2615
  // Response Parsing
@@ -2560,6 +2630,23 @@ If no bugs, return: []`;
2560
2630
  } catch {
2561
2631
  }
2562
2632
  }
2633
+ if (bugs.length === 0) {
2634
+ const codeBlockMatches = output.matchAll(/```(?:json)?\s*([\s\S]*?)```/g);
2635
+ for (const match of codeBlockMatches) {
2636
+ try {
2637
+ const parsed = JSON.parse(match[1].trim());
2638
+ const items = Array.isArray(parsed) ? parsed : [parsed];
2639
+ for (const item of items) {
2640
+ const bug = this.parseBugData(item, startIndex + bugs.length, files, passName);
2641
+ if (bug) {
2642
+ bugs.push(bug);
2643
+ this.progress.onBugFound?.(bug);
2644
+ }
2645
+ }
2646
+ } catch {
2647
+ }
2648
+ }
2649
+ }
2563
2650
  if (bugs.length === 0) {
2564
2651
  const arrayMatch = output.match(/\[[\s\S]*\]/);
2565
2652
  if (arrayMatch) {
@@ -2578,6 +2665,20 @@ If no bugs, return: []`;
2578
2665
  }
2579
2666
  }
2580
2667
  }
2668
+ if (bugs.length === 0) {
2669
+ const objectMatches = output.matchAll(/\{[^{}]*"file"[^{}]*"line"[^{}]*\}/g);
2670
+ for (const match of objectMatches) {
2671
+ try {
2672
+ const parsed = JSON.parse(match[0]);
2673
+ const bug = this.parseBugData(parsed, startIndex + bugs.length, files, passName);
2674
+ if (bug) {
2675
+ bugs.push(bug);
2676
+ this.progress.onBugFound?.(bug);
2677
+ }
2678
+ } catch {
2679
+ }
2680
+ }
2681
+ }
2581
2682
  return bugs;
2582
2683
  }
2583
2684
  parseBugData(data, index, files, passName) {
@@ -5274,10 +5375,26 @@ async function scanCommand(paths, options) {
5274
5375
  if (!isQuiet) {
5275
5376
  const spinner6 = p3.spinner();
5276
5377
  spinner6.start("Scanning files...");
5277
- filesToScan = paths.length > 0 ? paths : await scanCodebase(cwd, config);
5378
+ if (paths.length > 0) {
5379
+ filesToScan = await fg3(paths, {
5380
+ cwd,
5381
+ ignore: ["node_modules/**", "dist/**", "build/**", ".next/**"],
5382
+ absolute: false
5383
+ });
5384
+ } else {
5385
+ filesToScan = await scanCodebase(cwd, config);
5386
+ }
5278
5387
  spinner6.stop(`Found ${filesToScan.length} files to scan`);
5279
5388
  } else {
5280
- filesToScan = paths.length > 0 ? paths : await scanCodebase(cwd, config);
5389
+ if (paths.length > 0) {
5390
+ filesToScan = await fg3(paths, {
5391
+ cwd,
5392
+ ignore: ["node_modules/**", "dist/**", "build/**", ".next/**"],
5393
+ absolute: false
5394
+ });
5395
+ } else {
5396
+ filesToScan = await scanCodebase(cwd, config);
5397
+ }
5281
5398
  }
5282
5399
  } else {
5283
5400
  if (!config) {