Merge pull request #222 from crytic/dev-json-stdout

Added keyword for json output to stdout
pull/231/head
Feist Josselin 6 years ago committed by GitHub
commit 01c5bc2d78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      scripts/tests_generate_expected_json_5.sh
  2. 76
      slither/__main__.py
  3. 6
      tests/expected_json/arbitrary_send-0.5.1.arbitrary-send.json
  4. 6
      tests/expected_json/arbitrary_send.arbitrary-send.json
  5. 6
      tests/expected_json/backdoor.backdoor.json
  6. 6
      tests/expected_json/backdoor.suicidal.json
  7. 6
      tests/expected_json/const_state_variables.constable-states.json
  8. 6
      tests/expected_json/constant-0.5.1.constant-function.json
  9. 6
      tests/expected_json/constant.constant-function.json
  10. 6
      tests/expected_json/controlled_delegatecall.controlled-delegatecall.json
  11. 6
      tests/expected_json/deprecated_calls.deprecated-standards.json
  12. 6
      tests/expected_json/erc20_indexed.erc20-indexed.json
  13. 6
      tests/expected_json/external_function.external-function.json
  14. 6
      tests/expected_json/external_function_2.external-function.json
  15. 6
      tests/expected_json/incorrect_equality.incorrect-equality.json
  16. 6
      tests/expected_json/incorrect_erc20_interface.erc20-interface.json
  17. 6
      tests/expected_json/incorrect_erc721_interface.erc721-interface.json
  18. 6
      tests/expected_json/inline_assembly_contract-0.5.1.assembly.json
  19. 6
      tests/expected_json/inline_assembly_contract.assembly.json
  20. 6
      tests/expected_json/inline_assembly_library-0.5.1.assembly.json
  21. 6
      tests/expected_json/inline_assembly_library.assembly.json
  22. 6
      tests/expected_json/locked_ether-0.5.1.locked-ether.json
  23. 6
      tests/expected_json/locked_ether.locked-ether.json
  24. 6
      tests/expected_json/low_level_calls.low-level-calls.json
  25. 6
      tests/expected_json/multiple_calls_in_loop.calls-loop.json
  26. 6
      tests/expected_json/naming_convention.naming-convention.json
  27. 6
      tests/expected_json/old_solc.sol.json.solc-version.json
  28. 6
      tests/expected_json/pragma.0.4.24.pragma.json
  29. 52
      tests/expected_json/pragma.0.4.24.pragma.txt
  30. 6
      tests/expected_json/reentrancy-0.5.1.reentrancy-eth.json
  31. 10
      tests/expected_json/reentrancy-0.5.1.reentrancy.txt
  32. 6
      tests/expected_json/reentrancy.reentrancy-eth.json
  33. 6
      tests/expected_json/right_to_left_override.rtlo.json
  34. 6
      tests/expected_json/shadowing_abstract.shadowing-abstract.json
  35. 6
      tests/expected_json/shadowing_builtin_symbols.shadowing-builtin.json
  36. 6
      tests/expected_json/shadowing_local_variable.shadowing-local.json
  37. 6
      tests/expected_json/shadowing_state_variable.shadowing-state.json
  38. 6
      tests/expected_json/solc_version_incorrect.solc-version.json
  39. 6
      tests/expected_json/timestamp.timestamp.json
  40. 6
      tests/expected_json/too_many_digits.too-many-digits.json
  41. 6
      tests/expected_json/tx_origin-0.5.1.tx-origin.json
  42. 6
      tests/expected_json/tx_origin.tx-origin.json
  43. 6
      tests/expected_json/uninitialized-0.5.1.uninitialized-state.json
  44. 6
      tests/expected_json/uninitialized.uninitialized-state.json
  45. 6
      tests/expected_json/uninitialized_local_variable.uninitialized-local.json
  46. 6
      tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json
  47. 55
      tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.txt
  48. 6
      tests/expected_json/unused_return.unused-return.json
  49. 6
      tests/expected_json/unused_state.unused-state.json

