Merge pull request #918 from crytic/dev-sarif

Add sarif support
pull/920/head
Feist Josselin 3 years ago committed by GitHub
commit b3f6ceea19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      slither/__main__.py
  2. 1
      slither/utils/command_line.py
  3. 125
      slither/utils/output.py

@ -24,7 +24,7 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassi
from slither.printers import all_printers from slither.printers import all_printers
from slither.printers.abstract_printer import AbstractPrinter from slither.printers.abstract_printer import AbstractPrinter
from slither.slither import Slither from slither.slither import Slither
from slither.utils.output import output_to_json, output_to_zip, ZIP_TYPES_ACCEPTED from slither.utils.output import output_to_json, output_to_zip, output_to_sarif, ZIP_TYPES_ACCEPTED
from slither.utils.output_capture import StandardOutputCapture from slither.utils.output_capture import StandardOutputCapture
from slither.utils.colors import red, blue, set_colorization_enabled from slither.utils.colors import red, blue, set_colorization_enabled
from slither.utils.command_line import ( from slither.utils.command_line import (
@ -397,6 +397,13 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["json"], default=defaults_flag_in_config["json"],
) )
group_misc.add_argument(
"--sarif",
help='Export the results as a SARIF JSON file ("--sarif -" to export to stdout)',
action="store",
default=defaults_flag_in_config["sarif"],
)
group_misc.add_argument( group_misc.add_argument(
"--json-types", "--json-types",
help="Comma-separated list of result types to output to JSON, defaults to " help="Comma-separated list of result types to output to JSON, defaults to "
@ -645,6 +652,8 @@ def main_impl(all_detector_classes, all_printer_classes):
output_error = None output_error = None
outputting_json = args.json is not None outputting_json = args.json is not None
outputting_json_stdout = args.json == "-" outputting_json_stdout = args.json == "-"
outputting_sarif = args.sarif is not None
outputting_sarif_stdout = args.sarif == "-"
outputting_zip = args.zip is not None outputting_zip = args.zip is not None
if args.zip_type not in ZIP_TYPES_ACCEPTED.keys(): if args.zip_type not in ZIP_TYPES_ACCEPTED.keys():
to_log = f'Zip type not accepted, it must be one of {",".join(ZIP_TYPES_ACCEPTED.keys())}' to_log = f'Zip type not accepted, it must be one of {",".join(ZIP_TYPES_ACCEPTED.keys())}'
@ -652,8 +661,8 @@ def main_impl(all_detector_classes, all_printer_classes):
# If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout # If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout
# output. # output.
if outputting_json: if outputting_json or output_to_sarif:
StandardOutputCapture.enable(outputting_json_stdout) StandardOutputCapture.enable(outputting_json_stdout or outputting_sarif_stdout)
printer_classes = choose_printers(args, all_printer_classes) printer_classes = choose_printers(args, all_printer_classes)
detector_classes = choose_detectors(args, all_detector_classes) detector_classes = choose_detectors(args, all_detector_classes)
@ -732,7 +741,7 @@ def main_impl(all_detector_classes, all_printer_classes):
) = process_all(filename, args, detector_classes, printer_classes) ) = process_all(filename, args, detector_classes, printer_classes)
# Determine if we are outputting JSON # Determine if we are outputting JSON
if outputting_json or outputting_zip: if outputting_json or outputting_zip or output_to_sarif:
# Add our compilation information to JSON # Add our compilation information to JSON
if "compilations" in args.json_types: if "compilations" in args.json_types:
compilation_results = [] compilation_results = []
@ -809,6 +818,12 @@ def main_impl(all_detector_classes, all_printer_classes):
StandardOutputCapture.disable() StandardOutputCapture.disable()
output_to_json(None if outputting_json_stdout else args.json, output_error, json_results) output_to_json(None if outputting_json_stdout else args.json, output_error, json_results)
if outputting_sarif:
StandardOutputCapture.disable()
output_to_sarif(
None if outputting_sarif_stdout else args.sarif, json_results, detector_classes
)
if outputting_zip: if outputting_zip:
output_to_zip(args.zip, output_error, json_results, args.zip_type) output_to_zip(args.zip, output_error, json_results, args.zip_type)

