Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit c7b244d

Browse files
Merge pull request #172 from bcaller/formatter
Output tweaks
2 parents f56e761 + bf49259 commit c7b244d

File tree

10 files changed

+231
-56
lines changed

10 files changed

+231
-56
lines changed

‎.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ source =
1515
./tests
1616
omit =
1717
pyt/formatters/json.py
18+
pyt/formatters/screen.py
1819
pyt/formatters/text.py

‎pyt/__main__.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""The comand line module of PyT."""
22

3+
import logging
34
import os
45
import sys
56
from collections import defaultdict
@@ -12,10 +13,6 @@
1213
get_directory_modules,
1314
get_modules
1415
)
15-
from .formatters import (
16-
json,
17-
text
18-
)
1916
from .usage import parse_args
2017
from .vulnerabilities import (
2118
find_vulnerabilities,
@@ -30,6 +27,8 @@
3027
is_function_without_leading_
3128
)
3229

30+
log = logging.getLogger(__name__)
31+
3332

3433
def discover_files(targets, excluded_files, recursive=False):
3534
included_files = list()
@@ -41,11 +40,13 @@ def discover_files(targets, excluded_files, recursive=False):
4140
if file.endswith('.py') and file not in excluded_list:
4241
fullpath = os.path.join(root, file)
4342
included_files.append(fullpath)
43+
log.debug('Discovered file: %s', fullpath)
4444
if not recursive:
4545
break
4646
else:
4747
if target not in excluded_list:
4848
included_files.append(target)
49+
log.debug('Discovered file: %s', target)
4950
return included_files
5051

5152

@@ -64,6 +65,14 @@ def retrieve_nosec_lines(
6465
def main(command_line_args=sys.argv[1:]): # noqa: C901
6566
args = parse_args(command_line_args)
6667

68+
logging_level = (
69+
logging.ERROR if not args.verbose else
70+
logging.WARN if args.verbose == 1 else
71+
logging.INFO if args.verbose == 2 else
72+
logging.DEBUG
73+
)
74+
logging.basicConfig(level=logging_level, format='[%(levelname)s] %(name)s: %(message)s')
75+
6776
files = discover_files(
6877
args.targets,
6978
args.excluded_paths,
@@ -78,6 +87,7 @@ def main(command_line_args=sys.argv[1:]): # noqa: C901
7887

7988
cfg_list = list()
8089
for path in sorted(files):
90+
log.info("Processing %s", path)
8191
if not args.ignore_nosec:
8292
nosec_lines[path] = retrieve_nosec_lines(path)
8393

@@ -130,16 +140,13 @@ def main(command_line_args=sys.argv[1:]): # noqa: C901
130140
args.baseline
131141
)
132142

133-
if args.json:
134-
json.report(vulnerabilities, args.output_file)
135-
else:
136-
text.report(vulnerabilities, args.output_file)
143+
args.formatter.report(vulnerabilities, args.output_file, not args.only_unsanitised)
137144

138-
has_unsanitized_vulnerabilities = any(
145+
has_unsanitised_vulnerabilities = any(
139146
not isinstance(v, SanitisedVulnerability)
140147
for v in vulnerabilities
141148
)
142-
if has_unsanitized_vulnerabilities:
149+
if has_unsanitised_vulnerabilities:
143150
sys.exit(1)
144151

145152

‎pyt/cfg/stmt_visitor.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import ast
22
import itertools
3+
import logging
34
import os.path
5+
from pkgutil import iter_modules
46

57
from .alias_helper import (
68
as_alias_handler,
@@ -52,6 +54,9 @@
5254
remove_breaks
5355
)
5456

57+
log = logging.getLogger(__name__)
58+
uninspectable_modules = {module.name for module in iter_modules()} # Don't warn about failing to import these
59+
5560

5661
class StmtVisitor(ast.NodeVisitor):
5762
def __init__(self, allow_local_directory_imports=True):
@@ -429,9 +434,12 @@ def visit_Assign(self, node):
429434
else:
430435
label = LabelVisitor()
431436
label.visit(node)
432-
print('Assignment not properly handled.',
433-
'Could result in not finding a vulnerability.',
434-
'Assignment:', label.result)
437+
log.warn(
438+
'Assignment not properly handled in %s. Could result in not finding a vulnerability.'
439+
'Assignment: %s',
440+
getattr(self, 'filenames', ['?'])[0],
441+
self.label.result,
442+
)
435443
return self.append_node(AssignmentNode(
436444
label.result,
437445
label.result,
@@ -1022,6 +1030,10 @@ def visit_Import(self, node):
10221030
name.asname,
10231031
retrieve_import_alias_mapping(node.names)
10241032
)
1033+
for alias in node.names:
1034+
if alias.name not in uninspectable_modules:
1035+
log.warn("Cannot inspect module %s", alias.name)
1036+
uninspectable_modules.add(alias.name) # Don't repeatedly warn about this
10251037
return IgnoredNode()
10261038

10271039
def visit_ImportFrom(self, node):
@@ -1061,4 +1073,7 @@ def visit_ImportFrom(self, node):
10611073
retrieve_import_alias_mapping(node.names),
10621074
from_from=True
10631075
)
1076+
if node.module not in uninspectable_modules:
1077+
log.warn("Cannot inspect module %s", node.module)
1078+
uninspectable_modules.add(node.module)
10641079
return IgnoredNode()

‎pyt/core/ast_helper.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,25 @@
22
Useful when working with the ast module."""
33