@ -26,7 +26,6 @@ generate_expected_json(){
#generate_expected_json tests/pragma.0.4.24.sol "pragma"
#generate_expected_json tests/old_solc.sol.json "solc-version"
#generate_expected_json tests/reentrancy-0.5.1.sol "reentrancy-eth"
#generate_expected_json tests/reentrancy-0.5.1.sol "reentrancy"
#generate_expected_json tests/uninitialized_storage_pointer.sol "uninitialized-storage"
#generate_expected_json tests/tx_origin-0.5.1.sol "tx-origin"
#generate_expected_json tests/locked_ether-0.5.1.sol "locked-ether"

@ -29,7 +29,6 @@ from slither.exceptions import SlitherException
logging.basicConfig()
logger = logging.getLogger("Slither")
###################################################################################
###################################################################################
# region Process functions
@ -100,12 +99,27 @@ def process_files(filenames, args, detector_classes, printer_classes):
###################################################################################
###################################################################################
def wrap_json_stdout(success, error_message, results=None):
return {
"success": success,
"error": error_message,
"results": results
}
def output_json(results, filename):
json_result = wrap_json_stdout(True, None, results)
if filename is None:
# Write json to console
print(json.dumps(json_result))
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(results, f, indent=2)
json.dump(json_result, f, indent=2)
# endregion
###################################################################################
@ -328,7 +342,7 @@ def parse_args(detector_classes, printer_classes):
group_misc.add_argument('--json',
help='Export results as JSON',
help='Export the results as a JSON file ("--json -" to export to stdout)',
action='store',
default=defaults_flag_in_config['json'])
@ -457,6 +471,24 @@ class OutputWiki(argparse.Action):
parser.exit()
# endregion
###################################################################################
###################################################################################
# region CustomFormatter
###################################################################################
###################################################################################
class FormatterCryticCompile(logging.Formatter):
def format(self, record):
#for i, msg in enumerate(record.msg):
if record.msg.startswith('Compilation warnings/errors on '):
txt = record.args[1]
txt = txt.split('\n')
txt = [red(x) if 'Error' in x else x for x in txt]
txt = '\n'.join(txt)
record.args = (record.args[0], txt)
return super().format(record)
# endregion
###################################################################################
###################################################################################
@ -464,6 +496,7 @@ class OutputWiki(argparse.Action):
###################################################################################
###################################################################################
def main():
detectors, printers = get_detectors_and_printers()
@ -480,6 +513,11 @@ def main_impl(all_detector_classes, all_printer_classes):
# Set colorization option
set_colorization_enabled(not args.disable_color)
# If we are outputting json to stdout, we'll want to disable any logging.
stdout_json = args.json == "-"
if stdout_json:
logging.disable(logging.CRITICAL)
printer_classes = choose_printers(args, all_printer_classes)
detector_classes = choose_detectors(args, all_detector_classes)
@ -538,7 +576,7 @@ def main_impl(all_detector_classes, all_printer_classes):
raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename))
if args.json:
output_json(results, args.json)
output_json(results, None if stdout_json else args.json)
if args.checklist:
output_results_to_markdown(results)
# Dont print the number of result for printers
@ -552,13 +590,21 @@ def main_impl(all_detector_classes, all_printer_classes):
return
exit(results)
except SlitherException as e:
except SlitherException as se:
# Output our error accordingly, via JSON or logging.
if stdout_json:
print(json.dumps(wrap_json_stdout(False, repr(se), [])))
else:
logging.error(red('Error:'))
logging.error(red(e))
logging.error(red(se))
logging.error('Please report an issue to https://github.com/crytic/slither/issues')
sys.exit(-1)
except Exception:
# Output our error accordingly, via JSON or logging.
if stdout_json:
print(json.dumps(wrap_json_stdout(False, traceback.format_exc(), [])))
else:
logging.error('Error in %s' % args.filename)
logging.error(traceback.format_exc())
sys.exit(-1)
@ -570,21 +616,3 @@ if __name__ == '__main__':
# endregion
###################################################################################
###################################################################################
# region CustomFormatter
###################################################################################
###################################################################################
class FormatterCryticCompile(logging.Formatter):
def format(self, record):
#for i, msg in enumerate(record.msg):
if record.msg.startswith('Compilation warnings/errors on '):
txt = record.args[1]
txt = txt.split('\n')
txt = [red(x) if 'Error' in x else x for x in txt]
txt = '\n'.join(txt)
record.args = (record.args[0], txt)
return super().format(record)
# endregion

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "arbitrary-send",
"impact": "High",
@ -202,3 +205,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "arbitrary-send",
"impact": "High",
@ -202,3 +205,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "backdoor",
"impact": "High",
@ -50,3 +53,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "suicidal",
"impact": "High",
@ -50,3 +53,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "constable-states",
"impact": "Informational",
@ -110,3 +113,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "constant-function",
"impact": "Medium",
@ -65,3 +68,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "constant-function",
"impact": "Medium",
@ -250,3 +253,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "controlled-delegatecall",
"impact": "High",
@ -171,3 +174,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "deprecated-standards",
"impact": "Informational",
@ -178,3 +181,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "erc20-indexed",
"impact": "Informational",
@ -180,3 +183,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "external-function",
"impact": "Informational",
@ -244,3 +247,4 @@
]
}
]
}