@ -34,6 +34,7 @@ defaults_flag_in_config = {
"exclude_medium": False, "exclude_medium": False,
"exclude_high": False, "exclude_high": False,
"json": None, "json": None,
"sarif": None,
"json-types": ",".join(DEFAULT_JSON_OUTPUT_TYPES), "json-types": ",".join(DEFAULT_JSON_OUTPUT_TYPES),
"disable_color": False, "disable_color": False,
"filter_paths": None, "filter_paths": None,

@ -6,6 +6,7 @@ import zipfile
from collections import OrderedDict from collections import OrderedDict
from typing import Optional, Dict, List, Union, Any, TYPE_CHECKING from typing import Optional, Dict, List, Union, Any, TYPE_CHECKING
from zipfile import ZipFile from zipfile import ZipFile
from pkg_resources import require
from slither.core.cfg.node import Node from slither.core.cfg.node import Node
from slither.core.declarations import Contract, Function, Enum, Event, Structure, Pragma from slither.core.declarations import Contract, Function, Enum, Event, Structure, Pragma
@ -17,6 +18,7 @@ from slither.utils.myprettytable import MyPrettyTable
if TYPE_CHECKING: if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.compilation_unit import SlitherCompilationUnit
from slither.detectors.abstract_detector import AbstractDetector
logger = logging.getLogger("Slither") logger = logging.getLogger("Slither")
@ -28,7 +30,7 @@ logger = logging.getLogger("Slither")
################################################################################### ###################################################################################
def output_to_json(filename: str, error, results: Dict): def output_to_json(filename: Optional[str], error, results: Dict) -> None:
""" """
:param filename: Filename where the json will be written. If None or "-", write to stdout :param filename: Filename where the json will be written. If None or "-", write to stdout
@ -56,6 +58,127 @@ def output_to_json(filename: str, error, results: Dict):
json.dump(json_result, f, indent=2) json.dump(json_result, f, indent=2)
def _output_result_to_sarif(
detector: Dict, detectors_classes: List["AbstractDetector"], sarif: Dict
) -> None:
confidence = "very-high"
if detector["confidence"] == "Medium":
confidence = "high"
elif detector["confidence"] == "Low":
confidence = "medium"
elif detector["confidence"] == "Informational":
confidence = "low"
risk = "0.0"
if detector["impact"] == "High":
risk = "8.0"
elif detector["impact"] == "Medium":
risk = "4.0"
elif detector["impact"] == "Low":
risk = "3.0"
detector_class = next((d for d in detectors_classes if d.ARGUMENT == detector["check"]))
check_id = (
str(detector_class.IMPACT.value)
+ "-"
+ str(detector_class.CONFIDENCE.value)
+ "-"
+ detector["check"]
)
rule = {
"id": check_id,
"name": detector["check"],
"properties": {"precision": confidence, "security-severity": risk},
"shortDescription": {"text": detector_class.WIKI_TITLE},
"help": {"text": detector_class.WIKI_RECOMMENDATION},
}
# Add the rule if does not exist yet
if len([x for x in sarif["runs"][0]["tool"]["driver"]["rules"] if x["id"] == check_id]) == 0:
sarif["runs"][0]["tool"]["driver"]["rules"].append(rule)
if not detector["elements"]:
logger.info(yellow("Cannot generate Github security alert for finding without location"))
logger.info(yellow(detector["description"]))
logger.info(yellow("This will be supported in a future Slither release"))
return
# From 3.19.10 (http://docs.oasis-open.org/sarif/sarif/v2.0/csprd01/sarif-v2.0-csprd01.html)
# The locations array SHALL NOT contain more than one element unless the condition indicated by the result,
# if any, can only be corrected by making a change at every location specified in the array.
finding = detector["elements"][0]
path = finding["source_mapping"]["filename_relative"]
start_line = finding["source_mapping"]["lines"][0]
end_line = finding["source_mapping"]["lines"][-1]
sarif["runs"][0]["results"].append(
{
"ruleId": check_id,
"message": {"text": detector["description"], "markdown": detector["markdown"]},
"level": "warning",
"locations": [
{
"physicalLocation": {
"artifactLocation": {"uri": path},
"region": {"startLine": start_line, "endLine": end_line},
}
}
],
"partialFingerprints": {"id": detector["id"]},
}
)
def output_to_sarif(
filename: Optional[str], results: Dict, detectors_classes: List["AbstractDetector"]
) -> None:
"""
:param filename:
:type filename:
:param results:
:type results:
:return:
:rtype:
"""
sarif: Dict[str, Any] = {
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "Slither",
"informationUri": "https://github.com/crytic/slither",
"version": require("slither-analyzer")[0].version,
"rules": [],
}
},
"results": [],
}
],
}
for detector in results["detectors"]:
_output_result_to_sarif(detector, detectors_classes, sarif)
if filename == "-":
filename = None
# Determine if we should output to stdout
if filename is None:
# Write json to console
print(json.dumps(sarif))
else:
# Write json to file
if os.path.isfile(filename):
logger.info(yellow(f"{filename} exists already, the overwrite is prevented"))
else:
with open(filename, "w", encoding="utf8") as f:
json.dump(sarif, f, indent=2)
# https://docs.python.org/3/library/zipfile.html#zipfile-objects # https://docs.python.org/3/library/zipfile.html#zipfile-objects
ZIP_TYPES_ACCEPTED = { ZIP_TYPES_ACCEPTED = {
"lzma": zipfile.ZIP_LZMA, "lzma": zipfile.ZIP_LZMA,

Loading…
Cancel
Save