44
import ast
5+
import logging
56
import os
67
import subprocess
78
from functools import lru_cache
89

910
from .transformer import PytTransformer
1011

11-
12+
log=logging.getLogger(__name__)
1213
BLACK_LISTED_CALL_NAMES = ['self']
1314
recursive = False
1415

1516

1617
def _convert_to_3(path): # pragma: no cover
1718
"""Convert python 2 file to python 3."""
1819
try:
19-
print('##### Trying to convert file to Python 3. #####')
20+
log.warn('##### Trying to convert %s to Python 3. #####', path)
2021
subprocess.call(['2to3', '-w', path])
2122
except subprocess.SubprocessError:
22-
print('Check if 2to3 is installed. '
23-
'https://docs.python.org/2/library/2to3.html')
23+
log.exception('Check if 2to3 is installed. https://docs.python.org/2/library/2to3.html')
2424
exit(1)
2525

2626

‎pyt/formatters/json.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
"""This formatter outputs the issues in JSON."""
2-
32
import json
43
from datetime import datetime
54

5+
from ..vulnerabilities.vulnerability_helper import SanitisedVulnerability
6+
67

78
def report(
89
vulnerabilities,
9-
fileobj
10+
fileobj,
11+
print_sanitised,
1012
):
1113
"""
1214
Prints issues in JSON format.
@@ -19,7 +21,10 @@ def report(
1921

2022
machine_output = {
2123
'generated_at': time_string,
22-
'vulnerabilities': [vuln.as_dict() for vuln in vulnerabilities]
24+
'vulnerabilities': [
25+
vuln.as_dict() for vuln in vulnerabilities
26+
if print_sanitised or not isinstance(vuln, SanitisedVulnerability)
27+
]
2328
}
2429

2530
result = json.dumps(

‎pyt/formatters/screen.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""This formatter outputs the issues as color-coded text."""
2+
from ..vulnerabilities.vulnerability_helper import SanitisedVulnerability, UnknownVulnerability
3+
4+
RESET = '033円[0m'
5+
BOLD = '033円[1m'
6+
UNDERLINE = '033円[4m'
7+
DANGER = '033円[31m'
8+
GOOD = '033円[32m'
9+
HIGHLIGHT = '033円[45;1m'
10+
RED_ON_WHITE = '033円[31m033円[107m'
11+
12+
13+
def color(string, color_string):
14+
return color_string + str(string) + RESET
15+
16+
17+
def report(
18+
vulnerabilities,
19+
fileobj,
20+
print_sanitised,
21+
):
22+
"""
23+
Prints issues in color-coded text format.
24+
25+
Args:
26+
vulnerabilities: list of vulnerabilities to report
27+
fileobj: The output file object, which may be sys.stdout
28+
"""
29+
n_vulnerabilities = len(vulnerabilities)
30+
unsanitised_vulnerabilities = [v for v in vulnerabilities if not isinstance(v, SanitisedVulnerability)]
31+
n_unsanitised = len(unsanitised_vulnerabilities)
32+
n_sanitised = n_vulnerabilities - n_unsanitised
33+
heading = "{} vulnerabilit{} found{}.\n".format(
34+
'No' if n_unsanitised == 0 else n_unsanitised,
35+
'y' if n_unsanitised == 1 else 'ies',
36+
" (plus {} sanitised)".format(n_sanitised) if n_sanitised else "",
37+
)
38+
vulnerabilities_to_print = vulnerabilities if print_sanitised else unsanitised_vulnerabilities
39+
with fileobj:
40+
for i, vulnerability in enumerate(vulnerabilities_to_print, start=1):
41+
fileobj.write(vulnerability_to_str(i, vulnerability))
42+
43+
if n_unsanitised == 0:
44+
fileobj.write(color(heading, GOOD))
45+
else:
46+
fileobj.write(color(heading, DANGER))
47+
48+
49+
def vulnerability_to_str(i, vulnerability):
50+
lines = []
51+
lines.append(color('Vulnerability {}'.format(i), UNDERLINE))
52+
lines.append('File: {}'.format(color(vulnerability.source.path, BOLD)))
53+
lines.append(
54+
'User input at line {}, source "{}":'.format(
55+
vulnerability.source.line_number,
56+
color(vulnerability.source_trigger_word, HIGHLIGHT),
57+
)
58+
)
59+
lines.append('\t{}'.format(color(vulnerability.source.label, RED_ON_WHITE)))
60+
if vulnerability.reassignment_nodes:
61+
previous_path = None
62+
lines.append('Reassigned in:')
63+
for node in vulnerability.reassignment_nodes:
64+
if node.path != previous_path:
65+
lines.append('\tFile: {}'.format(node.path))
66+
previous_path = node.path
67+
label = node.label
68+
if (
69+
isinstance(vulnerability, SanitisedVulnerability) and
70+
node.label == vulnerability.sanitiser.label
71+
):
72+
label = color(label, GOOD)
73+
lines.append(
74+
'\t Line {}:\t{}'.format(
75+
node.line_number,
76+
label,
77+
)
78+
)
79+
if vulnerability.source.path != vulnerability.sink.path:
80+
lines.append('File: {}'.format(color(vulnerability.sink.path, BOLD)))
81+
lines.append(
82+
'Reaches line {}, sink "{}"'.format(
83+
vulnerability.sink.line_number,
84+
color(vulnerability.sink_trigger_word, HIGHLIGHT),
85+
)
86+
)
87+
lines.append('\t{}'.format(
88+
color(vulnerability.sink.label, RED_ON_WHITE)
89+
))
90+
if isinstance(vulnerability, SanitisedVulnerability):
91+
lines.append(
92+
'This vulnerability is {}{} by {}'.format(
93+
color('potentially ', BOLD) if not vulnerability.confident else '',
94+
color('sanitised', GOOD),
95+
color(vulnerability.sanitiser.label, BOLD),
96+
)
97+
)
98+
elif isinstance(vulnerability, UnknownVulnerability):
99+
lines.append(
100+
'This vulnerability is unknown due to "{}"'.format(
101+
color(vulnerability.unknown_assignment.label, BOLD),
102+
)
103+
)
104+
return '\n'.join(lines) + '\n\n'

