xploitscan 1.0.12 → 1.0.13
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 +39 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1976,17 +1976,19 @@ function parseReviewResponse(text) {
|
|
|
1976
1976
|
const parsed = JSON.parse(cleaned);
|
|
1977
1977
|
if (!Array.isArray(parsed)) return [];
|
|
1978
1978
|
return parsed.filter(
|
|
1979
|
-
(r) => typeof r === "object" && r !== null && "index" in r && "verdict" in r && r.verdict === "real" || r.verdict === "fp"
|
|
1979
|
+
(r) => typeof r === "object" && r !== null && "index" in r && "verdict" in r && (r.verdict === "real" || r.verdict === "fp")
|
|
1980
1980
|
);
|
|
1981
1981
|
} catch {
|
|
1982
1982
|
return [];
|
|
1983
1983
|
}
|
|
1984
1984
|
}
|
|
1985
1985
|
async function filterFalsePositives(findings, fileContents) {
|
|
1986
|
-
|
|
1987
|
-
if (
|
|
1986
|
+
const empty = { findings, filteredFindings: [], aiReviewed: false, removedCount: 0, totalBefore: findings.length };
|
|
1987
|
+
if (!process.env.ANTHROPIC_API_KEY) return empty;
|
|
1988
|
+
if (findings.length === 0) return empty;
|
|
1988
1989
|
const toReview = findings.slice(0, MAX_TOTAL_FINDINGS);
|
|
1989
1990
|
const overflow = findings.slice(MAX_TOTAL_FINDINGS);
|
|
1991
|
+
const totalBefore = findings.length;
|
|
1990
1992
|
const byFile = /* @__PURE__ */ new Map();
|
|
1991
1993
|
for (const f of toReview) {
|
|
1992
1994
|
const group = byFile.get(f.file) || [];
|
|
@@ -1997,9 +1999,9 @@ async function filterFalsePositives(findings, fileContents) {
|
|
|
1997
1999
|
try {
|
|
1998
2000
|
client = new Anthropic2();
|
|
1999
2001
|
} catch {
|
|
2000
|
-
return
|
|
2002
|
+
return empty;
|
|
2001
2003
|
}
|
|
2002
|
-
const
|
|
2004
|
+
const fpMap = /* @__PURE__ */ new Map();
|
|
2003
2005
|
for (const [file, fileFindings] of byFile) {
|
|
2004
2006
|
const content = fileContents.get(file);
|
|
2005
2007
|
if (!content) continue;
|
|
@@ -2018,7 +2020,9 @@ async function filterFalsePositives(findings, fileContents) {
|
|
|
2018
2020
|
for (const r of results) {
|
|
2019
2021
|
if (r.verdict === "fp" && r.index >= 0 && r.index < batch.length) {
|
|
2020
2022
|
const globalIndex = toReview.indexOf(batch[r.index]);
|
|
2021
|
-
if (globalIndex !== -1)
|
|
2023
|
+
if (globalIndex !== -1) {
|
|
2024
|
+
fpMap.set(globalIndex, r.reason);
|
|
2025
|
+
}
|
|
2022
2026
|
}
|
|
2023
2027
|
}
|
|
2024
2028
|
} catch {
|
|
@@ -2026,9 +2030,18 @@ async function filterFalsePositives(findings, fileContents) {
|
|
|
2026
2030
|
}
|
|
2027
2031
|
}
|
|
2028
2032
|
}
|
|
2029
|
-
const filtered = toReview.filter((_, i) => !
|
|
2030
|
-
const
|
|
2031
|
-
|
|
2033
|
+
const filtered = toReview.filter((_, i) => !fpMap.has(i));
|
|
2034
|
+
const filteredFindings = [];
|
|
2035
|
+
for (const [idx, reason] of fpMap) {
|
|
2036
|
+
filteredFindings.push({ finding: toReview[idx], reason });
|
|
2037
|
+
}
|
|
2038
|
+
return {
|
|
2039
|
+
findings: [...filtered, ...overflow],
|
|
2040
|
+
filteredFindings,
|
|
2041
|
+
aiReviewed: true,
|
|
2042
|
+
removedCount: fpMap.size,
|
|
2043
|
+
totalBefore
|
|
2044
|
+
};
|
|
2032
2045
|
}
|
|
2033
2046
|
|
|
2034
2047
|
// src/scanners/ast-analyzer.ts
|
|
@@ -3629,6 +3642,8 @@ async function scanCommand(directory, options) {
|
|
|
3629
3642
|
}
|
|
3630
3643
|
let aiReviewed = false;
|
|
3631
3644
|
let aiRemovedCount = 0;
|
|
3645
|
+
let aiTotalBefore = 0;
|
|
3646
|
+
let aiFilteredFindings = [];
|
|
3632
3647
|
if (process.env.ANTHROPIC_API_KEY && allFindings.length > 0) {
|
|
3633
3648
|
spinner.text = "AI reviewing findings for false positives...";
|
|
3634
3649
|
spinner.color = "cyan";
|
|
@@ -3643,6 +3658,8 @@ async function scanCommand(directory, options) {
|
|
|
3643
3658
|
const result2 = await filterFalsePositives(allFindings, fileContentsMap);
|
|
3644
3659
|
aiReviewed = result2.aiReviewed;
|
|
3645
3660
|
aiRemovedCount = result2.removedCount;
|
|
3661
|
+
aiTotalBefore = result2.totalBefore;
|
|
3662
|
+
aiFilteredFindings = result2.filteredFindings;
|
|
3646
3663
|
if (result2.removedCount > 0) {
|
|
3647
3664
|
allFindings.length = 0;
|
|
3648
3665
|
allFindings.push(...result2.findings);
|
|
@@ -3677,9 +3694,20 @@ async function scanCommand(directory, options) {
|
|
|
3677
3694
|
break;
|
|
3678
3695
|
}
|
|
3679
3696
|
if (aiReviewed && !isSilent) {
|
|
3680
|
-
const msg = aiRemovedCount > 0 ? ` AI review: ${aiRemovedCount} false positive${aiRemovedCount !== 1 ? "s" : ""} removed, ${dedupedFindings.length} verified finding${dedupedFindings.length !== 1 ? "s" : ""} remain` : ` AI review: all ${dedupedFindings.length} finding${dedupedFindings.length !== 1 ? "s" : ""} verified`;
|
|
3681
3697
|
console.log("");
|
|
3682
|
-
|
|
3698
|
+
if (aiRemovedCount > 0) {
|
|
3699
|
+
console.log(chalk2.cyan("\u{1F916} AI Review: ") + chalk2.white(`${aiTotalBefore} findings scanned \u2192 ${aiRemovedCount} false positive${aiRemovedCount !== 1 ? "s" : ""} filtered \u2192 `) + chalk2.green(`${dedupedFindings.length} verified`));
|
|
3700
|
+
} else {
|
|
3701
|
+
console.log(chalk2.cyan("\u{1F916} AI Review: ") + chalk2.green(`all ${dedupedFindings.length} finding${dedupedFindings.length !== 1 ? "s" : ""} verified`));
|
|
3702
|
+
}
|
|
3703
|
+
if (verbose && aiFilteredFindings.length > 0) {
|
|
3704
|
+
console.log(chalk2.gray(`
|
|
3705
|
+
Filtered findings (AI determined these are false positives):`));
|
|
3706
|
+
for (const { finding: f, reason } of aiFilteredFindings) {
|
|
3707
|
+
console.log(chalk2.gray(` ${f.severity.toUpperCase().padEnd(8)} ${f.rule} ${f.file}:${f.line}`));
|
|
3708
|
+
console.log(chalk2.gray(` Reason: ${reason}`));
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3683
3711
|
}
|
|
3684
3712
|
if (tier === "free" && !isSilent) {
|
|
3685
3713
|
console.log("");
|