codeant-cli 0.4.9 → 0.4.10
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/package.json +1 -1
- package/src/commands/scans/index.js +4 -2
- package/src/commands/scans/lib/filters.js +5 -32
- package/src/commands/scans/lib/normalize.js +0 -2
- package/src/commands/scans/results.js +9 -14
- package/src/components/ScanCenter.js +2 -2
- package/src/index.js +7 -2
- package/src/scanCenter/handleSelectResultType.js +4 -3
- package/src/scans/fetchAdvancedScanResults.js +18 -13
- package/src/scans/fetchScanResults.js +14 -9
package/package.json
CHANGED
|
@@ -71,7 +71,8 @@ export default function registerScansCommands(program, { runCmd }) {
|
|
|
71
71
|
.option('--severity <list>', 'Filter by severity (e.g. critical,high)')
|
|
72
72
|
.option('--path <glob>', 'Filter by file path glob')
|
|
73
73
|
.option('--check <regex>', 'Filter by check ID or name (regex)')
|
|
74
|
-
.option('--
|
|
74
|
+
.option('--filter-dismissed', 'Exclude dismissed findings (default: false)')
|
|
75
|
+
.option('--no-false-positives', 'Exclude false positives (default: included)')
|
|
75
76
|
.option('--format <fmt>', 'Output format: json|sarif|csv|md|table (default: json)', 'json')
|
|
76
77
|
.option('--output <path>', 'Write output to file instead of stdout')
|
|
77
78
|
.option('--fields <list>', 'Project findings to subset of fields (comma-separated)')
|
|
@@ -96,7 +97,8 @@ export default function registerScansCommands(program, { runCmd }) {
|
|
|
96
97
|
severity: opts.severity,
|
|
97
98
|
path: opts.path,
|
|
98
99
|
check: opts.check,
|
|
99
|
-
|
|
100
|
+
filterDismissed: opts.filterDismissed || false,
|
|
101
|
+
includeFalsePositives: opts.falsePositives ?? true,
|
|
100
102
|
format: opts.format,
|
|
101
103
|
output: opts.output,
|
|
102
104
|
fields: opts.fields,
|
|
@@ -1,29 +1,22 @@
|
|
|
1
1
|
import { minimatch } from 'minimatch';
|
|
2
|
-
import { isDismissed, findDismissMatch } from './dismissMatch.js';
|
|
3
2
|
import { normalizeSeverity } from './normalize.js';
|
|
4
3
|
|
|
5
|
-
const SEV_RANK = { critical: 5, high: 4, medium: 3, low: 2, info: 1, unknown: 0 };
|
|
6
|
-
|
|
7
4
|
/**
|
|
8
|
-
* Apply
|
|
5
|
+
* Apply client-side filters to findings (returns new array).
|
|
6
|
+
* Dismissed / false-positive filtering is handled exclusively by the backend.
|
|
9
7
|
*
|
|
10
8
|
* @param {Array} findings - NormalizedFinding[]
|
|
11
9
|
* @param {object} opts
|
|
12
|
-
* @param {string[]|null} opts.severity
|
|
13
|
-
* @param {string|null} opts.pathGlob
|
|
10
|
+
* @param {string[]|null} opts.severity - allowed severity levels (e.g. ['critical','high'])
|
|
11
|
+
* @param {string|null} opts.pathGlob - minimatch glob for file_path
|
|
14
12
|
* @param {string|null} opts.checkRegex - regex applied to check_id + check_name
|
|
15
|
-
* @
|
|
16
|
-
* @param {boolean} opts.includeDismissed
|
|
17
|
-
* @returns {Array} filtered NormalizedFinding[] (dismissed field annotated)
|
|
13
|
+
* @returns {Array} filtered NormalizedFinding[]
|
|
18
14
|
*/
|
|
19
15
|
export function applyFilters(findings, {
|
|
20
16
|
severity = null,
|
|
21
17
|
pathGlob = null,
|
|
22
18
|
checkRegex = null,
|
|
23
|
-
dismissedAlerts = [],
|
|
24
|
-
includeDismissed = false,
|
|
25
19
|
} = {}) {
|
|
26
|
-
// Pre-compile regex — strip Python/PCRE inline flag (?i) and fold into JS flag
|
|
27
20
|
let checkRe = null;
|
|
28
21
|
if (checkRegex) {
|
|
29
22
|
try {
|
|
@@ -37,35 +30,15 @@ export function applyFilters(findings, {
|
|
|
37
30
|
}
|
|
38
31
|
}
|
|
39
32
|
|
|
40
|
-
// Severity set
|
|
41
33
|
const sevSet = severity && severity.length > 0
|
|
42
34
|
? new Set(severity.map((s) => normalizeSeverity(s)))
|
|
43
35
|
: null;
|
|
44
36
|
|
|
45
37
|
const result = [];
|
|
46
38
|
for (const f of findings) {
|
|
47
|
-
// Annotate dismiss status
|
|
48
|
-
const match = findDismissMatch(f, dismissedAlerts);
|
|
49
|
-
f.dismissed = match !== null;
|
|
50
|
-
if (match) {
|
|
51
|
-
f.dismiss_info = {
|
|
52
|
-
reason: match.reason_for_dismiss || null,
|
|
53
|
-
comment: match.comment_for_dismiss || null,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Filter dismissed
|
|
58
|
-
if (f.dismissed && !includeDismissed) continue;
|
|
59
|
-
|
|
60
|
-
// Filter by severity
|
|
61
39
|
if (sevSet && !sevSet.has(f.severity)) continue;
|
|
62
|
-
|
|
63
|
-
// Filter by path glob
|
|
64
40
|
if (pathGlob && !minimatch(f.file_path, pathGlob, { matchBase: true })) continue;
|
|
65
|
-
|
|
66
|
-
// Filter by check regex
|
|
67
41
|
if (checkRe && !checkRe.test(f.check_id) && !checkRe.test(f.check_name)) continue;
|
|
68
|
-
|
|
69
42
|
result.push(f);
|
|
70
43
|
}
|
|
71
44
|
return result;
|
|
@@ -7,7 +7,6 @@ import { paginate } from './lib/paginate.js';
|
|
|
7
7
|
import { emit } from './lib/emit.js';
|
|
8
8
|
import { progress, logError } from './lib/log.js';
|
|
9
9
|
import { FORMATTERS } from './formatters/index.js';
|
|
10
|
-
import { fetchDismissedAlerts } from '../../scans/fetchDismissedAlerts.js';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* codeant scans results — full orchestration.
|
|
@@ -23,7 +22,8 @@ export async function runResults(opts = {}) {
|
|
|
23
22
|
severity,
|
|
24
23
|
path: pathGlob,
|
|
25
24
|
check: checkRegex,
|
|
26
|
-
|
|
25
|
+
filterDismissed = false,
|
|
26
|
+
includeFalsePositives = true,
|
|
27
27
|
format = 'json',
|
|
28
28
|
output: outputPath = null,
|
|
29
29
|
fields = null,
|
|
@@ -53,16 +53,12 @@ export async function runResults(opts = {}) {
|
|
|
53
53
|
// 2. Parse types
|
|
54
54
|
const categories = parseTypes(types);
|
|
55
55
|
|
|
56
|
-
// 3. Fetch in parallel
|
|
56
|
+
// 3. Fetch in parallel
|
|
57
57
|
progress(`fetching ${categories.map((c) => c.key).join(', ')}…`);
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
: fetchDismissedAlerts(repo, 'security'),
|
|
63
|
-
]);
|
|
64
|
-
|
|
65
|
-
const dismissedAlerts = dismissedResult.success ? (dismissedResult.dismissedAlerts ?? []) : [];
|
|
58
|
+
const fetchOpts = { filterDismissed, includeFalsePositives };
|
|
59
|
+
const settled = await Promise.allSettled(
|
|
60
|
+
categories.map((c) => c.fetcher(repo, scanMeta.commit_id, fetchOpts))
|
|
61
|
+
);
|
|
66
62
|
|
|
67
63
|
// 4. Collect findings + errors
|
|
68
64
|
const allFindings = [];
|
|
@@ -100,8 +96,6 @@ export async function runResults(opts = {}) {
|
|
|
100
96
|
severity: severityList,
|
|
101
97
|
pathGlob: pathGlob || null,
|
|
102
98
|
checkRegex: checkRegex || null,
|
|
103
|
-
dismissedAlerts,
|
|
104
|
-
includeDismissed,
|
|
105
99
|
});
|
|
106
100
|
|
|
107
101
|
// 6. Sort
|
|
@@ -115,7 +109,8 @@ export async function runResults(opts = {}) {
|
|
|
115
109
|
severity: severityList,
|
|
116
110
|
path: pathGlob || null,
|
|
117
111
|
check: checkRegex || null,
|
|
118
|
-
|
|
112
|
+
filter_dismissed: filterDismissed,
|
|
113
|
+
include_false_positives: includeFalsePositives,
|
|
119
114
|
};
|
|
120
115
|
|
|
121
116
|
// Rebuild summary from all filtered (pre-page) findings
|
|
@@ -153,7 +153,7 @@ function ErrorView({ message, onBack, canGoBack }) {
|
|
|
153
153
|
|
|
154
154
|
// ─── Main ScanCenter ─────────────────────────────────────────────────────────
|
|
155
155
|
|
|
156
|
-
export default function ScanCenter() {
|
|
156
|
+
export default function ScanCenter({ filterDismissed = false, includeFalsePositives = true }) {
|
|
157
157
|
const { exit } = useApp();
|
|
158
158
|
|
|
159
159
|
const [step, setStep] = useState(STEPS.LOADING);
|
|
@@ -198,7 +198,7 @@ export default function ScanCenter() {
|
|
|
198
198
|
|
|
199
199
|
// ── Step 5: result type selected → fetch exactly one endpoint ──
|
|
200
200
|
const handleSelectResultType = (item) =>
|
|
201
|
-
_handleSelectResultType({ STEPS, item, selectedRepo, selectedScan, setSelectedResultType, setStep, setLoadingMsg, setError, setResults });
|
|
201
|
+
_handleSelectResultType({ STEPS, item, selectedRepo, selectedScan, setSelectedResultType, setStep, setLoadingMsg, setError, setResults, filterDismissed, includeFalsePositives });
|
|
202
202
|
|
|
203
203
|
// ── Back navigation ──
|
|
204
204
|
const goBack = {
|
package/src/index.js
CHANGED
|
@@ -211,8 +211,13 @@ program
|
|
|
211
211
|
program
|
|
212
212
|
.command('scan-center')
|
|
213
213
|
.description('Browse scan results interactively')
|
|
214
|
-
.
|
|
215
|
-
|
|
214
|
+
.option('--filter-dismissed', 'Exclude dismissed alerts from results (default: false)')
|
|
215
|
+
.option('--no-false-positives', 'Exclude false positives from results (default: included)')
|
|
216
|
+
.action((options) => {
|
|
217
|
+
render(React.createElement(ScanCenter, {
|
|
218
|
+
filterDismissed: options.filterDismissed ?? false,
|
|
219
|
+
includeFalsePositives: options.falsePositives ?? true,
|
|
220
|
+
}));
|
|
216
221
|
});
|
|
217
222
|
|
|
218
223
|
program
|
|
@@ -2,7 +2,7 @@ import { fetchScanResults } from '../scans/fetchScanResults.js';
|
|
|
2
2
|
import { fetchAdvancedScanResults } from '../scans/fetchAdvancedScanResults.js';
|
|
3
3
|
import { fetchDismissedAlerts } from '../scans/fetchDismissedAlerts.js';
|
|
4
4
|
|
|
5
|
-
export async function handleSelectResultType({ STEPS, item, selectedRepo, selectedScan, setSelectedResultType, setStep, setLoadingMsg, setError, setResults }) {
|
|
5
|
+
export async function handleSelectResultType({ STEPS, item, selectedRepo, selectedScan, setSelectedResultType, setStep, setLoadingMsg, setError, setResults, filterDismissed = false, includeFalsePositives = true }) {
|
|
6
6
|
const rt = item.value;
|
|
7
7
|
setSelectedResultType(rt);
|
|
8
8
|
setStep(STEPS.LOADING);
|
|
@@ -10,12 +10,13 @@ export async function handleSelectResultType({ STEPS, item, selectedRepo, select
|
|
|
10
10
|
|
|
11
11
|
const repo = selectedRepo.full_name;
|
|
12
12
|
const commitId = selectedScan.commitId;
|
|
13
|
+
const fetchOpts = { filterDismissed, includeFalsePositives };
|
|
13
14
|
let res;
|
|
14
15
|
|
|
15
16
|
if (rt.kind === 'basic') {
|
|
16
|
-
res = await fetchScanResults(repo, commitId, rt.value);
|
|
17
|
+
res = await fetchScanResults(repo, commitId, rt.value, fetchOpts);
|
|
17
18
|
} else if (rt.kind === 'advanced') {
|
|
18
|
-
res = await fetchAdvancedScanResults(repo, commitId, rt.value);
|
|
19
|
+
res = await fetchAdvancedScanResults(repo, commitId, rt.value, fetchOpts);
|
|
19
20
|
} else if (rt.value === 'dismissed_alerts') {
|
|
20
21
|
const r = await fetchDismissedAlerts(repo, 'security');
|
|
21
22
|
res = r.success ? { success: true, issues: r.dismissedAlerts } : r;
|
|
@@ -200,9 +200,12 @@ function normalizeAdvancedIssue(item, resultType) {
|
|
|
200
200
|
* @param {string} repo - "org/repo-name"
|
|
201
201
|
* @param {string} commitId - 40-char commit SHA
|
|
202
202
|
* @param {string} resultType - one of ADVANCED_RESULT_TYPES values
|
|
203
|
+
* @param {{ filterDismissed?: boolean, includeFalsePositives?: boolean }} [opts]
|
|
203
204
|
* @returns {Promise<{ success: boolean, issues?: Array, healthyPackages?: Array, status?: string, error?: string }>}
|
|
204
205
|
*/
|
|
205
|
-
export async function fetchAdvancedScanResults(repo, commitId, resultType) {
|
|
206
|
+
export async function fetchAdvancedScanResults(repo, commitId, resultType, opts = {}) {
|
|
207
|
+
const { filterDismissed = false, includeFalsePositives = true } = opts;
|
|
208
|
+
|
|
206
209
|
if (!Object.values(ADVANCED_RESULT_TYPES).includes(resultType)) {
|
|
207
210
|
return {
|
|
208
211
|
success: false,
|
|
@@ -215,6 +218,8 @@ export async function fetchAdvancedScanResults(repo, commitId, resultType) {
|
|
|
215
218
|
repo,
|
|
216
219
|
commit_id: commitId,
|
|
217
220
|
result_type: resultType,
|
|
221
|
+
filter_dismissed: filterDismissed,
|
|
222
|
+
include_false_positives: includeFalsePositives,
|
|
218
223
|
});
|
|
219
224
|
|
|
220
225
|
if (!response) {
|
|
@@ -294,8 +299,8 @@ export async function fetchAdvancedScanResults(repo, commitId, resultType) {
|
|
|
294
299
|
}
|
|
295
300
|
}
|
|
296
301
|
|
|
297
|
-
// Filter secrets false positives
|
|
298
|
-
if (resultType === ADVANCED_RESULT_TYPES.SECRETS) {
|
|
302
|
+
// Filter secrets false positives only when caller explicitly excludes them
|
|
303
|
+
if (resultType === ADVANCED_RESULT_TYPES.SECRETS && !includeFalsePositives) {
|
|
299
304
|
issues = issues.filter((issue) => String(issue.confidence_score || '').toLowerCase() !== 'false_positive');
|
|
300
305
|
}
|
|
301
306
|
|
|
@@ -309,17 +314,17 @@ export async function fetchAdvancedScanResults(repo, commitId, resultType) {
|
|
|
309
314
|
}
|
|
310
315
|
}
|
|
311
316
|
|
|
312
|
-
export const fetchScaResults = (repo, commitId) =>
|
|
313
|
-
fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.SCA);
|
|
317
|
+
export const fetchScaResults = (repo, commitId, opts) =>
|
|
318
|
+
fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.SCA, opts);
|
|
314
319
|
|
|
315
|
-
export const fetchSbomResults = (repo, commitId) =>
|
|
316
|
-
fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.SBOM);
|
|
320
|
+
export const fetchSbomResults = (repo, commitId, opts) =>
|
|
321
|
+
fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.SBOM, opts);
|
|
317
322
|
|
|
318
|
-
export const fetchSecretsResults = (repo, commitId) =>
|
|
319
|
-
fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.SECRETS);
|
|
323
|
+
export const fetchSecretsResults = (repo, commitId, opts) =>
|
|
324
|
+
fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.SECRETS, opts);
|
|
320
325
|
|
|
321
|
-
export const fetchIacResults = (repo, commitId) =>
|
|
322
|
-
fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.IAC);
|
|
326
|
+
export const fetchIacResults = (repo, commitId, opts) =>
|
|
327
|
+
fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.IAC, opts);
|
|
323
328
|
|
|
324
|
-
export const fetchDeadCodeResults = (repo, commitId) =>
|
|
325
|
-
fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.DEAD_CODE);
|
|
329
|
+
export const fetchDeadCodeResults = (repo, commitId, opts) =>
|
|
330
|
+
fetchAdvancedScanResults(repo, commitId, ADVANCED_RESULT_TYPES.DEAD_CODE, opts);
|
|
@@ -49,9 +49,12 @@ function extractCleanPath(fullPath, commitId, suffix = null) {
|
|
|
49
49
|
* @param {string} repo - "org/repo-name"
|
|
50
50
|
* @param {string} commitId - 40-char commit SHA
|
|
51
51
|
* @param {string} resultType - one of VALID_RESULT_TYPES values
|
|
52
|
+
* @param {{ filterDismissed?: boolean, includeFalsePositives?: boolean }} [opts]
|
|
52
53
|
* @returns {Promise<{ success: boolean, issues?: Array, status?: string, error?: string }>}
|
|
53
54
|
*/
|
|
54
|
-
export async function fetchScanResults(repo, commitId, resultType) {
|
|
55
|
+
export async function fetchScanResults(repo, commitId, resultType, opts = {}) {
|
|
56
|
+
const { filterDismissed = false, includeFalsePositives = true } = opts;
|
|
57
|
+
|
|
55
58
|
if (!Object.values(VALID_RESULT_TYPES).includes(resultType)) {
|
|
56
59
|
return {
|
|
57
60
|
success: false,
|
|
@@ -64,6 +67,8 @@ export async function fetchScanResults(repo, commitId, resultType) {
|
|
|
64
67
|
repo,
|
|
65
68
|
commit_id: commitId,
|
|
66
69
|
result_type: resultType,
|
|
70
|
+
filter_dismissed: filterDismissed,
|
|
71
|
+
include_false_positives: includeFalsePositives,
|
|
67
72
|
});
|
|
68
73
|
|
|
69
74
|
if (!response) {
|
|
@@ -102,14 +107,14 @@ export async function fetchScanResults(repo, commitId, resultType) {
|
|
|
102
107
|
}
|
|
103
108
|
}
|
|
104
109
|
|
|
105
|
-
export const fetchSastResults = (repo, commitId) =>
|
|
106
|
-
fetchScanResults(repo, commitId, VALID_RESULT_TYPES.SECURITY_ISSUES);
|
|
110
|
+
export const fetchSastResults = (repo, commitId, opts) =>
|
|
111
|
+
fetchScanResults(repo, commitId, VALID_RESULT_TYPES.SECURITY_ISSUES, opts);
|
|
107
112
|
|
|
108
|
-
export const fetchAntiPatternsResults = (repo, commitId) =>
|
|
109
|
-
fetchScanResults(repo, commitId, VALID_RESULT_TYPES.ANTI_PATTERNS);
|
|
113
|
+
export const fetchAntiPatternsResults = (repo, commitId, opts) =>
|
|
114
|
+
fetchScanResults(repo, commitId, VALID_RESULT_TYPES.ANTI_PATTERNS, opts);
|
|
110
115
|
|
|
111
|
-
export const fetchDocstringResults = (repo, commitId) =>
|
|
112
|
-
fetchScanResults(repo, commitId, VALID_RESULT_TYPES.DOCSTRING);
|
|
116
|
+
export const fetchDocstringResults = (repo, commitId, opts) =>
|
|
117
|
+
fetchScanResults(repo, commitId, VALID_RESULT_TYPES.DOCSTRING, opts);
|
|
113
118
|
|
|
114
|
-
export const fetchComplexFunctionsResults = (repo, commitId) =>
|
|
115
|
-
fetchScanResults(repo, commitId, VALID_RESULT_TYPES.COMPLEX_FUNCTIONS);
|
|
119
|
+
export const fetchComplexFunctionsResults = (repo, commitId, opts) =>
|
|
120
|
+
fetchScanResults(repo, commitId, VALID_RESULT_TYPES.COMPLEX_FUNCTIONS, opts);
|