@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 +160 -43
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +140 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
##
|
|
1564
|
+
## REPORTING GUIDELINES
|
|
1507
1565
|
|
|
1508
|
-
1. **ONLY ${pass.name.toUpperCase()}
|
|
1509
|
-
2. **
|
|
1510
|
-
3. **
|
|
1511
|
-
4. **
|
|
1512
|
-
5. **BE SPECIFIC** -
|
|
1513
|
-
6. **
|
|
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
|
|
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
|
|
1532
|
-
"description": "
|
|
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": "
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2877
|
-
1.
|
|
2878
|
-
2.
|
|
2879
|
-
3.
|
|
2880
|
-
4.
|
|
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
|
-
|
|
2887
|
-
|
|
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
|
-
|
|
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) {
|