Skip to content

Commit

Permalink
SARIF + Version Bump (#35)
Browse files Browse the repository at this point in the history
* Added SARIF support
* Semgrep version bump
* Rule QA
  • Loading branch information
ajinabraham authored Nov 16, 2020
1 parent 55cb900 commit a17982f
Show file tree
Hide file tree
Showing 12 changed files with 337 additions and 143 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ Requires Python 3.6+ and supports only Mac and Linux

```bash
$ njsscan
usage: njsscan [-h] [--json] [--sonarqube] [-o OUTPUT] [--missing-controls]
[-w] [-v]
usage: njsscan [-h] [--json] [--sarif] [--sonarqube] [-o OUTPUT]
[--missing-controls] [-w] [-v]
[path [path ...]]

positional arguments:
Expand All @@ -40,6 +40,7 @@ positional arguments:
optional arguments:
-h, --help show this help message and exit
--json set output format as JSON
--sarif set output format as SARIF 2.1.0
--sonarqube set output format compatible with SonarQube
-o OUTPUT, --output OUTPUT
output filename to save the result
Expand Down
2 changes: 1 addition & 1 deletion njsscan/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
__title__ = 'njsscan'
__authors__ = 'Ajin Abraham'
__copyright__ = 'Copyright 2020 Ajin Abraham, OpenSecurity'
__version__ = '0.1.6'
__version__ = '0.1.7'
__version_info__ = tuple(int(i) for i in __version__.split('.'))
__all__ = [
'__title__',
Expand Down
149 changes: 15 additions & 134 deletions njsscan/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,139 +2,16 @@
# -*- coding: utf_8 -*-
"""The nodejsscan cli: njsscan."""
import argparse
import json
import sys

from njsscan import __version__
from njsscan.logger import init_logger
from njsscan.njsscan import NJSScan

logger = init_logger(__name__)


def cli_out(rule_id, details):
"""Get CLI friendly format."""
items = []
items.append('\n==================================================='
'===================================================')
items.append(f'RULE ID: {rule_id}')
for meta, value in details['metadata'].items():
if meta == 'id':
continue
meta_format = meta.upper().replace('_', '')
items.append(f'{meta_format}: {value}')
items.append('==================================================='
'===================================================')
files = details.get('files')
if not files:
return '\n'.join(items)
items.append('\n__________________FILES___________________________')
for match in files:
items.append('\n')
file_path = match['file_path']
items.append(f'File: {file_path}')
position = match['match_position']
items.append(f'Match Position: {position[0]} - {position[1]}')
lines = match.get('match_lines')
line = (lines[0] if lines[0] == lines[1]
else f'{lines[0]}: {lines[1]}')
items.append(f'Line Number(s): {line}')
match_string = match['match_string']
if isinstance(match_string, list):
match_string = '\n'.join(ln.strip() for ln in match_string)
items.append(f'Match String: {match_string}')
return '\n'.join(items)


def format_output(outfile, scan_results):
"""Format output printing."""
if not scan_results:
return
scan_results.pop('errors', None)
buffer = []
for out in scan_results:
for rule_id, details in scan_results[out].items():
formatted = cli_out(rule_id, details)
buffer.append(formatted)
severity = details['metadata']['severity'].lower()
if not outfile:
if severity == 'error':
logger.error(formatted)
elif severity == 'warning':
logger.warning(formatted)
else:
logger.info(formatted)
if outfile and buffer:
outdata = '\n'.join(buffer)
with open(outfile, 'w') as of:
of.write(outdata)
return buffer


def json_output(outfile, scan_results):
"""JSON Output."""
if outfile:
with open(outfile, 'w') as of:
json.dump(scan_results, of, sort_keys=True,
indent=2, separators=(',', ': '))
else:
json_output = (json.dumps(scan_results, sort_keys=True,
indent=2, separators=(',', ': ')))
print(json_output)
return json_output


def sonarqube_output(outfile, scan_results):
"""Sonarqube JSON Output."""
sonarqube_issues = []
for i in ['nodejs', 'templates']:
for k, v in scan_results[i].items():
issue = get_sonarqube_issue(v)
issue['ruleId'] = k
sonarqube_issues.append(issue)
sonarqube_report = {
'issues': sonarqube_issues,
}
return json_output(outfile, sonarqube_report)


def get_sonarqube_issue(njsscan_issue):
sonarqube_severity_mapping = {
'ERROR': 'CRITICAL',
'WARNING': 'MAJOR',
'INFO': 'MINOR',
}
secondary_locations = []
issue_data = njsscan_issue['metadata']
# Handle missing controls
if not njsscan_issue.get('files'):
primary_location = None
else:
for ix, file in enumerate(njsscan_issue['files']):
text_range = {
'startLine': file['match_lines'][0],
'endLine': file['match_lines'][1],
'startColumn': file['match_position'][0],
'endColumn': file['match_position'][1],
}
location = {
'message': issue_data['description'],
'filePath': file['file_path'],
'textRange': text_range,
}
if ix == 0:
primary_location = location
else:
secondary_locations.append(location)
issue = {
'engineId': 'njsscan',
'type': 'VULNERABILITY',
'severity': sonarqube_severity_mapping[issue_data['severity']],
'primaryLocation': primary_location,
}
if secondary_locations:
issue['secondaryLocations'] = secondary_locations
return issue
from njsscan.formatters import (
cli,
json,
sarif,
sonarqube,
)


def handle_exit(results, exit_warn):
Expand Down Expand Up @@ -162,6 +39,9 @@ def main():
parser.add_argument('--json',
help='set output format as JSON',
action='store_true')
parser.add_argument('--sarif',
help='set output format as SARIF 2.1.0',
action='store_true')
parser.add_argument('--sonarqube',
help='set output format compatible with SonarQube',
action='store_true')
Expand All @@ -182,19 +62,20 @@ def main():
action='store_true')
args = parser.parse_args()
if args.path:
is_json = args.json or args.sonarqube
is_json = args.json or args.sonarqube or args.sarif
scan_results = NJSScan(
args.path,
is_json,
args.missing_controls,
).scan()
if args.sonarqube:
sonarqube_output(args.output, scan_results)
sonarqube.sonarqube_output(args.output, scan_results)
elif args.json:
json_output(args.output, scan_results)
json.json_output(args.output, scan_results)
elif args.sarif:
sarif.sarif_output(args.output, scan_results, __version__)
else:
format_output(args.output, scan_results)

cli.cli_output(args.output, scan_results)
handle_exit(scan_results, args.exit_warning)

elif args.version:
Expand Down
Empty file added njsscan/formatters/__init__.py
Empty file.
64 changes: 64 additions & 0 deletions njsscan/formatters/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# -*- coding: utf_8 -*-
"""CLI njsscan output format."""
from njsscan.logger import init_logger

logger = init_logger(__name__)


def format_cli(rule_id, details):
"""Get CLI friendly format."""
items = []
items.append('\n==================================================='
'===================================================')
items.append(f'RULE ID: {rule_id}')
for meta, value in details['metadata'].items():
if meta == 'id':
continue
meta_format = meta.upper().replace('_', '')
items.append(f'{meta_format}: {value}')
items.append('==================================================='
'===================================================')
files = details.get('files')
if not files:
return '\n'.join(items)
items.append('\n__________________FILES___________________________')
for match in files:
items.append('\n')
file_path = match['file_path']
items.append(f'File: {file_path}')
position = match['match_position']
items.append(f'Match Position: {position[0]} - {position[1]}')
lines = match.get('match_lines')
line = (lines[0] if lines[0] == lines[1]
else f'{lines[0]}: {lines[1]}')
items.append(f'Line Number(s): {line}')
match_string = match['match_string']
if isinstance(match_string, list):
match_string = '\n'.join(ln.strip() for ln in match_string)
items.append(f'Match String: {match_string}')
return '\n'.join(items)


def cli_output(outfile, scan_results):
"""Format output printing."""
if not scan_results:
return
scan_results.pop('errors', None)
buffer = []
for out in scan_results:
for rule_id, details in scan_results[out].items():
formatted = format_cli(rule_id, details)
buffer.append(formatted)
severity = details['metadata']['severity'].lower()
if not outfile:
if severity == 'error':
logger.error(formatted)
elif severity == 'warning':
logger.warning(formatted)
else:
logger.info(formatted)
if outfile and buffer:
outdata = '\n'.join(buffer)
with open(outfile, 'w') as of:
of.write(outdata)
return buffer
16 changes: 16 additions & 0 deletions njsscan/formatters/json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: utf_8 -*-
"""JSON output format."""
import json


def json_output(outfile, scan_results):
"""JSON Output."""
if outfile:
with open(outfile, 'w') as of:
json.dump(scan_results, of, sort_keys=True,
indent=2, separators=(',', ': '))
else:
json_output = (json.dumps(scan_results, sort_keys=True,
indent=2, separators=(',', ': ')))
print(json_output)
return json_output
Loading

0 comments on commit a17982f

Please sign in to comment.