def test_print_stats_failed_conditions_zero_and_multiple_secrets_per_condition( self, capsys, live_secrets_fixture, unaudited_secrets_fixture, audited_real_secrets_fixture, ): unaudited_secrets_fixture = audited_real_secrets_fixture = [] with self.mock_env(): print_stats( live_secrets_fixture, unaudited_secrets_fixture, audited_real_secrets_fixture, baseline_filename, True, True, True, ) secrets = audit.get_secrets_list_from_file(baseline_filename) captured = capsys.readouterr() assert captured.out == '\n{} potential secrets in {} were reviewed.'.format( colorize(len(secrets), AnsiColor.BOLD), colorize(baseline_filename, AnsiColor.BOLD), ) + ' Found {} live secret, {} unaudited secrets'.format( colorize(len(live_secrets_fixture), AnsiColor.BOLD), colorize(len(unaudited_secrets_fixture), AnsiColor.BOLD), ) + ' and {} secrets that were audited as real.\n\n'.format( colorize(len(audited_real_secrets_fixture), AnsiColor.BOLD), )
def test_print_summary_all_failed_conditions(self, capsys): unaudited_return_code = live_return_code = audited_real_return_code = 1 print_summary( unaudited_return_code, live_return_code, audited_real_return_code, baseline_filename, True, True, True, False, ) captured = capsys.readouterr() assert captured.out == '\nFailed conditions:\n\n{}\n{}\n{}\n{}\n{}\n{}{}{}\n{}\n'.format( colorize('\t- Unaudited secrets were found', AnsiColor.BOLD), '\n\t\tRun detect-secrets audit {}, and audit all potential secrets.\n' .format(baseline_filename, ), colorize('\t- Live secrets were found', AnsiColor.BOLD), '\n\t\tRevoke all live secrets and remove them from the codebase.' ' Afterwards, run detect-secrets scan --update {} to re-scan.\n'. format(baseline_filename, ), colorize('\t- Audited true secrets were found', AnsiColor.BOLD), '\n\t\tIf any active secrets meet this condition, revoke them.', ' Then, remove secrets that were audited as real from the codebase and', ' run detect-secrets scan --update {} to re-scan.\n'.format( baseline_filename, ), 'For additional help, run detect-secrets audit --help.\n', )
def test_print_stats_only_audited_real( self, capsys, live_secrets_fixture, unaudited_secrets_fixture, audited_real_secrets_fixture, ): unaudited_secrets_fixture = live_secrets_fixture = [] with self.mock_env(): print_stats( live_secrets_fixture, unaudited_secrets_fixture, audited_real_secrets_fixture, baseline_filename, False, False, True, ) secrets = audit.get_secrets_list_from_file(baseline_filename) captured = capsys.readouterr() assert captured.out == '\n{} potential secrets in {} were reviewed.'.format( colorize(len(secrets), AnsiColor.BOLD), colorize(baseline_filename, AnsiColor.BOLD), ) + ' Found {} secret that was audited as real.\n\n'.format( colorize(len(audited_real_secrets_fixture), AnsiColor.BOLD), )
def test_print_stats_no_failed_conditions( self, capsys, live_secrets_fixture, unaudited_secrets_fixture, audited_real_secrets_fixture, ): live_secrets_fixture = unaudited_secrets_fixture = audited_real_secrets_fixture = [] with self.mock_env(): print_stats( live_secrets_fixture, unaudited_secrets_fixture, audited_real_secrets_fixture, baseline_filename, True, True, True, ) secrets = audit.get_secrets_list_from_file(baseline_filename) captured = capsys.readouterr() assert captured.out == ('\n{} potential secrets in {} were reviewed.' ' All checks have passed.\n\n'.format( colorize(len(secrets), AnsiColor.BOLD), colorize(baseline_filename, AnsiColor.BOLD), ))
def print_line(name, time, baseline, timeout): """ :type name: str :type time: float :param time: seconds it took to execute :type baseline: float :param baseline: expected seconds to execute :type timeout: float :param timeout: used to calculate difference when either current execution or baseline execution exceeds timeout. """ if not time: time_string = 'Timeout exceeded!' else: time_string = '{}s'.format(str(time)) if baseline is not None: if time and baseline: difference = round(baseline - time, 2) elif time: # This handles the case when the baseline execution exceeds timeout difference = round(timeout - time, 2) elif baseline: # This handles the case when this current execution exceeds timeout difference = round(timeout - baseline, 2) else: # They both failed. difference = 0 if difference > 0: difference_string = colorize( '▲ {}'.format(difference), AnsiColor.LIGHT_GREEN, ) difference_string = '{:>22s}'.format(difference_string) elif difference < 0: difference_string = colorize( '▼ {}'.format(difference), AnsiColor.RED, ) difference_string = '{:>22s}'.format(difference_string) else: difference_string = '{:>10s}'.format('-') print( '{:<25s}{:^20s}{}'.format( name, time_string, difference_string, ), ) else: print('{:<25s}{:>20s}'.format(name, time_string))
def print_stats( live_secrets: List, unaudited_secrets: List, audited_real_secrets: List, baseline_filename: str, fail_on_live: bool, fail_on_unaudited: bool, fail_on_audited_real: bool, ) -> None: """ Given lists of secrets which failed certain conditions and a baseline file name, print a sentence summarizing aggregate stats. """ secrets = audit.get_secrets_list_from_file(baseline_filename) secrets_failing_conditions = live_secrets + unaudited_secrets + audited_real_secrets if len(secrets_failing_conditions) == 0: print( '\n{} potential secrets in {} were reviewed.' ' All checks have passed.\n'.format( colorize(len(secrets), AnsiColor.BOLD), colorize(baseline_filename, AnsiColor.BOLD), ), ) return stats = '\n{} potential secrets in {} were reviewed. Found'.format( colorize(len(secrets), AnsiColor.BOLD), colorize(baseline_filename, AnsiColor.BOLD), ) if fail_on_live: stats += ' {} live secret{}{}'.format( colorize(len(live_secrets), AnsiColor.BOLD), 's' if len(live_secrets) > 1 or len(live_secrets) == 0 else '', '.\n' if not fail_on_unaudited and not fail_on_audited_real else '', ) if fail_on_unaudited: stats += '{}{} {} unaudited secret{}{}'.format( ',' if fail_on_audited_real and fail_on_live else '', ' and' if not fail_on_audited_real and fail_on_live else '', colorize(len(unaudited_secrets), AnsiColor.BOLD), 's' if len(unaudited_secrets) > 1 or len(unaudited_secrets) == 0 else '', '.\n' if not fail_on_audited_real else '', ) if fail_on_audited_real: stats += ' {}{} secret{} that {} audited as real.\n'.format( 'and ' if fail_on_live or fail_on_unaudited else '', colorize(len(audited_real_secrets), AnsiColor.BOLD), 's' if len(audited_real_secrets) > 1 or len(audited_real_secrets) == 0 else '', 'were' if len(audited_real_secrets) > 1 or len(audited_real_secrets) == 0 else 'was', ) print(stats)
def test_print_summary_all_failed_conditions_omit_instructions( self, capsys): unaudited_return_code = live_return_code = audited_real_return_code = 1 print_summary( unaudited_return_code, live_return_code, audited_real_return_code, baseline_filename, True, True, True, True, ) captured = capsys.readouterr() assert captured.out == '\nFailed conditions:\n\n{}\n\n{}\n\n{}\n\n'.format( colorize('\t- Unaudited secrets were found', AnsiColor.BOLD), colorize('\t- Live secrets were found', AnsiColor.BOLD), colorize('\t- Audited true secrets were found', AnsiColor.BOLD), )
def test_print_summary_no_failed_conditions(self, capsys): unaudited_return_code = live_return_code = audited_real_return_code = 0 print_summary( unaudited_return_code, live_return_code, audited_real_return_code, baseline_filename, True, True, True, True, ) captured = capsys.readouterr() assert captured.out == '{}\n\n{}\n\n{}\n\n'.format( colorize('\t- No unaudited secrets were found', AnsiColor.BOLD), colorize('\t- No live secrets were found', AnsiColor.BOLD), colorize('\t- No secrets that were audited as real were found', AnsiColor.BOLD), )
def test_print_summary_only_unaudited_pass(self, capsys): unaudited_return_code = live_return_code = audited_real_return_code = 0 print_summary( unaudited_return_code, live_return_code, audited_real_return_code, baseline_filename, False, True, False, False, ) captured = capsys.readouterr() assert captured.out == '{}\n\n'.format( colorize('\t- No unaudited secrets were found', AnsiColor.BOLD), )
def test_print_summary_only_unaudited_fail(self, capsys): unaudited_return_code = live_return_code = audited_real_return_code = 1 print_summary( unaudited_return_code, live_return_code, audited_real_return_code, baseline_filename, False, True, False, False, ) captured = capsys.readouterr() assert captured.out == '\nFailed conditions:\n\n{}\n{}\n{}\n'.format( colorize('\t- Unaudited secrets were found', AnsiColor.BOLD), '\n\t\tRun detect-secrets audit {}, and audit all potential secrets.' .format(baseline_filename, ), '\nFor additional help, run detect-secrets audit --help.\n', )
def test_print_summary_only_live_fail(self, capsys): unaudited_return_code = live_return_code = audited_real_return_code = 1 print_summary( unaudited_return_code, live_return_code, audited_real_return_code, baseline_filename, True, False, False, False, ) captured = capsys.readouterr() assert captured.out == '\nFailed conditions:\n\n{}\n{}\n{}\n'.format( colorize('\t- Live secrets were found', AnsiColor.BOLD), '\n\t\tRevoke all live secrets and remove them from the codebase.' ' Afterwards, run detect-secrets scan --update {} to re-scan.'. format(baseline_filename, ), '\nFor additional help, run detect-secrets audit --help.\n', )
def test_print_stats_failed_conditions_multiple_secrets_per_condition( self, capsys, live_secrets_fixture, unaudited_secrets_fixture, audited_real_secrets_fixture, ): modified_baseline = deepcopy(self.baseline) modified_baseline['results']['filenameA'][0]['is_secret'] = True modified_baseline['results']['filenameA'][1]['is_secret'] = None modified_baseline['results']['filenameA'].append( { 'hashed_secret': 'd', 'line_number': 150, 'type': 'Private key', 'is_secret': None, }, ) modified_baseline['results']['filenameB'][0]['is_verified'] = True modified_baseline['results']['filenameB'].append( { 'hashed_secret': 'e', 'line_number': 185, 'type': 'Hex High Entropy String', 'is_verified': True, }, ) modified_baseline['results']['filenameB'].append( { 'hashed_secret': 'f', 'line_number': 200, 'type': 'Hex High Entropy String', 'is_secret': True, }, ) live_secrets_fixture.append( { 'failed_condition': ReportSecretType.LIVE.value, 'filename': baseline_filename, 'line': 180, 'type': 'Private key', }, ) unaudited_secrets_fixture.append( { 'failed_condition': ReportSecretType.UNAUDITED.value, 'filename': baseline_filename, 'line': 150, 'type': 'Hex High Entropy String', }, ) audited_real_secrets_fixture.append( { 'failed_condition': ReportSecretType.AUDITED_REAL.value, 'filename': baseline_filename, 'line': 200, 'type': 'Hex High Entropy String', }, ) with self.mock_env(baseline=modified_baseline): print_stats( live_secrets_fixture, unaudited_secrets_fixture, audited_real_secrets_fixture, baseline_filename, True, True, True, ) secrets = audit.get_secrets_list_from_file(baseline_filename) captured = capsys.readouterr() assert captured.out == '\n{} potential secrets in {} were reviewed.'.format( colorize(len(secrets), AnsiColor.BOLD), colorize(baseline_filename, AnsiColor.BOLD), ) + ' Found {} live secrets, {} unaudited secrets'.format( colorize(len(live_secrets_fixture), AnsiColor.BOLD), colorize(len(unaudited_secrets_fixture), AnsiColor.BOLD), ) + ' and {} secrets that were audited as real.\n\n'.format( colorize(len(audited_real_secrets_fixture), AnsiColor.BOLD), )
def _get_secret_with_context( filename, secret, plugins_used, custom_plugin_paths, lines_of_context=5, force_line_printing=False, ): """ Displays the secret, with surrounding lines of code for better context. :type filename: str :param filename: filename where secret resides in :type secret: dict, PotentialSecret.json() format :param secret: the secret listed in baseline :type plugins_used: list :param plugins_used: output of "plugins_used" in baseline. e.g. >>> [ ... { ... 'name': 'Base64HighEntropyString', ... 'base64_limit': 4.5, ... }, ... ] :type custom_plugin_paths: Tuple[str] :param custom_plugin_paths: possibly empty tuple of paths that have custom plugins. :type lines_of_context: int :param lines_of_context: number of lines displayed before and after secret. :type force_line_printing: bool :param force_line_printing: if True, will print the lines of code even if it doesn't find the secret expected :raises: SecretNotFoundOnSpecifiedLineError """ try: file_content = _open_file_with_cache(filename) if not file_content: raise SecretNotFoundOnSpecifiedLineError(secret['line_number']) file_lines = file_content.splitlines() snippet = CodeSnippetHighlighter().get_code_snippet( file_lines, secret['line_number'], lines_of_context=lines_of_context, ) raw_secret_value = get_raw_secret_value( secret=secret, plugins_used=plugins_used, custom_plugin_paths=custom_plugin_paths, file_handle=io.StringIO(file_content), filename=filename, ) try: snippet.highlight_line(raw_secret_value) except ValueError: raise SecretNotFoundOnSpecifiedLineError(secret['line_number']) except SecretNotFoundOnSpecifiedLineError: if not force_line_printing: raise snippet.target_line = colorize( snippet.target_line, AnsiColor.BOLD, ) return snippet.add_line_numbers()
def _print_context( # pragma: no cover filename, secret, count, total, plugins_used, custom_plugin_paths, additional_header_lines=None, force_line_printing=False, ): """ :type filename: str :param filename: the file currently scanned. :type secret: dict, in PotentialSecret.json() format :param secret: the secret, represented in the baseline file. :type count: int :param count: current count of secrets scanned so far :type total: int :param total: total number of secrets in baseline :type plugins_used: list :param plugins_used: output of "plugins_used" in baseline. e.g. >>> [ ... { ... 'name': 'Base64HighEntropyString', ... 'base64_limit': 4.5, ... }, ... ] :type custom_plugin_paths: Tuple[str] :param custom_plugin_paths: possibly empty tuple of paths that have custom plugins. :type additional_header_lines: str :param additional_header_lines: any additional lines to add to the header of the interactive audit display. :type force_line_printing: bool :param force_line_printing: if True, will print the lines of code even if it doesn't find the secret expected :raises: SecretNotFoundOnSpecifiedLineError """ print( '{} {} {} {}\n{} {}\n{} {}'.format( colorize('Secret: ', AnsiColor.BOLD), colorize(str(count), AnsiColor.PURPLE), colorize('of', AnsiColor.BOLD), colorize(str(total), AnsiColor.PURPLE), colorize('Filename: ', AnsiColor.BOLD), colorize(filename, AnsiColor.PURPLE), colorize('Secret Type:', AnsiColor.BOLD), colorize(secret['type'], AnsiColor.PURPLE), ), ) if additional_header_lines: print(additional_header_lines) print('-' * 10) error_obj = None try: secret_with_context = _get_secret_with_context( filename=filename, secret=secret, plugins_used=plugins_used, custom_plugin_paths=custom_plugin_paths, force_line_printing=force_line_printing, ) print(secret_with_context) except SecretNotFoundOnSpecifiedLineError as e: error_obj = e print(e) print('-' * 10) if error_obj: raise error_obj
def compare_baselines(old_baseline_filename, new_baseline_filename): """ This function enables developers to more easily configure plugin settings, by comparing two generated baselines and highlighting their differences. For effective use, a few assumptions are made: 1. Baselines are sorted by (filename, line_number, hash). This allows for a deterministic order, when doing a side-by-side comparison. 2. Baselines are generated for the same codebase snapshot. This means that we won't have cases where secrets are moved around; only added or removed. Note: We don't want to do a version check, because we want to be able to use this functionality across versions (to see how the new version fares compared to the old one). """ if old_baseline_filename == new_baseline_filename: raise RedundantComparisonError old_baseline = _get_baseline_from_file(old_baseline_filename) new_baseline = _get_baseline_from_file(new_baseline_filename) _remove_nonexistent_files_from_baseline(old_baseline) _remove_nonexistent_files_from_baseline(new_baseline) # We aggregate the secrets first, so that we can display a total count. secrets_to_compare = _get_secrets_to_compare(old_baseline, new_baseline) total_reviews = len(secrets_to_compare) current_index = 0 secret_iterator = BidirectionalIterator(secrets_to_compare) for filename, secret, is_removed in secret_iterator: _clear_screen() current_index += 1 header = '{} {}' if is_removed: plugins_used = old_baseline['plugins_used'] custom_plugin_paths = old_baseline['custom_plugin_paths'] header = header.format( colorize('Status:', AnsiColor.BOLD), '>> {} <<'.format( colorize('REMOVED', AnsiColor.RED), ), ) else: plugins_used = new_baseline['plugins_used'] custom_plugin_paths = new_baseline['custom_plugin_paths'] header = header.format( colorize('Status:', AnsiColor.BOLD), '>> {} <<'.format( colorize('ADDED', AnsiColor.LIGHT_GREEN), ), ) try: _print_context( filename=filename, secret=secret, count=current_index, total=total_reviews, plugins_used=plugins_used, custom_plugin_paths=custom_plugin_paths, additional_header_lines=header, force_line_printing=is_removed, ) decision = _get_user_decision( can_step_back=secret_iterator.can_step_back(), prompt_secret_decision=False, ) except SecretNotFoundOnSpecifiedLineError: decision = _get_user_decision(prompt_secret_decision=False) if decision == 'q': print('Quitting...') break if decision == 'b': # pragma: no cover current_index -= 2 secret_iterator.step_back_on_next_iteration()
def print_summary( unaudited_return_code: int, live_return_code: int, audited_real_return_code: int, baseline_filename: str, fail_on_live: bool, fail_on_unaudited: bool, fail_on_audited_real: bool, omit_instructions=False, ) -> None: """ Prints information about failed checks in a report, as well as how to remediate them. Instructions can optionally be omitted. """ if unaudited_return_code == live_return_code == audited_real_return_code == 0: if fail_on_unaudited: print( '{}\n'.format( colorize('\t- No unaudited secrets were found', AnsiColor.BOLD), ), ) if fail_on_live: print( '{}\n'.format( colorize('\t- No live secrets were found', AnsiColor.BOLD), ), ) if fail_on_audited_real: print( '{}\n'.format( colorize( '\t- No secrets that were audited as real were found', AnsiColor.BOLD), ), ) return print('\nFailed conditions:\n') if fail_on_unaudited and unaudited_return_code != 0: print( '{}\n'.format( colorize('\t- Unaudited secrets were found', AnsiColor.BOLD), ), ) if omit_instructions is False: print( '\t\tRun detect-secrets audit {}, and audit all potential secrets.\n' .format(baseline_filename, ), ) if fail_on_live and live_return_code != 0: print( '{}\n'.format( colorize('\t- Live secrets were found', AnsiColor.BOLD), ), ) if omit_instructions is False: print( '\t\tRevoke all live secrets and remove them from the codebase.' ' Afterwards, run detect-secrets scan --update {} to re-scan.\n' .format(baseline_filename, ), ) if fail_on_audited_real and audited_real_return_code != 0: print( '{}\n'.format( colorize('\t- Audited true secrets were found', AnsiColor.BOLD), ), ) if omit_instructions is False: print( '\t\tIf any active secrets meet this condition, revoke them.' ' Then, remove secrets that were audited as real from the codebase' ' and run detect-secrets scan --update {} to re-scan.\n'. format(baseline_filename), ) if omit_instructions is False: print('For additional help, run detect-secrets audit --help.\n')