‎pyt/formatters/text.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,33 @@
11
"""This formatter outputs the issues as plain text."""
2+
from ..vulnerabilities.vulnerability_helper import SanitisedVulnerability
23

34

45
def report(
56
vulnerabilities,
6-
fileobj
7+
fileobj,
8+
print_sanitised,
79
):
810
"""
911
Prints issues in text format.
1012
1113
Args:
1214
vulnerabilities: list of vulnerabilities to report
1315
fileobj: The output file object, which may be sys.stdout
16+
print_sanitised: Print just unsanitised vulnerabilities or sanitised vulnerabilities as well
1417
"""
15-
number_of_vulnerabilities = len(vulnerabilities)
18+
n_vulnerabilities = len(vulnerabilities)
19+
unsanitised_vulnerabilities = [v for v in vulnerabilities if not isinstance(v, SanitisedVulnerability)]
20+
n_unsanitised = len(unsanitised_vulnerabilities)
21+
n_sanitised = n_vulnerabilities - n_unsanitised
22+
heading = "{} vulnerabilit{} found{}{}\n".format(
23+
'No' if n_unsanitised == 0 else n_unsanitised,
24+
'y' if n_unsanitised == 1 else 'ies',
25+
" (plus {} sanitised)".format(n_sanitised) if n_sanitised else "",
26+
':' if n_vulnerabilities else '.',
27+
)
28+
vulnerabilities_to_print = vulnerabilities if print_sanitised else unsanitised_vulnerabilities
1629
with fileobj:
17-
if number_of_vulnerabilities == 0:
18-
fileobj.write('No vulnerabilities found.\n')
19-
elif number_of_vulnerabilities == 1:
20-
fileobj.write('%s vulnerability found:\n' % number_of_vulnerabilities)
21-
else:
22-
fileobj.write('%s vulnerabilities found:\n' % number_of_vulnerabilities)
30+
fileobj.write(heading)
2331

24-
for i, vulnerability in enumerate(vulnerabilities, start=1):
32+
for i, vulnerability in enumerate(vulnerabilities_to_print, start=1):
2533
fileobj.write('Vulnerability {}:\n{}\n\n'.format(i, vulnerability))

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /