Skip to content

Commit ee9c949

Browse files
committed
feat(text-output): only display introduced secrets unless verbose is enabled
1 parent 4a68be2 commit ee9c949

File tree

2 files changed

+75
-12
lines changed

2 files changed

+75
-12
lines changed

ggshield/verticals/secret/output/secret_text_output_handler.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import Dict, List, Optional, Tuple
44

55
from pygitguardian.client import VERSIONS
6-
from pygitguardian.models import PolicyBreak
6+
from pygitguardian.models import DiffKind, PolicyBreak
77

88
from ggshield.core.filter import group_policy_breaks_by_ignore_sha
99
from ggshield.core.lines import Line, get_offset, get_padding
@@ -31,17 +31,29 @@
3131
class SecretTextOutputHandler(SecretOutputHandler):
3232
def _process_scan_impl(self, scan: SecretScanCollection) -> str:
3333
"""Output Secret Scan Collection in text format"""
34-
processed_scan_results = self.process_scan_results(scan)
34+
diff_kinds = [DiffKind.ADDITION]
35+
if self.verbose:
36+
diff_kinds += [DiffKind.DELETION, DiffKind.CONTEXT]
3537

38+
processed_scan_results = self.process_scan_results(scan, diff_kinds=diff_kinds)
3639
scan_buf = StringIO()
3740
if self.verbose:
3841
scan_buf.write(secrets_engine_version())
3942
scan_buf.write(processed_scan_results)
4043
if not processed_scan_results:
44+
if self.ignore_known_secrets and scan.known_secrets_count:
45+
scan_buf.write(no_new_leak_message())
46+
elif scan.deletion_or_context_secrets_count:
47+
scan_buf.write(no_introduced_leak_message())
48+
else:
49+
scan_buf.write(no_leak_message())
50+
51+
if not self.verbose and scan.deletion_or_context_secrets_count:
4152
scan_buf.write(
42-
no_new_leak_message()
43-
if (self.ignore_known_secrets and scan.known_secrets_count)
44-
else no_leak_message()
53+
f"\nWarning: {scan.deletion_or_context_secrets_count} "
54+
f"{pluralize('secret', scan.deletion_or_context_secrets_count)} ignored "
55+
f"because {pluralize('it is', scan.deletion_or_context_secrets_count, 'they are')} removed or in the "
56+
f"context of a commit.\nUse `--verbose` for more details.\n"
4557
)
4658

4759
if self.ignore_known_secrets and scan.known_secrets_count > 0:
@@ -52,22 +64,29 @@ def _process_scan_impl(self, scan: SecretScanCollection) -> str:
5264
)
5365

5466
if self.verbose:
55-
scan_buf.write(self.process_scan_results(scan, True))
67+
scan_buf.write(
68+
self.process_scan_results(
69+
scan, diff_kinds=diff_kinds, show_only_known_secrets=True
70+
)
71+
)
5672
else:
5773
scan_buf.write("Use `--verbose` for more details.\n")
5874

5975
return scan_buf.getvalue()
6076

6177
def process_scan_results(
62-
self, scan: SecretScanCollection, show_only_known_secrets: bool = False
78+
self,
79+
scan: SecretScanCollection,
80+
diff_kinds: List[DiffKind],
81+
show_only_known_secrets: bool = False,
6382
) -> str:
6483
"""Iterate through the scans and sub-scan results to prepare the display."""
6584
results_buf = StringIO()
6685
if scan.results:
6786
current_result_buf = StringIO()
6887
for result in scan.results.results:
6988
current_result_buf.write(
70-
self.process_result(result, show_only_known_secrets)
89+
self.process_result(result, diff_kinds, show_only_known_secrets)
7190
)
7291
current_result_string = current_result_buf.getvalue()
7392

