Skip to content

Commit 7515401

Browse files
committed
Diff branch (#1)
* --diff-branch * tests * https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose * test * + * + * test * ? * fix git diff command syntax, tests * spelling * test * --diff-branch Signed-off-by: Nick Jones <[email protected]> * tests Signed-off-by: Nick Jones <[email protected]> * https://docs.pytest.org/en/stable/deprecations.html#support-for-tests-written-for-nose Signed-off-by: Nick Jones <[email protected]> * test Signed-off-by: Nick Jones <[email protected]> * + Signed-off-by: Nick Jones <[email protected]> * + Signed-off-by: Nick Jones <[email protected]> * test Signed-off-by: Nick Jones <[email protected]> * ? Signed-off-by: Nick Jones <[email protected]> * fix git diff command syntax, tests Signed-off-by: Nick Jones <[email protected]> * spelling Signed-off-by: Nick Jones <[email protected]> * test Signed-off-by: Nick Jones <[email protected]> * formatting corrections * fix * fix * change quote style * update .secrets.baseline --------- Signed-off-by: Nick Jones <[email protected]>
1 parent 58f54db commit 7515401

File tree

9 files changed

+138
-7
lines changed

9 files changed

+138
-7
lines changed

.secrets.baseline

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"files": "test_data/.*|tests/.*|^.secrets.baseline$",
44
"lines": null
55
},
6-
"generated_at": "2023-05-25T14:44:17Z",
6+
"generated_at": "2023-12-18T20:51:27Z",
77
"plugins_used": [
88
{
99
"name": "AWSKeyDetector"
@@ -200,15 +200,15 @@
200200
"hashed_secret": "f32a07369c6fd4eaacb1f5a8877824ef98204a1c",
201201
"is_secret": false,
202202
"is_verified": false,
203-
"line_number": 102,
203+
"line_number": 105,
204204
"type": "Secret Keyword",
205205
"verified_result": null
206206
},
207207
{
208208
"hashed_secret": "1af17e73721dbe0c40011b82ed4bb1a7dbe3ce29",
209209
"is_secret": false,
210210
"is_verified": false,
211-
"line_number": 105,
211+
"line_number": 108,
212212
"type": "Secret Keyword",
213213
"verified_result": null
214214
}
@@ -242,7 +242,7 @@
242242
}
243243
]
244244
},
245-
"version": "0.13.1+ibm.58.dss",
245+
"version": "0.13.1+ibm.62.dss",
246246
"word_list": {
247247
"file": null,
248248
"hash": null

detect_secrets/core/baseline.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def initialize(
2424
output_raw=False,
2525
output_verified_false=False,
2626
suppress_unscannable_file_warnings=False,
27+
diff_branch=None,
2728
):
2829
"""Scans the entire codebase for secrets, and returns a
2930
SecretsCollection object.
@@ -49,6 +50,10 @@ def initialize(
4950
:type suppress_unscannable_file_warnings boolean
5051
:param suppress_unscannable_file_warnings: whether or not to suppress unscannable file warnings
5152
53+
:type diff_branch: str|None
54+
:param diff_branch: optional name of branch to check for
55+
differences against in determining files to scan.
56+
5257
:rtype: SecretsCollection
5358
"""
5459
output = SecretsCollection(
@@ -68,6 +73,10 @@ def initialize(
6873
files_to_scan.extend(
6974
_get_files_recursively(element),
7075
)
76+
elif diff_branch is not None:
77+
files_to_scan.extend(
78+
_get_git_tracked_diff_files(element, diff_branch),
79+
)
7180
else:
7281
files_to_scan.extend(
7382
_get_git_tracked_files(element),
@@ -380,6 +389,47 @@ def _get_git_tracked_files(rootdir='.'):
380389
return output
381390

382391

392+
def _get_git_tracked_diff_files(rootdir='.', diff_branch=None):
393+
"""On incremental builds it is only necessary to scan the files that
394+
have changed. This will allow a scan of files that have differences
395+
from the named branch. The filter does not list filess that are
396+
deleted because it is impossible to scan them now.
397+
398+
:type rootdir: str
399+
:param rootdir: root directory of where you want to list files from
400+
401+
:type diff_branch: str
402+
:param diff_branch: name of branch to check diferences from.
403+
'test' would find files with differences between the current branch
404+
and the local test branch.
405+
'origin/main' would find files with differences between the current
406+
branch and the remote main branch.
407+
408+
:rtype: set|None
409+
:returns: filepaths to files with differences from the diff_branch
410+
which git currently tracks (locally)
411+
"""
412+
output = []
413+
try:
414+
with open(os.devnull, 'w') as fnull:
415+
git_files = subprocess.check_output(
416+
[
417+
'git',
418+
'diff',
419+
'--name-only',
420+
'--diff-filter=ACMRTUX',
421+
diff_branch,
422+
'--', rootdir,
423+
],
424+
stderr=fnull,
425+
)
426+
for filename in git_files.decode('utf-8').split():
427+
output.append(filename)
428+
except subprocess.CalledProcessError:
429+
pass
430+
return output
431+
432+
383433
def _get_files_recursively(rootdir):
384434
"""Sometimes, we want to use this tool with non-git repositories.
385435
This function allows us to do so.

detect_secrets/core/usage.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ def add_arguments(self):
205205
self._add_initialize_baseline_argument()\
206206
._add_adhoc_scanning_argument()\
207207
._add_output_raw_argument()\
208-
._add_suppress_unscannable_file_warnings()
208+
._add_suppress_unscannable_file_warnings()\
209+
._add_diff_branch()\
209210

210211
PluginOptions(self.parser).add_arguments()
211212

@@ -289,6 +290,18 @@ def _add_suppress_unscannable_file_warnings(self):
289290
add_suppress_unscannable_file_warnings(self.parser)
290291
return self
291292

293+
def _add_diff_branch(self):
294+
self.parser.add_argument(
295+
'--diff-branch',
296+
type=str,
297+
help=(
298+
'Scan only files that are tracked to git containing '
299+
'differences from the named branch.'
300+
),
301+
dest='diff_branch',
302+
)
303+
return self
304+
292305

293306
class AuditOptions:
294307
def __init__(self, subparser):

detect_secrets/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ def _perform_scan(args, plugins, automaton, word_list_hash):
189189
output_raw=args.output_raw,
190190
output_verified_false=args.output_verified_false,
191191
suppress_unscannable_file_warnings=args.suppress_unscannable_file_warnings,
192+
diff_branch=args.diff_branch,
192193
).format_for_baseline_output()
193194

194195
if old_baseline:
@@ -206,7 +207,8 @@ def _get_existing_baseline(import_filename):
206207
try:
207208
return _read_from_file(import_filename[0])
208209
except FileNotFoundError as fnf_error:
209-
if fnf_error.errno == 2: # create new baseline if not existed
210+
if fnf_error.errno == 2 or fnf_error.errno == 129:
211+
# create new baseline if not existed, 129 is for z/OS
210212
return None
211213
else: # throw exception for other cases
212214
print(

docs/cheat-sheet.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ detect-secrets scan file1 file2
7676

7777
# Scan all files except for .gitignore
7878
detect-secrets scan --all-files
79+
80+
# Scan only files that are tracked to git containing differences from the named branch
81+
detect-secrets scan --diff-branch diff_branch_name
7982
```
8083

8184
### Ad-hoc scan on a single string

tests/__init__.py

Whitespace-only changes.

tests/core/__init__.py

Whitespace-only changes.

tests/core/baseline_test.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
class TestInitializeBaseline:
2424

25-
def setup(self):
25+
def setup_method(self):
2626
self.plugins = (
2727
Base64HighEntropyString(4.5),
2828
HexHighEntropyString(3),
@@ -33,12 +33,14 @@ def get_results(
3333
path=['./test_data/files'],
3434
exclude_files_regex=None,
3535
scan_all_files=False,
36+
diff_branch=None,
3637
):
3738
return baseline.initialize(
3839
path,
3940
self.plugins,
4041
exclude_files_regex=exclude_files_regex,
4142
should_scan_all_files=scan_all_files,
43+
diff_branch=diff_branch,
4244
).json()
4345

4446
@pytest.mark.parametrize(
@@ -184,6 +186,44 @@ def test_scan_all_files_with_bad_symlinks(self):
184186
)
185187
assert len(results.keys()) == 0
186188

189+
def test_diff_branch_nodiff(self):
190+
results = self.get_results(path=['./test_data/files'], diff_branch='origin/master')
191+
192+
# No expected results, because differences
193+
assert not results
194+
195+
def test_diff_branch_diff(self):
196+
with mock_git_calls(
197+
'detect_secrets.core.baseline.subprocess.check_output',
198+
(
199+
SubprocessMock(
200+
expected_input='git diff --name-only --diff-filter=ACMRTUX '
201+
+ 'origin/master -- ./test_data/files',
202+
mocked_output=b'test_data/files/file_with_secrets.py\n',
203+
),
204+
),
205+
):
206+
results = self.get_results(path=['./test_data/files'], diff_branch='origin/master')
207+
assert len(results.keys()) == 1
208+
assert len(results['test_data/files/file_with_secrets.py']) == 1
209+
210+
def test_diff_branch_diff2(self):
211+
with mock_git_calls(
212+
'detect_secrets.core.baseline.subprocess.check_output',
213+
(
214+
SubprocessMock(
215+
expected_input='git diff --name-only --diff-filter=ACMRTUX '
216+
+ 'origin/master -- ./test_data/files',
217+
mocked_output=b'test_data/files/file_with_secrets.py\n'
218+
+ b'test_data/files/tmp/file_with_secrets.py\n',
219+
),
220+
),
221+
):
222+
results = self.get_results(path=['./test_data/files'], diff_branch='origin/master')
223+
assert len(results.keys()) == 2
224+
assert len(results['test_data/files/file_with_secrets.py']) == 1
225+
assert len(results['test_data/files/tmp/file_with_secrets.py']) == 2
226+
187227

188228
class TestGetSecretsNotInBaseline:
189229

tests/main_test.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def test_scan_basic(self, mock_baseline_initialize):
9696
word_list_file=None,
9797
word_list_hash=None,
9898
suppress_unscannable_file_warnings=False,
99+
diff_branch=None,
99100
)
100101

101102
def test_scan_with_rootdir(self, mock_baseline_initialize):
@@ -113,6 +114,7 @@ def test_scan_with_rootdir(self, mock_baseline_initialize):
113114
word_list_file=None,
114115
word_list_hash=None,
115116
suppress_unscannable_file_warnings=False,
117+
diff_branch=None,
116118
)
117119

118120
def test_scan_with_exclude_args(self, mock_baseline_initialize):
@@ -132,6 +134,7 @@ def test_scan_with_exclude_args(self, mock_baseline_initialize):
132134
word_list_file=None,
133135
word_list_hash=None,
134136
suppress_unscannable_file_warnings=False,
137+
diff_branch=None,
135138
)
136139

137140
@pytest.mark.parametrize(
@@ -217,6 +220,25 @@ def test_scan_with_all_files_flag(self, mock_baseline_initialize):
217220
word_list_file=None,
218221
word_list_hash=None,
219222
suppress_unscannable_file_warnings=False,
223+
diff_branch=None,
224+
)
225+
226+
def test_scan_with_diff_branch(self, mock_baseline_initialize):
227+
with mock_stdin():
228+
assert main('scan --diff-branch some_branch_here'.split()) == 0
229+
230+
mock_baseline_initialize.assert_called_once_with(
231+
plugins=Any(tuple),
232+
exclude_files_regex=None,
233+
exclude_lines_regex=None,
234+
path='.',
235+
should_scan_all_files=False,
236+
output_raw=False,
237+
output_verified_false=False,
238+
word_list_file=None,
239+
word_list_hash=None,
240+
suppress_unscannable_file_warnings=False,
241+
diff_branch='some_branch_here',
220242
)
221243

222244
def test_reads_from_stdin(self, mock_merge_baseline):
@@ -274,6 +296,7 @@ def test_reads_non_existed_baseline_from_file(
274296
word_list_file=None,
275297
word_list_hash=None,
276298
suppress_unscannable_file_warnings=False,
299+
diff_branch=None,
277300
)
278301
mock_merge_baseline.assert_not_called()
279302

0 commit comments

Comments
 (0)