@ -1 +1,5 @@
[]
{
"success": true,
"error": null,
"results": []
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "incorrect-equality",
"impact": "Medium",
@ -1364,3 +1367,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "erc20-interface",
"impact": "Medium",
@ -254,3 +257,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "erc721-interface",
"impact": "Medium",
@ -440,3 +443,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "assembly",
"impact": "Informational",
@ -102,3 +105,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "assembly",
"impact": "Informational",
@ -102,3 +105,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "assembly",
"impact": "Informational",
@ -256,3 +259,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "assembly",
"impact": "Informational",
@ -256,3 +259,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "locked-ether",
"impact": "Medium",
@ -67,3 +70,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "locked-ether",
"impact": "Medium",
@ -67,3 +70,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "low-level-calls",
"impact": "Informational",
@ -65,3 +68,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "calls-loop",
"impact": "Low",
@ -77,3 +80,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "naming-convention",
"impact": "Informational",
@ -364,3 +367,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "solc-version",
"impact": "Informational",
@ -23,3 +26,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "pragma",
"impact": "Informational",
@ -42,3 +45,4 @@
]
}
]
}

@ -1,44 +1,8 @@
ERROR:root:Error in tests/pragma.0.4.24.sol
ERROR:root:Traceback (most recent call last):
File "/home/monty/Private/tob/tools/crytic-compile/crytic_compile/platform/solc.py", line 189, in _run_solc
ret = json.loads(stdout)
File "/usr/lib/python3.6/json/__init__.py", line 354, in loads
return _default_decoder.decode(s)
File "/usr/lib/python3.6/json/decoder.py", line 339, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/usr/lib/python3.6/json/decoder.py", line 357, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/travis/build/crytic/slither/slither/slither.py", line 55, in __init__
crytic_compile = CryticCompile(contract, **kwargs)
File "/home/monty/Private/tob/tools/crytic-compile/crytic_compile/crytic_compile.py", line 68, in __init__
self._compile(target, **kwargs)
File "/home/monty/Private/tob/tools/crytic-compile/crytic_compile/crytic_compile.py", line 590, in _compile
self._platform.compile(self, target, **kwargs)
File "/home/monty/Private/tob/tools/crytic-compile/crytic_compile/platform/solc.py", line 33, in compile
working_dir=solc_working_dir)
File "/home/monty/Private/tob/tools/crytic-compile/crytic_compile/platform/solc.py", line 192, in _run_solc
raise InvalidCompilation(f'Invalid solc compilation {stderr}')
crytic_compile.platform.exceptions.InvalidCompilation: Invalid solc compilation tests/pragma.0.4.23.sol:1:1: Error: Source file requires different compiler version (current compiler is 0.5.1+commit.c8a2cb62.Linux.g++ - note that nightly builds are considered to be strictly less than the released version
pragma solidity ^0.4.23;
^----------------------^
tests/pragma.0.4.24.sol:1:1: Error: Source file requires different compiler version (current compiler is 0.5.1+commit.c8a2cb62.Linux.g++ - note that nightly builds are considered to be strictly less than the released version
pragma solidity ^0.4.24;
^----------------------^
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/travis/build/crytic/slither/slither/__main__.py", line 520, in main_impl
(results, number_contracts) = process(filename, args, detector_classes, printer_classes)
File "/home/travis/build/crytic/slither/slither/__main__.py", line 52, in process
**vars(args))
File "/home/travis/build/crytic/slither/slither/slither.py", line 58, in __init__
raise SlitherError('Invalid compilation: '+e)
TypeError: must be str, not InvalidCompilation
INFO:Detectors:
Different versions of Solidity is used in :
- Version used: ['^0.4.23', '^0.4.24']
- tests/pragma.0.4.23.sol#1 declares pragma solidity^0.4.23
- tests/pragma.0.4.24.sol#1 declares pragma solidity^0.4.24
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used
INFO:Slither:/home/travis/build/crytic/slither/scripts/../tests/expected_json/pragma.0.4.24.pragma.json exists already, the overwrite is prevented
INFO:Slither:tests/pragma.0.4.24.sol analyzed (1 contracts), 1 result(s) found

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "reentrancy-eth",
"impact": "High",
@ -273,3 +276,4 @@
]
}
]
}