@@ -80,14 +99,17 @@ def process_scan_results(
8099
if scan.scans:
81100
for sub_scan in scan.scans:
82101
inner_scan_str = self.process_scan_results(
83-
sub_scan, show_only_known_secrets
102+
sub_scan, diff_kinds, show_only_known_secrets
84103
)
85104
results_buf.write(inner_scan_str)
86105

87106
return results_buf.getvalue()
88107

89108
def process_result(
90-
self, result: Result, show_only_known_secrets: bool = False
109+
self,
110+
result: Result,
111+
diff_kinds: List[DiffKind],
112+
show_only_known_secrets: bool = False,
91113
) -> str:
92114
"""
93115
Build readable message on the found incidents.
@@ -99,7 +121,9 @@ def process_result(
99121
"""
100122
result_buf = StringIO()
101123

102-
sha_dict = group_policy_breaks_by_ignore_sha(result.scan.policy_breaks)
124+
sha_dict = group_policy_breaks_by_ignore_sha(
125+
result.policy_breaks(diff_kinds=diff_kinds)
126+
)
103127

104128
if not self.show_secrets:
105129
result.censor()
@@ -306,6 +330,13 @@ def no_new_leak_message() -> str:
306330
return format_text("\nNo new secrets have been found\n", STYLE["no_secret"])
307331

308332

333+
def no_introduced_leak_message() -> str:
334+
"""
335+
Build a message if no secrets are introduced.
336+
"""
337+
return format_text("\nNo introduced secrets have been found\n", STYLE["no_secret"])
338+
339+
309340
def format_line_with_secret(
310341
line_content: str,
311342
secret_index_start: int,

ggshield/verticals/secret/secret_scan_collection.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33
from typing import Any, Dict, Iterable, List, NamedTuple, Optional, Tuple, Union, cast
44

55
from pygitguardian import GGClient
6-
from pygitguardian.models import Detail, DiffKind, Match, ScanResult, SecretIncident
6+
from pygitguardian.models import (
7+
Detail,
8+
DiffKind,
9+
Match,
10+
PolicyBreak,
11+
ScanResult,
12+
SecretIncident,
13+
)
714

815
from ggshield.core.errors import UnexpectedError, handle_api_error
916
from ggshield.core.filter import group_policy_breaks_by_ignore_sha
@@ -70,6 +77,18 @@ def censor(self) -> None:
7077
def has_policy_breaks(self) -> bool:
7178
return self.scan.has_policy_breaks
7279

80+
def policy_breaks(
81+
self, *, diff_kinds: Optional[List[DiffKind]] = None
82+
) -> List[PolicyBreak]:
83+
policy_breaks = self.scan.policy_breaks
84+
if diff_kinds is not None and self.scan.is_diff:
85+
policy_breaks = [
86+
policy_break
87+
for policy_break in self.scan.policy_breaks
88+
if policy_break.diff_kind in diff_kinds
89+
]
90+
return policy_breaks
91+
7392

7493
class Error(NamedTuple):
7594
files: List[Tuple[str, Filemode]]
@@ -133,6 +152,9 @@ def __init__(
133152
self.known_secrets_count,
134153
self.new_secrets_count,
135154
) = self._get_known_new_secrets_count()
155+
self.deletion_or_context_secrets_count = (
156+
self._get_deletion_or_context_secrets_count()
157+
)
136158

137159
@property
138160
def has_new_secrets(self) -> bool:
@@ -163,6 +185,16 @@ def introduces_secret(self) -> bool:
163185
return True
164186
return False
165187

188+
def _get_deletion_or_context_secrets_count(self) -> int:
189+
deletion_or_context_secrets = 0
190+
for result in self.get_all_results():
191+
if not result.scan.is_diff:
192+
continue
193+
for policy_break in result.scan.policy_breaks:
194+
if policy_break.diff_kind in [DiffKind.DELETION, DiffKind.CONTEXT]:
195+
deletion_or_context_secrets += 1
196+
return deletion_or_context_secrets
197+
166198
def _get_known_new_secrets_count(self) -> Tuple[int, int]:
167199
policy_breaks = []
168200
for result in self.get_all_results():

0 commit comments

Comments
 (0)