@shakecodeslikecray/whiterose 1.0.3 → 1.0.4

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
@@ -4327,6 +4327,9 @@ function loadAccumulatedBugs(cwd) {
4327
4327
  if (!Array.isArray(stored.bugs)) {
4328
4328
  stored.bugs = [];
4329
4329
  }
4330
+ if (!stored.fingerprints || typeof stored.fingerprints !== "object") {
4331
+ stored.fingerprints = {};
4332
+ }
4330
4333
  stored.bugs = stored.bugs.map((b) => ({ ...b, kind: b.kind || "bug" }));
4331
4334
  return stored;
4332
4335
  } catch (error) {
@@ -5875,7 +5878,9 @@ var FixConfirm = ({ bug, dryRun, onConfirm, onCancel, onFixComplete }) => {
5875
5878
  setStatus("fixing");
5876
5879
  setProgressMessage("Starting agentic fix...");
5877
5880
  try {
5878
- const result = await onConfirm();
5881
+ const result = await onConfirm((message) => {
5882
+ setProgressMessage(message);
5883
+ });
5879
5884
  if (result.falsePositive) {
5880
5885
  setFalsePositiveReason(result.falsePositiveReason || "The AI determined this bug is not real after analyzing the code.");
5881
5886
  setStatus("false-positive");
@@ -6019,11 +6024,11 @@ var App = ({ bugs, config, fixOptions, onFix, onExit }) => {
6019
6024
  };
6020
6025
  const [fixError, setFixError] = useState(null);
6021
6026
  const [lastFixedBugId, setLastFixedBugId] = useState(null);
6022
- const handleConfirmFix = async () => {
6027
+ const handleConfirmFix = async (onProgress) => {
6023
6028
  if (selectedBug) {
6024
6029
  setFixError(null);
6025
6030
  setLastFixedBugId(selectedBug.id);
6026
- const result = await onFix(selectedBug);
6031
+ const result = await onFix(selectedBug, onProgress);
6027
6032
  return result;
6028
6033
  }
6029
6034
  return { falsePositive: false };
@@ -6228,6 +6233,67 @@ function markBugAsFixed(bug, commitHash, cwd = process.cwd()) {
6228
6233
  }
6229
6234
 
6230
6235
  // src/core/fixer.ts
6236
+ var MAX_SARIF_TEXT_LENGTH = 2e3;
6237
+ var PROMPT_INJECTION_PATTERNS = [
6238
+ // Attempts to override or ignore instructions
6239
+ /ignore\s+(previous|all|above)\s+instructions?/i,
6240
+ /disregard\s+(previous|all|above)\s+instructions?/i,
6241
+ /forget\s+(previous|all|above)\s+instructions?/i,
6242
+ // Attempts to define new roles or personas
6243
+ /you\s+are\s+(now|actually)\s+/i,
6244
+ /act\s+as\s+(a|an)\s+/i,
6245
+ /pretend\s+(you|to\s+be)\s+/i,
6246
+ // Attempts to inject system-level commands
6247
+ /\[SYSTEM\]/i,
6248
+ /\[INST\]/i,
6249
+ /<\|system\|>/i,
6250
+ /<\|assistant\|>/i,
6251
+ /<\|user\|>/i,
6252
+ // Attempts to break out with special markers
6253
+ /###\s*(INSTRUCTION|SYSTEM|END|NEW)/i,
6254
+ /```\s*(system|instruction)/i,
6255
+ // Direct file operation injections
6256
+ /delete\s+(all|the)\s+files?/i,
6257
+ /rm\s+-rf\s+/i,
6258
+ /remove\s+(all|every)\s+file/i,
6259
+ // Exfiltration attempts
6260
+ /send\s+(this|the|all)\s+(data|content|file)/i,
6261
+ /upload\s+(to|this)/i,
6262
+ /curl\s+.*\s+-d/i,
6263
+ /fetch\s*\(\s*['"][^'"]*['"]\s*,\s*\{[^}]*method\s*:\s*['"]POST['"]/i
6264
+ ];
6265
+ function sanitizeSarifText(text2, fieldName = "field") {
6266
+ if (!text2 || typeof text2 !== "string") {
6267
+ return "";
6268
+ }
6269
+ let sanitized = text2.length > MAX_SARIF_TEXT_LENGTH ? text2.substring(0, MAX_SARIF_TEXT_LENGTH) + `... [truncated ${fieldName}]` : text2;
6270
+ for (const pattern of PROMPT_INJECTION_PATTERNS) {
6271
+ if (pattern.test(sanitized)) {
6272
+ sanitized = sanitized.replace(pattern, "[REDACTED: potential injection]");
6273
+ }
6274
+ }
6275
+ sanitized = sanitized.replace(/```+/g, "`\u200B`\u200B`");
6276
+ sanitized = sanitized.replace(/###/g, "#\u200B#\u200B#");
6277
+ return sanitized;
6278
+ }
6279
+ function sanitizeSarifEvidence(evidence) {
6280
+ if (!Array.isArray(evidence)) {
6281
+ return [];
6282
+ }
6283
+ return evidence.filter((e) => typeof e === "string").slice(0, 10).map((e) => sanitizeSarifText(e, "evidence"));
6284
+ }
6285
+ function sanitizeSarifCodePath(codePath) {
6286
+ if (!Array.isArray(codePath)) {
6287
+ return [];
6288
+ }
6289
+ return codePath.slice(0, 20).map((entry, idx) => ({
6290
+ step: typeof entry?.step === "number" ? entry.step : idx + 1,
6291
+ file: sanitizeSarifText(String(entry?.file || ""), "codePath.file").substring(0, 500),
6292
+ line: typeof entry?.line === "number" ? entry.line : 0,
6293
+ code: sanitizeSarifText(String(entry?.code || ""), "codePath.code"),
6294
+ explanation: sanitizeSarifText(String(entry?.explanation || ""), "codePath.explanation")
6295
+ }));
6296
+ }
6231
6297
  function isPathWithinProject(filePath, projectDir) {
6232
6298
  const resolvedPath = resolve(projectDir, filePath);
6233
6299
  const relativePath = relative(projectDir, resolvedPath);
@@ -6291,7 +6357,7 @@ In: ${bug.file}:${bug.line}`
6291
6357
  }
6292
6358
  let agenticResult;
6293
6359
  try {
6294
- agenticResult = await runAgenticFix(bug, config, projectDir);
6360
+ agenticResult = await runAgenticFix(bug, config, projectDir, options.onProgress);
6295
6361
  } catch (error) {
6296
6362
  return {
6297
6363
  success: false,
@@ -6331,7 +6397,7 @@ In: ${bug.file}:${bug.line}`
6331
6397
  commitHash
6332
6398
  };
6333
6399
  }
6334
- async function runAgenticFix(bug, config, projectDir) {
6400
+ async function runAgenticFix(bug, config, projectDir, onProgress) {
6335
6401
  const providerCommand = getProviderCommand(config.provider);
6336
6402
  const prompt = buildAgenticFixPrompt(bug);
6337
6403
  const controller = new AbortController();
@@ -6385,9 +6451,13 @@ async function runAgenticFix(bug, config, projectDir) {
6385
6451
  }
6386
6452
  }
6387
6453
  } else if (config.provider === "claude-code") {
6388
- const result = await execa(
6454
+ const args = ["--dangerously-skip-permissions", "-p"];
6455
+ if (onProgress) {
6456
+ args.push("--verbose", "--output-format", "stream-json");
6457
+ }
6458
+ const subprocess = execa(
6389
6459
  providerCommand,
6390
- ["--dangerously-skip-permissions", "-p"],
6460
+ args,
6391
6461
  {
6392
6462
  cwd: projectDir,
6393
6463
  input: prompt,
@@ -6398,6 +6468,41 @@ async function runAgenticFix(bug, config, projectDir) {
6398
6468
  cancelSignal: controller.signal
6399
6469
  }
6400
6470
  );
6471
+ if (onProgress && subprocess.stdout) {
6472
+ let lineBuffer = "";
6473
+ subprocess.stdout.on("data", (chunk) => {
6474
+ const text2 = chunk.toString();
6475
+ lineBuffer += text2;
6476
+ const lines = lineBuffer.split("\n");
6477
+ lineBuffer = lines.pop() || "";
6478
+ for (const line of lines) {
6479
+ const trimmed = line.trim();
6480
+ if (trimmed) {
6481
+ try {
6482
+ const event = JSON.parse(trimmed);
6483
+ if (event.type === "assistant" && event.message?.content) {
6484
+ for (const block of event.message.content) {
6485
+ if (block.type === "tool_use") {
6486
+ const toolName = block.name || "tool";
6487
+ onProgress(`Using ${toolName}...`);
6488
+ } else if (block.type === "text" && block.text) {
6489
+ const preview = block.text.substring(0, 80).replace(/\n/g, " ").trim();
6490
+ if (preview) {
6491
+ onProgress(preview + (block.text.length > 80 ? "..." : ""));
6492
+ }
6493
+ }
6494
+ }
6495
+ }
6496
+ } catch {
6497
+ if (trimmed.length > 3 && trimmed.length < 100) {
6498
+ onProgress(trimmed);
6499
+ }
6500
+ }
6501
+ }
6502
+ }
6503
+ });
6504
+ }
6505
+ const result = await subprocess;
6401
6506
  stdout = result.stdout || "";
6402
6507
  stderr = result.stderr || "";
6403
6508
  } else if (config.provider === "gemini") {
@@ -6567,8 +6672,8 @@ function generateSimpleDiff(original, modified, filename) {
6567
6672
  }
6568
6673
  async function startFixTUI(bugs, config, options, cwd) {
6569
6674
  return new Promise((resolve6) => {
6570
- const handleFix = async (bug) => {
6571
- const result = await applyFix(bug, config, options);
6675
+ const handleFix = async (bug, onProgress) => {
6676
+ const result = await applyFix(bug, config, { ...options, onProgress });
6572
6677
  if (result.falsePositive) {
6573
6678
  if (cwd) {
6574
6679
  removeBugFromAccumulated(cwd, bug.id);
@@ -6727,39 +6832,67 @@ function loadBugsFromSarif(sarifPath) {
6727
6832
  } catch (error) {
6728
6833
  throw new Error(`Failed to parse SARIF file: ${sarifPath}. File may be corrupted or malformed.`);
6729
6834
  }
6730
- return sarif.runs?.[0]?.results?.map((r, i) => {
6835
+ if (!sarif || typeof sarif !== "object") {
6836
+ throw new Error(`Invalid SARIF file: ${sarifPath}. Expected a JSON object.`);
6837
+ }
6838
+ const runs = sarif.runs;
6839
+ if (!Array.isArray(runs) || runs.length === 0) {
6840
+ return [];
6841
+ }
6842
+ const results = runs[0]?.results;
6843
+ if (!Array.isArray(results)) {
6844
+ return [];
6845
+ }
6846
+ return results.map((r, i) => {
6847
+ if (!r || typeof r !== "object") {
6848
+ throw new Error(`Invalid SARIF result at index ${i}: expected an object.`);
6849
+ }
6731
6850
  const props = r.properties || {};
6851
+ const rawTitle = r.message?.text || "Unknown bug";
6852
+ const rawDescription = r.message?.markdown || r.message?.text || "";
6853
+ const rawCodePath = r.codeFlows?.[0]?.threadFlows?.[0]?.locations?.map((loc, idx) => ({
6854
+ step: idx + 1,
6855
+ file: loc.location?.physicalLocation?.artifactLocation?.uri || "",
6856
+ line: loc.location?.physicalLocation?.region?.startLine || 0,
6857
+ code: "",
6858
+ explanation: loc.message?.text || ""
6859
+ })) || [];
6860
+ const rawFile = r.locations?.[0]?.physicalLocation?.artifactLocation?.uri;
6861
+ const file = typeof rawFile === "string" ? rawFile : "unknown";
6862
+ const rawLine = r.locations?.[0]?.physicalLocation?.region?.startLine;
6863
+ const line = typeof rawLine === "number" && Number.isFinite(rawLine) ? Math.floor(rawLine) : 0;
6864
+ const rawEndLine = r.locations?.[0]?.physicalLocation?.region?.endLine;
6865
+ const endLine = typeof rawEndLine === "number" && Number.isFinite(rawEndLine) ? Math.floor(rawEndLine) : void 0;
6866
+ const rawId = r.ruleId;
6867
+ const id = typeof rawId === "string" ? rawId : `WR-${String(i + 1).padStart(3, "0")}`;
6868
+ const validatedKind = FindingKind.safeParse(props.kind);
6869
+ const validatedCategory = BugCategory.safeParse(props.category);
6870
+ const validatedConfidence = ConfidenceLevel.safeParse(props.confidence);
6732
6871
  return {
6733
- id: r.ruleId || `WR-${String(i + 1).padStart(3, "0")}`,
6734
- title: r.message?.text || "Unknown bug",
6735
- description: r.message?.markdown || r.message?.text || "",
6736
- file: r.locations?.[0]?.physicalLocation?.artifactLocation?.uri || "unknown",
6737
- line: r.locations?.[0]?.physicalLocation?.region?.startLine || 0,
6738
- endLine: r.locations?.[0]?.physicalLocation?.region?.endLine,
6739
- kind: props.kind || "bug",
6872
+ id,
6873
+ title: sanitizeSarifText(String(rawTitle), "title"),
6874
+ description: sanitizeSarifText(String(rawDescription), "description"),
6875
+ file,
6876
+ line,
6877
+ endLine,
6878
+ kind: validatedKind.success ? validatedKind.data : "bug",
6740
6879
  severity: mapSarifLevel(r.level),
6741
- category: props.category || "logic-error",
6880
+ category: validatedCategory.success ? validatedCategory.data : "logic-error",
6742
6881
  confidence: {
6743
- overall: props.confidence || "medium",
6744
- codePathValidity: props.codePathValidity || 0.8,
6745
- reachability: props.reachability || 0.8,
6746
- intentViolation: props.intentViolation || false,
6747
- staticToolSignal: props.staticToolSignal || false,
6748
- adversarialSurvived: props.adversarialSurvived || false
6882
+ overall: validatedConfidence.success ? validatedConfidence.data : "medium",
6883
+ codePathValidity: typeof props.codePathValidity === "number" ? props.codePathValidity : 0.8,
6884
+ reachability: typeof props.reachability === "number" ? props.reachability : 0.8,
6885
+ intentViolation: typeof props.intentViolation === "boolean" ? props.intentViolation : false,
6886
+ staticToolSignal: typeof props.staticToolSignal === "boolean" ? props.staticToolSignal : false,
6887
+ adversarialSurvived: typeof props.adversarialSurvived === "boolean" ? props.adversarialSurvived : false
6749
6888
  },
6750
- codePath: r.codeFlows?.[0]?.threadFlows?.[0]?.locations?.map((loc, idx) => ({
6751
- step: idx + 1,
6752
- file: loc.location?.physicalLocation?.artifactLocation?.uri || "",
6753
- line: loc.location?.physicalLocation?.region?.startLine || 0,
6754
- code: "",
6755
- explanation: loc.message?.text || ""
6756
- })) || [],
6757
- evidence: props.evidence || [],
6758
- suggestedFix: props.suggestedFix,
6889
+ codePath: sanitizeSarifCodePath(rawCodePath),
6890
+ evidence: sanitizeSarifEvidence(props.evidence),
6891
+ suggestedFix: props.suggestedFix ? sanitizeSarifText(String(props.suggestedFix), "suggestedFix") : void 0,
6759
6892
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6760
6893
  status: "open"
6761
6894
  };
6762
- }) || [];
6895
+ });
6763
6896
  }
6764
6897
  async function loadBugFromGitHub(issueUrl, cwd) {
6765
6898
  try {
@@ -6999,12 +7132,24 @@ async function fixSingleBug(bug, config, options, cwd) {
6999
7132
  process.exit(0);
7000
7133
  }
7001
7134
  }
7002
- const spinner6 = p3.spinner();
7003
- spinner6.start(options.dryRun ? "Generating fix preview..." : "Applying fix...");
7135
+ console.log();
7136
+ console.log(chalk3.cyan(" \u25C6 Starting agentic fix..."));
7137
+ console.log();
7004
7138
  try {
7005
- const result = await applyFix(bug, config, options);
7139
+ let lastMessage = "";
7140
+ const result = await applyFix(bug, config, {
7141
+ ...options,
7142
+ onProgress: (message) => {
7143
+ if (message !== lastMessage) {
7144
+ lastMessage = message;
7145
+ const truncated = message.length > 72 ? message.substring(0, 72) + "..." : message;
7146
+ process.stdout.write(`\r\x1B[K ${chalk3.dim("\u203A")} ${chalk3.gray(truncated)}`);
7147
+ }
7148
+ }
7149
+ });
7150
+ process.stdout.write("\r\x1B[K");
7006
7151
  if (result.success) {
7007
- spinner6.stop(options.dryRun ? "Fix preview generated" : "Fix applied");
7152
+ console.log(chalk3.green(" \u2713 Fix applied successfully"));
7008
7153
  if (result.diff) {
7009
7154
  console.log();
7010
7155
  console.log(chalk3.dim(" Changes:"));
@@ -7030,12 +7175,12 @@ async function fixSingleBug(bug, config, options, cwd) {
7030
7175
  }
7031
7176
  p3.outro(chalk3.green("Fix complete!"));
7032
7177
  } else {
7033
- spinner6.stop("Fix failed");
7178
+ console.log(chalk3.red(" \u2717 Fix failed"));
7034
7179
  p3.log.error(result.error || "Unknown error");
7035
7180
  process.exit(1);
7036
7181
  }
7037
7182
  } catch (error) {
7038
- spinner6.stop("Fix failed");
7183
+ console.log(chalk3.red(" \u2717 Fix failed"));
7039
7184
  p3.log.error(error.message);
7040
7185
  process.exit(1);
7041
7186
  }