@ -1,10 +0,0 @@
Traceback (most recent call last):
File "/home/monty/Envs/slither/bin/slither", line 11, in <module>
load_entry_point('slither-analyzer', 'console_scripts', 'slither')()
File "/home/travis/build/crytic/slither/slither/__main__.py", line 470, in main
main_impl(all_detector_classes=detectors, all_printer_classes=printers)
File "/home/travis/build/crytic/slither/slither/__main__.py", line 484, in main_impl
detector_classes = choose_detectors(args, all_detector_classes)
File "/home/travis/build/crytic/slither/slither/__main__.py", line 177, in choose_detectors
raise Exception('Error: {} is not a detector'.format(d))
Exception: Error: reentrancy is not a detector

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "reentrancy-eth",
"impact": "High",
@ -307,3 +310,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "rtlo",
"impact": "High",
@ -7,3 +10,4 @@
"elements": []
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "shadowing-abstract",
"impact": "Medium",
@ -42,3 +45,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "shadowing-builtin",
"impact": "Low",
@ -405,3 +408,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "shadowing-local",
"impact": "Low",
@ -320,3 +323,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "shadowing-state",
"impact": "High",
@ -42,3 +45,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "solc-version",
"impact": "Informational",
@ -42,3 +45,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "timestamp",
"impact": "Low",
@ -241,3 +244,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "too-many-digits",
"impact": "Informational",
@ -194,3 +197,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "tx-origin",
"impact": "Medium",
@ -172,3 +175,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "tx-origin",
"impact": "Medium",
@ -172,3 +175,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "uninitialized-state",
"impact": "High",
@ -299,3 +302,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "uninitialized-state",
"impact": "High",
@ -299,3 +302,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "uninitialized-local",
"impact": "Medium",
@ -71,3 +74,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "uninitialized-storage",
"impact": "High",
@ -77,3 +80,4 @@
]
}
]
}

@ -1,50 +1,5 @@
ERROR:root:Error in tests/uninitialized_storage_pointer.sol
ERROR:root:Traceback (most recent call last):
File "/home/monty/Private/tob/tools/crytic-compile/crytic_compile/platform/solc.py", line 189, in _run_solc
ret = json.loads(stdout)
File "/usr/lib/python3.6/json/__init__.py", line 354, in loads
return _default_decoder.decode(s)
File "/usr/lib/python3.6/json/decoder.py", line 339, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/usr/lib/python3.6/json/decoder.py", line 357, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/travis/build/crytic/slither/slither/slither.py", line 55, in __init__
crytic_compile = CryticCompile(contract, **kwargs)
File "/home/monty/Private/tob/tools/crytic-compile/crytic_compile/crytic_compile.py", line 68, in __init__
self._compile(target, **kwargs)
File "/home/monty/Private/tob/tools/crytic-compile/crytic_compile/crytic_compile.py", line 590, in _compile
self._platform.compile(self, target, **kwargs)
File "/home/monty/Private/tob/tools/crytic-compile/crytic_compile/platform/solc.py", line 33, in compile
working_dir=solc_working_dir)
File "/home/monty/Private/tob/tools/crytic-compile/crytic_compile/platform/solc.py", line 192, in _run_solc
raise InvalidCompilation(f'Invalid solc compilation {stderr}')
crytic_compile.platform.exceptions.InvalidCompilation: Invalid solc compilation tests/uninitialized_storage_pointer.sol:7:5: Error: No visibility specified. Did you intend to add "public"?
function func() {
^ (Relevant source part starts here and spans across multiple lines).
tests/uninitialized_storage_pointer.sol:1:1: Warning: Source file does not specify required compiler version! Consider adding "pragma solidity ^0.5.1;"
contract Uninitialized{
^ (Relevant source part starts here and spans across multiple lines).
tests/uninitialized_storage_pointer.sol:8:9: Error: Data location must be "storage" or "memory" for variable, but none was given.
St st; // non init, but never read so its fine
^---^
tests/uninitialized_storage_pointer.sol:10:9: Error: Data location must be "storage" or "memory" for variable, but none was given.
St st_bug;
^-------^
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/travis/build/crytic/slither/slither/__main__.py", line 520, in main_impl
(results, number_contracts) = process(filename, args, detector_classes, printer_classes)
File "/home/travis/build/crytic/slither/slither/__main__.py", line 52, in process
**vars(args))
File "/home/travis/build/crytic/slither/slither/slither.py", line 58, in __init__
raise SlitherError('Invalid compilation: '+e)
TypeError: must be str, not InvalidCompilation
INFO:Detectors:
st_bug in Uninitialized.func (tests/uninitialized_storage_pointer.sol#10) is a storage variable never initialiazed
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables
INFO:Slither:/home/travis/build/crytic/slither/scripts/../tests/expected_json/uninitialized_storage_pointer.uninitialized-storage.json exists already, the overwrite is prevented
INFO:Slither:tests/uninitialized_storage_pointer.sol analyzed (1 contracts), 1 result(s) found

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "unused-return",
"impact": "Medium",
@ -105,3 +108,4 @@
]
}
]
}

@ -1,4 +1,7 @@
[
{
"success": true,
"error": null,
"results": [
{
"check": "unused-state",
"impact": "Informational",
@ -25,3 +28,4 @@
]
}
]
}
Loading…
Cancel
Save