|
5 | 5 | """ |
6 | 6 |
|
7 | 7 | from functools import reduce |
8 | | -import json as js |
9 | 8 | import logging |
10 | 9 | import os |
11 | 10 | import sys |
|
14 | 13 |
|
15 | 14 | import click |
16 | 15 | from prettytable import PrettyTable |
17 | | -from termcolor import colored |
18 | 16 |
|
19 | 17 | from guarddog.analyzer.metadata import get_metadata_detectors |
20 | 18 | from guarddog.analyzer.sourcecode import get_sourcecode_rules |
21 | 19 | from guarddog.ecosystems import ECOSYSTEM |
22 | | -from guarddog.reporters.sarif import report_verify_sarif |
| 20 | +from guarddog.reporters.reporter_factory import ReporterFactory, ReporterType |
| 21 | + |
23 | 22 | from guarddog.scanners import get_package_scanner, get_project_scanner |
24 | 23 | from guarddog.utils.archives import safe_extract |
25 | 24 |
|
@@ -127,7 +126,7 @@ def _get_all_rules(ecosystem: ECOSYSTEM) -> set[str]: |
127 | 126 |
|
128 | 127 | def _get_rule_param( |
129 | 128 | rules: tuple[str, ...], exclude_rules: tuple[str, ...], ecosystem: ECOSYSTEM |
130 | | -) -> Optional[set]: |
| 129 | +) -> Optional[set[str]]: |
131 | 130 | """ |
132 | 131 | This function should return None if no rules are provided |
133 | 132 | Else a set of rules to be used for scanning |
@@ -162,28 +161,20 @@ def _verify( |
162 | 161 | log.error(f"Command verify is not supported for ecosystem {ecosystem}") |
163 | 162 | exit(1) |
164 | 163 |
|
165 | | - def display_result(result: dict) -> None: |
166 | | - identifier = ( |
167 | | - result["dependency"] |
168 | | - if result["version"] is None |
169 | | - else f"{result['dependency']} version {result['version']}" |
170 | | - ) |
171 | | - if output_format is None: |
172 | | - print_scan_results(result.get("result"), identifier) |
173 | | - |
174 | | - if len(result.get("errors", [])) > 0: |
175 | | - print_errors(result.get("error"), identifier) |
| 164 | + dependencies, results = scanner.scan_local(path=path,rules=rule_param ) |
176 | 165 |
|
177 | | - results = scanner.scan_local(path, rule_param, display_result) |
178 | | - if output_format == "json": |
179 | | - return_value = js.dumps(results) |
| 166 | + rule_docs = list(rule_param or _get_all_rules(ecosystem=ecosystem)) |
180 | 167 |
|
181 | | - if output_format == "sarif": |
182 | | - sarif_rules = _get_all_rules(ecosystem) |
183 | | - return_value = report_verify_sarif(path, list(sarif_rules), results, ecosystem) |
| 168 | + reporter = ReporterFactory.create_reporter(ReporterType.from_str(output_format)) |
| 169 | + stdout, stderr = reporter.render_verify( |
| 170 | + dependency_files=dependencies, |
| 171 | + rule_names=rule_docs, |
| 172 | + scan_results=results, |
| 173 | + ecosystem=ecosystem, |
| 174 | + ) |
184 | 175 |
|
185 | | - if output_format is not None: |
186 | | - print(return_value) |
| 176 | + sys.stdout.write(stdout) |
| 177 | + sys.stderr.write(stderr) |
187 | 178 |
|
188 | 179 | if exit_non_zero_on_finding: |
189 | 180 | exit_with_status_code([result["result"] for result in results]) |
@@ -231,10 +222,10 @@ def _scan( |
231 | 222 | log.error(f"Error occurred while scanning target {identifier}: '{e}'\n") |
232 | 223 | sys.exit(1) |
233 | 224 |
|
234 | | - if output_format == "json": |
235 | | - print(js.dumps(result)) |
236 | | - else: |
237 | | - print_scan_results(result, result["package"]) |
| 225 | + reporter = ReporterFactory.create_reporter(ReporterType.from_str(output_format)) |
| 226 | + stdout, stderr = reporter.render_scan(result) |
| 227 | + sys.stdout.write(stdout) |
| 228 | + sys.stderr.write(stderr) |
238 | 229 |
|
239 | 230 | if exit_non_zero_on_finding: |
240 | 231 | exit_with_status_code([result]) |
@@ -359,83 +350,6 @@ def scan( |
359 | 350 | exit_non_zero_on_finding, |
360 | 351 | ECOSYSTEM.PYPI, |
361 | 352 | ) |
362 | | - |
363 | | - |
364 | | -# Pretty prints scan results for the console |
365 | | -def print_scan_results(results, identifier): |
366 | | - num_issues = results.get("issues") |
367 | | - errors = results.get("errors", []) |
368 | | - |
369 | | - if num_issues == 0: |
370 | | - print( |
371 | | - "Found " |
372 | | - + colored("0 potentially malicious indicators", "green", attrs=["bold"]) |
373 | | - + " scanning " |
374 | | - + colored(identifier, None, attrs=["bold"]) |
375 | | - ) |
376 | | - print() |
377 | | - else: |
378 | | - print( |
379 | | - "Found " |
380 | | - + colored( |
381 | | - str(num_issues) + " potentially malicious indicators", |
382 | | - "red", |
383 | | - attrs=["bold"], |
384 | | - ) |
385 | | - + " in " |
386 | | - + colored(identifier, None, attrs=["bold"]) |
387 | | - ) |
388 | | - print() |
389 | | - |
390 | | - findings = results.get("results", []) |
391 | | - for finding in findings: |
392 | | - description = findings[finding] |
393 | | - if isinstance(description, str): # package metadata |
394 | | - print(colored(finding, None, attrs=["bold"]) + ": " + description) |
395 | | - print() |
396 | | - elif isinstance(description, list): # semgrep rule result: |
397 | | - source_code_findings = description |
398 | | - print( |
399 | | - colored(finding, None, attrs=["bold"]) |
400 | | - + ": found " |
401 | | - + str(len(source_code_findings)) |
402 | | - + " source code matches" |
403 | | - ) |
404 | | - for finding in source_code_findings: |
405 | | - print( |
406 | | - " * " |
407 | | - + finding["message"] |
408 | | - + " at " |
409 | | - + finding["location"] |
410 | | - + "\n " |
411 | | - + format_code_line_for_output(finding["code"]) |
412 | | - ) |
413 | | - print() |
414 | | - |
415 | | - if len(errors) > 0: |
416 | | - print_errors(errors, identifier) |
417 | | - print("\n") |
418 | | - |
419 | | - |
420 | | -def print_errors(errors, identifier): |
421 | | - print( |
422 | | - colored("Some rules failed to run while scanning " + identifier + ":", "yellow") |
423 | | - ) |
424 | | - print() |
425 | | - for rule in errors: |
426 | | - print(f"* {rule}: {errors[rule]}") |
427 | | - print() |
428 | | - |
429 | | - |
430 | | -def format_code_line_for_output(code): |
431 | | - return " " + colored( |
432 | | - code.strip().replace("\n", "\n ").replace("\t", " "), |
433 | | - None, |
434 | | - "on_red", |
435 | | - attrs=["bold"], |
436 | | - ) |
437 | | - |
438 | | - |
439 | 353 | # Given the results, exit with the appropriate status code |
440 | 354 | def exit_with_status_code(results): |
441 | 355 | for result in results: |
|
0 commit comments