diff --git a/changelog.d/20241203_154800_mathias.millet_spi_515_add_all_secrets_option_to_ggshield_secret_scans_bis.md b/changelog.d/20241203_154800_mathias.millet_spi_515_add_all_secrets_option_to_ggshield_secret_scans_bis.md new file mode 100644 index 0000000000..367ec70854 --- /dev/null +++ b/changelog.d/20241203_154800_mathias.millet_spi_515_add_all_secrets_option_to_ggshield_secret_scans_bis.md @@ -0,0 +1,41 @@ + + + + +### Added + +- The `--all-secrets` option to secret scans, allowing to display all found secrets, and their possible ignore reason. + + + + + diff --git a/ggshield/cmd/secret/scan/secret_scan_common_options.py b/ggshield/cmd/secret/scan/secret_scan_common_options.py index a7fe55c125..0fc74d8329 100644 --- a/ggshield/cmd/secret/scan/secret_scan_common_options.py +++ b/ggshield/cmd/secret/scan/secret_scan_common_options.py @@ -140,6 +140,15 @@ def _banlist_detectors_callback( ) +_all_secrets = click.option( + "--all-secrets", + is_flag=True, + help=("Do not ignore any secret. Possible ignore-reason is shown as well."), + callback=create_config_callback("secret", "all_secrets"), + default=None, +) + + def add_secret_scan_common_options() -> Callable[[AnyFunction], AnyFunction]: def decorator(cmd: AnyFunction) -> AnyFunction: add_common_options()(cmd) @@ -153,6 +162,7 @@ def decorator(cmd: AnyFunction) -> AnyFunction: _banlist_detectors_option(cmd) _with_incident_details_option(cmd) instance_option(cmd) + _all_secrets(cmd) return cmd return decorator diff --git a/ggshield/core/config/user_config.py b/ggshield/core/config/user_config.py index fa2cefc7a0..d701d1f219 100644 --- a/ggshield/core/config/user_config.py +++ b/ggshield/core/config/user_config.py @@ -1,3 +1,4 @@ +import json import logging import re from dataclasses import field @@ -47,6 +48,7 @@ class SecretConfig(FilteredConfig): ignore_known_secrets: bool = False with_incident_details: bool = False # if configuration key is left unset the dashboard's remediation message is used. + all_secrets: bool = False prereceive_remediation_message: str = "" def add_ignored_match(self, secret: IgnoredMatch) -> None: @@ -61,6 +63,22 @@ def add_ignored_match(self, secret: IgnoredMatch) -> None: return self.ignored_matches.append(secret) + def dump_for_monitoring(self) -> str: + return json.dumps( + { + "show_secrets": self.show_secrets, + "ignored_detectors_count": len(self.ignored_detectors), + "ignored_matches_count": len(self.ignored_matches), + "ignored_paths_count": len(self.ignored_paths), + "ignore_known_secrets": self.ignore_known_secrets, + "with_incident_details": self.with_incident_details, + "has_prereceive_remediation_message": bool( + self.prereceive_remediation_message + ), + "all_secrets": self.all_secrets, + } + ) + def validate_policy_id(policy_id: str) -> bool: return bool(POLICY_ID_PATTERN.fullmatch(policy_id)) diff --git a/ggshield/verticals/secret/output/secret_text_output_handler.py b/ggshield/verticals/secret/output/secret_text_output_handler.py index 73d2b8dfc6..b628df4024 100644 --- a/ggshield/verticals/secret/output/secret_text_output_handler.py +++ b/ggshield/verticals/secret/output/secret_text_output_handler.py @@ -5,20 +5,19 @@ from pygitguardian.client import VERSIONS from pygitguardian.models import PolicyBreak +from ggshield.core.constants import IncidentStatus from ggshield.core.filter import group_policy_breaks_by_ignore_sha from ggshield.core.lines import Line, get_offset, get_padding from ggshield.core.text_utils import ( STYLE, clip_long_line, - file_info, format_text, pluralize, translate_validity, ) -from ggshield.verticals.secret.secret_scanner import IgnoreReason from ..extended_match import ExtendedMatch -from ..secret_scan_collection import Result, SecretScanCollection +from ..secret_scan_collection import IgnoreReason, Result, SecretScanCollection from .secret_output_handler import SecretOutputHandler @@ -29,6 +28,31 @@ NB_CONTEXT_LINES = 3 +def secret_file_info( + filename: str, + incident_count: int, + incident_status: IncidentStatus = IncidentStatus.DETECTED, + hidden_secrets_count: int = 0, +) -> str: + """Return the formatted file info (number of secrets, filename, optional number of hidden secrets).""" + result = "\n{} {}: {} {} {}\n".format( + format_text(">", STYLE["detector_line_start"]), + format_text(filename, STYLE["filename"]), + incident_count, + pluralize("secret", incident_count, "secrets"), + incident_status.value, + ) + if hidden_secrets_count > 0: + result += "{} {} {} {} ignored - use the `--all-secrets` option to display {}\n".format( + format_text(">", STYLE["detector_line_start"]), + " " * len(filename), + hidden_secrets_count, + pluralize("secret", hidden_secrets_count, "secrets"), + pluralize("it", hidden_secrets_count, "them"), + ) + return result + + class SecretTextOutputHandler(SecretOutputHandler): def _process_scan_impl(self, scan: SecretScanCollection) -> str: """Output Secret Scan Collection in text format""" @@ -100,6 +124,9 @@ def process_result(self, result: Result) -> str: result.censor() number_of_displayed_secrets = 0 + number_of_hidden_secrets = sum( + result.ignored_policy_breaks_count_by_reason.values() + ) for ignore_sha, policy_breaks in sha_dict.items(): number_of_displayed_secrets += 1 @@ -118,8 +145,12 @@ def process_result(self, result: Result) -> str: ) file_info_line = "" - if number_of_displayed_secrets > 0: - file_info_line = file_info(result.filename, number_of_displayed_secrets) + if number_of_displayed_secrets > 0 or number_of_hidden_secrets > 0: + file_info_line = secret_file_info( + result.filename, + incident_count=number_of_displayed_secrets, + hidden_secrets_count=number_of_hidden_secrets, + ) return file_info_line + result_buf.getvalue() @@ -259,28 +290,30 @@ def policy_break_header( """ Build a header for the policy break. """ + policy_break = policy_breaks[0] indent = " " validity_msg = ( - f"\n{indent}Validity: {format_text(translate_validity(policy_breaks[0].validity), STYLE['incident_validity'])}" - if policy_breaks[0].validity + f"\n{indent}Validity: {format_text(translate_validity(policy_break.validity), STYLE['incident_validity'])}" + if policy_break.validity else "" ) start_line = format_text(">>", STYLE["detector_line_start"]) - policy_break_type = format_text( - policy_breaks[0].break_type, STYLE["policy_break_type"] - ) + policy_break_type = format_text(policy_break.break_type, STYLE["policy_break_type"]) number_occurrences = format_text(str(len(policy_breaks)), STYLE["occurrence_count"]) ignore_sha = format_text(ignore_sha, STYLE["ignore_sha"]) - return f""" + message = f""" {start_line} Secret detected: {policy_break_type}{validity_msg} {indent}Occurrences: {number_occurrences} {indent}Known by GitGuardian dashboard: {"YES" if known_secret else "NO"} -{indent}Incident URL: {policy_breaks[0].incident_url if known_secret and policy_breaks[0].incident_url else "N/A"} +{indent}Incident URL: {policy_breaks[0].incident_url if known_secret and policy_break.incident_url else "N/A"} {indent}Secret SHA: {ignore_sha} - """ + if policy_break.is_excluded: + message += f"{indent}Ignored: {policy_break.exclude_reason}\n" + + return message + "\n" def no_leak_message() -> str: diff --git a/ggshield/verticals/secret/secret_scan_collection.py b/ggshield/verticals/secret/secret_scan_collection.py index 7edbbdfa92..2d8825f828 100644 --- a/ggshield/verticals/secret/secret_scan_collection.py +++ b/ggshield/verticals/secret/secret_scan_collection.py @@ -2,8 +2,7 @@ from enum import Enum from pathlib import Path from typing import ( - Any, - Callable, + Counter, Dict, Iterable, List, @@ -15,22 +14,52 @@ ) from pygitguardian import GGClient -from pygitguardian.models import Detail, Match, PolicyBreak, ScanResult, SecretIncident +from pygitguardian.models import ( + Detail, + DiffKind, + Match, + PolicyBreak, + ScanResult, + SecretIncident, +) +from ggshield.core.config.user_config import SecretConfig from ggshield.core.errors import UnexpectedError, handle_api_error +from ggshield.core.filter import is_in_ignored_matches from ggshield.core.lines import Line, get_lines_from_content -from ggshield.core.scan.scannable import Scannable +from ggshield.core.scan import Scannable from ggshield.utils.git_shell import Filemode from ggshield.verticals.secret.extended_match import ExtendedMatch -class IgnoreReason(Enum): +class IgnoreReason(str, Enum): IGNORED_MATCH = "ignored_match" IGNORED_DETECTOR = "ignored_detector" KNOWN_SECRET = "known_secret" NOT_INTRODUCED = "not_introduced" + BACKEND_EXCLUDED = "backend_excluded" + + +def compute_ignore_reason( + policy_break: PolicyBreak, secret_config: SecretConfig +) -> Optional[str]: + """Computes the possible ignore reason associated with a PolicyBreak""" + ignore_reason = None + if policy_break.diff_kind in {DiffKind.DELETION, DiffKind.CONTEXT}: + ignore_reason = IgnoreReason.NOT_INTRODUCED + elif policy_break.is_excluded: + ignore_reason = f"Excluded from backend ({policy_break.exclude_reason})" + elif is_in_ignored_matches(policy_break, secret_config.ignored_matches or []): + ignore_reason = IgnoreReason.IGNORED_MATCH + elif policy_break.break_type in secret_config.ignored_detectors: + ignore_reason = IgnoreReason.IGNORED_DETECTOR + elif secret_config.ignore_known_secrets and policy_break.known_secret: + ignore_reason = IgnoreReason.KNOWN_SECRET + return ignore_reason + +@dataclass class Result: """ Return model for a scan which zips the information @@ -42,28 +71,7 @@ class Result: path: Path url: str policy_breaks: List[PolicyBreak] - ignored_policy_breaks_count_by_reason: Dict[IgnoreReason, int] - - def __init__(self, file: Scannable, scan: ScanResult): - self.filename = file.filename - self.filemode = file.filemode - self.path = file.path - self.url = file.url - self.policy_breaks = scan.policy_breaks - lines = get_lines_from_content(file.content, self.filemode) - self.enrich_matches(lines) - self.ignored_policy_breaks_count_by_reason = {} - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, Result): - return False - return ( - self.filename == other.filename - and self.filemode == other.filemode - and self.path == other.path - and self.url == other.url - and self.policy_breaks == other.policy_breaks - ) + ignored_policy_breaks_count_by_reason: Counter[str] @property def is_on_patch(self) -> bool: @@ -90,21 +98,41 @@ def censor(self) -> None: def has_policy_breaks(self) -> bool: return len(self.policy_breaks) > 0 - def apply_ignore_function( - self, reason: IgnoreReason, ignore_function: Callable[[PolicyBreak], bool] - ): - assert ( - reason not in self.ignored_policy_breaks_count_by_reason - ), f"Ignore was already computed for {IgnoreReason}" + @classmethod + def from_scan_result( + cls, file: Scannable, scan_result: ScanResult, secret_config: SecretConfig + ) -> "Result": + """Creates a Result from a Scannable and a ScanResult. + - Removes ignored policy breaks + - replace matches by ExtendedMatches + """ + to_keep = [] - ignored_count = 0 - for policy_break in self.policy_breaks: - if ignore_function(policy_break): - ignored_count += 1 + ignored_policy_breaks_count_by_reason = Counter() + for policy_break in scan_result.policy_breaks: + ignore_reason = compute_ignore_reason(policy_break, secret_config) + if ignore_reason is not None: + if secret_config.all_secrets: + policy_break.exclude_reason = ignore_reason + policy_break.is_excluded = True + to_keep.append(policy_break) + else: + ignored_policy_breaks_count_by_reason[ignore_reason] += 1 else: to_keep.append(policy_break) - self.policy_breaks = to_keep - self.ignored_policy_breaks_count_by_reason[reason] = ignored_count + + result = Result( + filename=file.filename, + filemode=file.filemode, + path=file.path, + url=file.url, + policy_breaks=to_keep, + ignored_policy_breaks_count_by_reason=ignored_policy_breaks_count_by_reason, + ) + + lines = get_lines_from_content(file.content, file.filemode) + result.enrich_matches(lines) + return result class Error(NamedTuple): diff --git a/ggshield/verticals/secret/secret_scanner.py b/ggshield/verticals/secret/secret_scanner.py index c47881e67c..dff5eac3ec 100644 --- a/ggshield/verticals/secret/secret_scanner.py +++ b/ggshield/verticals/secret/secret_scanner.py @@ -7,13 +7,7 @@ from typing import Dict, Iterable, List, Optional, Union from pygitguardian import GGClient -from pygitguardian.models import ( - APITokensResponse, - Detail, - DiffKind, - MultiScanResult, - TokenScope, -) +from pygitguardian.models import APITokensResponse, Detail, MultiScanResult, TokenScope from ggshield.core import ui from ggshield.core.cache import Cache @@ -21,12 +15,11 @@ from ggshield.core.config.user_config import SecretConfig from ggshield.core.constants import MAX_WORKERS from ggshield.core.errors import MissingScopesError, UnexpectedError, handle_api_error -from ggshield.core.filter import is_in_ignored_matches from ggshield.core.scan import DecodeError, ScanContext, Scannable from ggshield.core.text_utils import pluralize from ggshield.core.ui.scanner_ui import ScannerUI -from .secret_scan_collection import Error, IgnoreReason, Result, Results +from .secret_scan_collection import Error, Result, Results # GitGuardian API does not accept paths longer than this @@ -62,9 +55,9 @@ def __init__( self.client = client self.cache = cache self.secret_config = secret_config - self.ignored_matches = secret_config.ignored_matches or [] - self.ignored_detectors = secret_config.ignored_detectors self.headers = scan_context.get_http_headers() + self.headers.update({"scan_options": secret_config.dump_for_monitoring()}) + self.command_id = scan_context.command_id if secret_config.with_incident_details: @@ -120,7 +113,7 @@ def _scan_chunk( self.client.multi_content_scan, documents, self.headers, - ignore_known_secrets=True, + all_secrets=True, ) def _start_scans( @@ -220,33 +213,7 @@ def _collect_results( assert isinstance(scan, MultiScanResult) for file, scan_result in zip(chunk, scan.scan_results): - result = Result( - file=file, - scan=scan_result, - ) - if not scan_result.has_policy_breaks: - continue - result.apply_ignore_function( - IgnoreReason.NOT_INTRODUCED, - lambda policy_break: policy_break.diff_kind - in {DiffKind.DELETION, DiffKind.CONTEXT}, - ) - result.apply_ignore_function( - IgnoreReason.IGNORED_MATCH, - lambda policy_break: is_in_ignored_matches( - policy_break, self.ignored_matches - ), - ) - result.apply_ignore_function( - IgnoreReason.IGNORED_DETECTOR, - lambda policy_break: policy_break.break_type - in self.ignored_detectors, - ) - if self.secret_config.ignore_known_secrets: - result.apply_ignore_function( - IgnoreReason.KNOWN_SECRET, - lambda policy_break: policy_break.known_secret, - ) + result = Result.from_scan_result(file, scan_result, self.secret_config) for policy_break in result.policy_breaks: self.cache.add_found_policy_break(policy_break, file.filename) results.append(result) diff --git a/pdm.lock b/pdm.lock index fbaad69300..073e37506c 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,7 @@ groups = ["default", "dev", "standalone", "tests"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:bfd002750bb9ea775191e6583046639d7fe9587ed2744391a3c296b2826c2485" +content_hash = "sha256:a019a43ca733311c71c30f7598a795f13d93be952e0273134b544b1c1d7d36fd" [[metadata.targets]] requires_python = ">=3.8.1" @@ -519,6 +519,35 @@ files = [ {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, ] +[[package]] +name = "factory-boy" +version = "3.3.1" +requires_python = ">=3.8" +summary = "A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby." +groups = ["tests"] +dependencies = [ + "Faker>=0.7.0", +] +files = [ + {file = "factory_boy-3.3.1-py2.py3-none-any.whl", hash = "sha256:7b1113c49736e1e9995bc2a18f4dbf2c52cf0f841103517010b1d825712ce3ca"}, + {file = "factory_boy-3.3.1.tar.gz", hash = "sha256:8317aa5289cdfc45f9cae570feb07a6177316c82e34d14df3c2e1f22f26abef0"}, +] + +[[package]] +name = "faker" +version = "33.1.0" +requires_python = ">=3.8" +summary = "Faker is a Python package that generates fake data for you." +groups = ["tests"] +dependencies = [ + "python-dateutil>=2.4", + "typing-extensions", +] +files = [ + {file = "Faker-33.1.0-py3-none-any.whl", hash = "sha256:d30c5f0e2796b8970de68978365247657486eb0311c5abe88d0b895b68dff05d"}, + {file = "faker-33.1.0.tar.gz", hash = "sha256:1c925fc0e86a51fc46648b504078c88d0cd48da1da2595c4e712841cab43a1e4"}, +] + [[package]] name = "fastdiff" version = "0.3.0" @@ -1679,6 +1708,20 @@ files = [ {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, ] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +summary = "Extensions to the standard Python datetime module" +groups = ["tests"] +dependencies = [ + "six>=1.5", +] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + [[package]] name = "python-dotenv" version = "0.21.1" @@ -2138,7 +2181,7 @@ files = [ [[package]] name = "wasmer-compiler-cranelift" version = "1.1.0" -summary = "The Cranelift compiler for the `wasmer` package (to compile WebAssembly module)" +summary = "Python extension to run WebAssembly binaries" groups = ["tests"] files = [ {file = "wasmer_compiler_cranelift-1.1.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:9869910179f39696a020edc5689f7759257ac1cce569a7a0fcf340c59788baad"}, diff --git a/pyproject.toml b/pyproject.toml index 271b3745c1..59439c9436 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,22 +76,6 @@ dev = [ "check-wheel-contents", ] -tests = [ - "pytest-mock", - "pytest-socket", - "pytest-xdist", - "pytest-voluptuous", - "seed-isort-config", - "snapshottest", - "import-linter", - "jsonschema", - "vcrpy>=5.1.0", - # pytest-voluptuous dependency. Pinned because 0.15.0 does not support - # Python 3.8 anymore - "voluptuous<0.15.0", - # Pin because 5.6.0 currently (2024-07-15) does not work on Windows - "pyfakefs>=5.2.0,<5.6.0", -] standalone = [ "pyinstaller==6.7.0", @@ -150,3 +134,34 @@ version = "literal: ggshield/__init__.py: __version__" format = "md" md_header_level = "2" insert_marker = "# Changelog" + +[dependency-groups] +tests = [ + "pytest-mock", + "pytest-socket", + "pytest-xdist", + "pytest-voluptuous", + "seed-isort-config", + "snapshottest", + "import-linter", + "jsonschema", + "vcrpy>=5.1.0", + "voluptuous<0.15.0", + "pyfakefs>=5.2.0,<5.6.0", + "factory-boy>=3.3.1", +] +dev = [ + "scriv[toml]", + "black==24.3.0", + "coverage", + "flake8", + "flake8-isort", + "flake8-quotes", + "pre-commit", + "pyright==1.1.367", + "build>=1.2.2.post1", + "check-wheel-contents", +] +standalone = [ + "pyinstaller==6.7.0", +] diff --git a/tests/factories.py b/tests/factories.py new file mode 100644 index 0000000000..e1f4d100aa --- /dev/null +++ b/tests/factories.py @@ -0,0 +1,89 @@ +import random + +import factory +import factory.fuzzy +from pygitguardian.models import Match, PolicyBreak, ScanResult + +from ggshield.core.scan.scannable import StringScannable +from ggshield.utils.git_shell import Filemode +from tests.factory_constants import DETECTOR_NAMES, MATCH_NAMES + + +def get_line_index(content, index): + """Return the index of the line containing the character at the given index""" + current_line_index = 0 + lines = content.splitlines(keepends=True) + while True: + line = lines.pop(0) + if index <= len(line): + return current_line_index + index -= len(line) + current_line_index += 1 + + +class ScannableFactory(factory.Factory): + class Meta: + model = StringScannable + + url = factory.Faker("hostname") + content = factory.Faker("text") + # Only returning FILE for new, since diff would need a custom content + filemode = Filemode.FILE + + +class MatchFactory(factory.Factory): + class Meta: + model = Match + + content = factory.Faker("text") + match_len = factory.fuzzy.FuzzyInteger(5, 15) + index_start = factory.lazy_attribute( + lambda obj: random.randint(0, len(obj.content) - obj.match_len) + ) + index_end = factory.lazy_attribute(lambda obj: obj.index_start + obj.match_len) + match = factory.lazy_attribute( + lambda obj: obj.content[obj.index_start : obj.index_end] + ) + line_start = factory.lazy_attribute( + lambda obj: get_line_index(obj.content, obj.index_start) + ) + line_end = factory.lazy_attribute( + lambda obj: get_line_index(obj.content, obj.index_end) + ) + match_type = factory.lazy_attribute(lambda obj: random.choice(MATCH_NAMES)) + + +class PolicyBreakFactory(factory.Factory): + class Meta: + model = PolicyBreak + + break_type = factory.lazy_attribute(lambda obj: random.choice(DETECTOR_NAMES)) + policy = "Secrets detection" + validity = "valid" + known_secret = False + incident_url = None + is_excluded = False + exclude_reason = None + diff_kind = None + content = factory.Faker("text") + nb_matches = factory.fuzzy.FuzzyInteger(1, 2) + + @factory.lazy_attribute + def matches(self): + # Note: matches may overlap, but at least we ensure they + # have different names + match_names = random.sample(MATCH_NAMES, self.nb_matches) + return [ + MatchFactory(match_type=match_name, content=self.content) + for match_name in match_names + ] + + +class ScanResultFactory(factory.Factory): + class Meta: + model = ScanResult + + policy_break_count = factory.lazy_attribute(lambda obj: len(obj.policy_breaks)) + policy_breaks = [] + policies = ["Secrets detection"] + is_diff = False diff --git a/tests/factory_constants.py b/tests/factory_constants.py new file mode 100644 index 0000000000..f5e4fc1df1 --- /dev/null +++ b/tests/factory_constants.py @@ -0,0 +1,92 @@ +DETECTOR_NAMES = [ + "Basic Auth String", + "Generic Password", + "JSON Web Token", + "Generic Terraform Variable Secret", + "Generic Database Assignment", + "Company Email Password", + "Generic Password", + "Base64 Generic High Entropy Secret", + "Generic Database Assignment", + "Generic CLI Option Secret", + "Username Password", + "Generic High Entropy Secret", + "Base64 Basic Authentication", + "Bearer Token", + "Authentication Tuple", + "Typeform API Token", + "New Relic API Key", + "Pingdom token v3", + "Datadog Keys", + "Databricks Authentication Token With Hostname", + "GitGuardian Public Monitoring API Key", + "GitHub Server-to-server Token", + "Infracost API Key", + "Facebook App Keys", + "Firebase Cloud Messaging API Key", + "Intercom Token", + "Sourcegraph Access Token v1", + "Stripe Webhook Secret", + "GitLab Token", + "New Relic API Service Key", + "Eventbrite OAuth2 Token", + "Base64 AWS SES Keys", + "Doppler API Key", + "Heartland API key", + "Tailscale Pre-Authentication Key", + "Kraken Keys", + "Coveralls Repository Token", + "Docker Credentials", + "Algolia Monitoring Keys", + "Grafana Token", + "PackageCloud API Token", + "Square Access Token", + "DigitalOcean Token", + "Sourcegraph Access Token v3", + "Akamai API Credentials", + "Linode Personal Access Token", + "Scalr API Access Token", + "FullContact Key", + "Nylas API Key", + "Plaid Access Token", +] +MATCH_NAMES = [ + "apikey", + "client_id", + "client_secret", + "host", + "password", + "username", + "token", + "port", + "scheme", + "connection_uri", + "subdomain", + "private_key", + "domain", + "secret_key", + "access_token", + "project_id", + "cloud_name", + "database", + "client_token", + "secret_token", + "tenant_id", + "private_key_id", + "integration_key", + "azure_endpoint", + "app_id", + "cluster", + "pub_key", + "sub_key", + "environment", + "refresh_token", + "organization", + "session_token", + "connection_string", + "account", + "user", + "client_certificate", + "client_key", + "config_value", +] diff --git a/tests/functional/secret/test_scan_docset.py b/tests/functional/secret/test_scan_docset.py index d43aa18fef..550d99ee3a 100644 --- a/tests/functional/secret/test_scan_docset.py +++ b/tests/functional/secret/test_scan_docset.py @@ -20,7 +20,7 @@ def test_scan_docset_positive(tmp_path: Path) -> None: result = run_ggshield_scan("docset", str(test_file), cwd=tmp_path, expected_code=1) assert "apikey =" in result.stdout assert ( - "https://github.com/foo/bar/issues/1#issuecomment-42: 1 incident detected" + "https://github.com/foo/bar/issues/1#issuecomment-42: 1 secret detected" in result.stdout ) diff --git a/tests/test_factories.py b/tests/test_factories.py new file mode 100644 index 0000000000..811984e379 --- /dev/null +++ b/tests/test_factories.py @@ -0,0 +1,23 @@ +import pytest + +from tests.factories import get_line_index + + +TEST_CONTENT = """aaa +bb +cccc""" + + +@pytest.mark.parametrize( + ("index", "expected_line_index"), + ( + (1, 0), + (4, 0), + (5, 1), + (7, 1), + (8, 2), + (11, 2), + ), +) +def test_get_line_index(index, expected_line_index): + assert get_line_index(TEST_CONTENT, index) == expected_line_index diff --git a/tests/unit/cassettes/multiline_secret.yaml b/tests/unit/cassettes/multiline_secret.yaml index 8465b4b3bc..7858fca7e7 100644 --- a/tests/unit/cassettes/multiline_secret.yaml +++ b/tests/unit/cassettes/multiline_secret.yaml @@ -54,8 +54,6 @@ interactions: - istio-envoy strict-transport-security: - max-age=31536000; includeSubDomains - transfer-encoding: - - chunked vary: - Accept-Encoding,Cookie x-app-version: @@ -161,4 +159,87 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "/tmp/pytest-of-mmillet/pytest-1/test_json_output_for_patch__MU1/file", + "document": "-----BEGIN RSA PRIVATE KEY-----\nMIIBOgIBAAJBAIIRkYjxjE3KIZiEc8k4sWWGNsPYRNE0u0bl5oFVApPLm+uXQ/4l\nbKO9LFtMiVPy700oMWLScwAN5OAiqVLMvHUCAwEAAQJANLr8nmEWuV6t2hAwhK5I\nNNmBkEo4M/xFxEtl9J7LKbE2gtNrlCQiJlPP1EMhwAjDOzQcJ3lgFB28dkqH5rMW\nTQIhANrCE7O+wlCKe0WJqQ3lYlHG91XWyGVgfExJwBDsAD9LAiEAmDY5OSsH0n2A\n22tthkAvcN1s66lG+0DztOVJ4QLI2z8CIBPeDGwGpx8pdIicN/5LFuLWbyAcoZaT\nbLaA/DCNPniBAiA0l//bzg+M3srIhm04xzLdR9Vb9IjPRlkvN074zdKDVwIhAKJb\nRF3C+CMFb0wXme/ovcDeM1+3W/UmSHYUW4b3WYq4\n-----END + RSA PRIVATE KEY-----"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '602' + Content-Type: + - application/json + GGShield-Command-Id: + - 0705b453-f434-4c4c-8c80-3ecd4d7ad679 + GGShield-Command-Path: + - external + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":1,"policies":["Secrets detection"],"policy_breaks":[{"type":"RSA + Private Key","policy":"Secrets detection","matches":[{"type":"apikey","match":"-----BEGIN + RSA PRIVATE KEY-----\nMIIBOgIBAAJBAIIRkYjxjE3KIZiEc8k4sWWGNsPYRNE0u0bl5oFVApPLm+uXQ/4l\nbKO9LFtMiVPy700oMWLScwAN5OAiqVLMvHUCAwEAAQJANLr8nmEWuV6t2hAwhK5I\nNNmBkEo4M/xFxEtl9J7LKbE2gtNrlCQiJlPP1EMhwAjDOzQcJ3lgFB28dkqH5rMW\nTQIhANrCE7O+wlCKe0WJqQ3lYlHG91XWyGVgfExJwBDsAD9LAiEAmDY5OSsH0n2A\n22tthkAvcN1s66lG+0DztOVJ4QLI2z8CIBPeDGwGpx8pdIicN/5LFuLWbyAcoZaT\nbLaA/DCNPniBAiA0l//bzg+M3srIhm04xzLdR9Vb9IjPRlkvN074zdKDVwIhAKJb\nRF3C+CMFb0wXme/ovcDeM1+3W/UmSHYUW4b3WYq4\n-----END + RSA PRIVATE KEY-----","index_start":0,"index_end":491,"line_start":1,"line_end":9}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":null}],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '872' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:13:35 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '65' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/multiple_secrets.yaml b/tests/unit/cassettes/multiple_secrets.yaml index 1a357c3566..d6393d89dc 100644 --- a/tests/unit/cassettes/multiple_secrets.yaml +++ b/tests/unit/cassettes/multiple_secrets.yaml @@ -132,4 +132,86 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "commit://patch/test.txt", "document": "@@ -0,0 +1,2 @@\n+FacebookAppKeys + :\n+String docker run --name geonetwork -d -p 8080:8080 -e MYSQL_HOST=google.com -e + MYSQL_PORT=5434 -e MYSQL_USERNAME=root -e MYSQL_PASSWORD=m42ploz2wd + geonetwork\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '291' + Content-Type: + - application/json + GGShield-Command-Id: + - bd5647f1-8ab2-4870-8e42-7423d4a20ed6 + GGShield-Command-Path: + - external + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":1,"policies":["Secrets detection"],"policy_breaks":[{"type":"MySQL + Credentials","policy":"Secrets detection","matches":[{"type":"host","match":"google.com","index_start":114,"index_end":123,"line_start":3,"line_end":3},{"type":"port","match":"5434","index_start":151,"index_end":154,"line_start":3,"line_end":3},{"type":"username","match":"root","index_start":174,"index_end":177,"line_start":3,"line_end":3},{"type":"password","match":"m42ploz2wd","index_start":209,"index_end":218,"line_start":3,"line_end":3}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"failed_to_check","diff_kind":"addition"}],"is_diff":true}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '687' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:13:27 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '3100' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/no_secret.yaml b/tests/unit/cassettes/no_secret.yaml index 6f628b1f60..018646dc2a 100644 --- a/tests/unit/cassettes/no_secret.yaml +++ b/tests/unit/cassettes/no_secret.yaml @@ -129,4 +129,82 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "commit://patch/test", "document": "@@ -0,0 +1 @@\n+this + is a patch without secret\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '101' + Content-Type: + - application/json + GGShield-Command-Id: + - 2e14936f-1dda-4d60-86bf-207e7b3aac85 + GGShield-Command-Path: + - external + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: '[{"policy_break_count":0,"policies":["Secrets detection"],"policy_breaks":[],"is_diff":true}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '93' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:13:31 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '49' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/one_line_and_multiline_patch.yaml b/tests/unit/cassettes/one_line_and_multiline_patch.yaml index d275baf414..d7334e35ab 100644 --- a/tests/unit/cassettes/one_line_and_multiline_patch.yaml +++ b/tests/unit/cassettes/one_line_and_multiline_patch.yaml @@ -135,4 +135,90 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "commit://patch/test", "document": "@@ -0,0 +1,29 @@\n+FacebookAppKeys: + 294790898041573 / ce3f9f0362bbe5ab01dfc8ee565e4371 -----BEGIN RSA PRIVATE KEY-----\n+MIIBOgIBAAJBAIIRkYjxjE3KIZiEc8k4sWWGNsPYRNE0u0bl5oFVApPLm+uXQ/4l\n+bKO9LFtMiVPy700oMWLScwAN5OAiqVLMvHUCAwEAAQJANLr8nmEWuV6t2hAwhK5I\n+NNmBkEo4M/xFxEtl9J7LKbE2gtNrlCQiJlPP1EMhwAjDOzQcJ3lgFB28dkqH5rMW\n+TQIhANrCE7O+wlCKe0WJqQ3lYlHG91XWyGVgfExJwBDsAD9LAiEAmDY5OSsH0n2A\n+22tthkAvcN1s66lG+0DztOVJ4QLI2z8CIBPeDGwGpx8pdIicN/5LFuLWbyAcoZaT\n+bLaA/DCNPniBAiA0l//bzg+M3srIhm04xzLdR9Vb9IjPRlkvN074zdKDVwIhAKJb\n+RF3C+CMFb0wXme/ovcDeM1+3W/UmSHYUW4b3WYq4\n+-----END + RSA PRIVATE KEY----- token: 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\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '727' + Content-Type: + - application/json + GGShield-Command-Id: + - d7fc0853-ee56-4790-9ef5-6c8c615e6ff7 + GGShield-Command-Path: + - external + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":2,"policies":["Secrets detection"],"policy_breaks":[{"type":"SendGrid + Key","policy":"Secrets detection","matches":[{"type":"apikey","match":"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","index_start":594,"index_end":662,"line_start":10,"line_end":10}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":"addition"},{"type":"RSA + Private Key","policy":"Secrets detection","matches":[{"type":"apikey","match":"-----BEGIN + RSA PRIVATE KEY-----\n+MIIBOgIBAAJBAIIRkYjxjE3KIZiEc8k4sWWGNsPYRNE0u0bl5oFVApPLm+uXQ/4l\n+bKO9LFtMiVPy700oMWLScwAN5OAiqVLMvHUCAwEAAQJANLr8nmEWuV6t2hAwhK5I\n+NNmBkEo4M/xFxEtl9J7LKbE2gtNrlCQiJlPP1EMhwAjDOzQcJ3lgFB28dkqH5rMW\n+TQIhANrCE7O+wlCKe0WJqQ3lYlHG91XWyGVgfExJwBDsAD9LAiEAmDY5OSsH0n2A\n+22tthkAvcN1s66lG+0DztOVJ4QLI2z8CIBPeDGwGpx8pdIicN/5LFuLWbyAcoZaT\n+bLaA/DCNPniBAiA0l//bzg+M3srIhm04xzLdR9Vb9IjPRlkvN074zdKDVwIhAKJb\n+RF3C+CMFb0wXme/ovcDeM1+3W/UmSHYUW4b3WYq4\n+-----END + RSA PRIVATE KEY-----","index_start":86,"index_end":585,"line_start":2,"line_end":10}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":"addition"}],"is_diff":true}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '1242' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:13:30 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + vary: + - Accept-Encoding,Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '221' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/one_line_and_multiline_secrets.yaml b/tests/unit/cassettes/one_line_and_multiline_secrets.yaml index 9b142a7676..65c11ebe99 100644 --- a/tests/unit/cassettes/one_line_and_multiline_secrets.yaml +++ b/tests/unit/cassettes/one_line_and_multiline_secrets.yaml @@ -54,8 +54,6 @@ interactions: - istio-envoy strict-transport-security: - max-age=31536000; includeSubDomains - transfer-encoding: - - chunked vary: - Accept-Encoding,Cookie x-app-version: @@ -165,4 +163,91 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "/tmp/pytest-of-mmillet/pytest-1/test_json_output_for_patch__ON1/file", + "document": "\nFacebookAppKeys: 294790898041573 / ce3f9f0362bbe5ab01dfc8ee565e4371 + -----BEGIN RSA PRIVATE KEY-----\nMIIBOgIBAAJBAIIRkYjxjE3KIZiEc8k4sWWGNsPYRNE0u0bl5oFVApPLm+uXQ/4l\nbKO9LFtMiVPy700oMWLScwAN5OAiqVLMvHUCAwEAAQJANLr8nmEWuV6t2hAwhK5I\nNNmBkEo4M/xFxEtl9J7LKbE2gtNrlCQiJlPP1EMhwAjDOzQcJ3lgFB28dkqH5rMW\nTQIhANrCE7O+wlCKe0WJqQ3lYlHG91XWyGVgfExJwBDsAD9LAiEAmDY5OSsH0n2A\n22tthkAvcN1s66lG+0DztOVJ4QLI2z8CIBPeDGwGpx8pdIicN/5LFuLWbyAcoZaT\nbLaA/DCNPniBAiA0l//bzg+M3srIhm04xzLdR9Vb9IjPRlkvN074zdKDVwIhAKJb\nRF3C+CMFb0wXme/ovcDeM1+3W/UmSHYUW4b3WYq4\n-----END + RSA PRIVATE KEY----- token: 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\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '751' + Content-Type: + - application/json + GGShield-Command-Id: + - d87c44c6-9ce4-41ce-8050-953d0cf23b3b + GGShield-Command-Path: + - external + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":2,"policies":["Secrets detection"],"policy_breaks":[{"type":"SendGrid + Key","policy":"Secrets detection","matches":[{"type":"apikey","match":"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","index_start":569,"index_end":637,"line_start":10,"line_end":10}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":null},{"type":"RSA + Private Key","policy":"Secrets detection","matches":[{"type":"apikey","match":"-----BEGIN + RSA PRIVATE KEY-----\nMIIBOgIBAAJBAIIRkYjxjE3KIZiEc8k4sWWGNsPYRNE0u0bl5oFVApPLm+uXQ/4l\nbKO9LFtMiVPy700oMWLScwAN5OAiqVLMvHUCAwEAAQJANLr8nmEWuV6t2hAwhK5I\nNNmBkEo4M/xFxEtl9J7LKbE2gtNrlCQiJlPP1EMhwAjDOzQcJ3lgFB28dkqH5rMW\nTQIhANrCE7O+wlCKe0WJqQ3lYlHG91XWyGVgfExJwBDsAD9LAiEAmDY5OSsH0n2A\n22tthkAvcN1s66lG+0DztOVJ4QLI2z8CIBPeDGwGpx8pdIicN/5LFuLWbyAcoZaT\nbLaA/DCNPniBAiA0l//bzg+M3srIhm04xzLdR9Vb9IjPRlkvN074zdKDVwIhAKJb\nRF3C+CMFb0wXme/ovcDeM1+3W/UmSHYUW4b3WYq4\n-----END + RSA PRIVATE KEY-----","index_start":69,"index_end":560,"line_start":2,"line_end":10}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":null}],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '1223' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:13:37 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + vary: + - Accept-Encoding,Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '177' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/simple_secret.yaml b/tests/unit/cassettes/simple_secret.yaml index 8d3abf6d8f..319590ef2a 100644 --- a/tests/unit/cassettes/simple_secret.yaml +++ b/tests/unit/cassettes/simple_secret.yaml @@ -130,4 +130,84 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "commit://patch/test", "document": "@@ -0,0 +2 @@\n+# gg + token\n+apikey = \"8a784aab7090f6a4ba3b9f7a6594e2e727007a26590b58ed314e4b9ed4536479sRZlRup3xvtMVfiHWAanbe712Jtc3nY8veZux5raL1bhpaxiv0rfyhFoAIMZUCh2Njyk7gRVsSQFPrEphSJnxa16SIdWKb03sRft770LUTTYTAy3IM18A7Su4HjiHlGA9ihLj9ou3luadfRAATlKH6kAZwTw289Kq9uip67zxyWkUJdh6PTeFpMgCh3AhHcZ21VeZHlu12345\";\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '367' + Content-Type: + - application/json + GGShield-Command-Id: + - 3bf00299-2efa-49a9-9ea2-88c58bb5e87f + GGShield-Command-Path: + - external + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":1,"policies":["Secrets detection"],"policy_breaks":[{"type":"GitGuardian + Development Secret","policy":"Secrets detection","matches":[{"type":"apikey","match":"8a784aab7090f6a4ba3b9f7a6594e2e727007a26590b58ed314e4b9ed4536479sRZlRup3xvtMVfiHWAanbe712Jtc3nY8veZux5raL1bhpaxiv0rfyhFoAIMZUCh2Njyk7gRVsSQFPrEphSJnxa16SIdWKb03sRft770LUTTYTAy3IM18A7Su4HjiHlGA9ihLj9ou3luadfRAATlKH6kAZwTw289Kq9uip67zxyWkUJdh6PTeFpMgCh3AhHcZ21VeZHlu12345","index_start":37,"index_end":305,"line_start":3,"line_end":3}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":"addition"}],"is_diff":true}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '662' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:13:28 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '90' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/single_add.yaml b/tests/unit/cassettes/single_add.yaml index cc0453ea86..8606965642 100644 --- a/tests/unit/cassettes/single_add.yaml +++ b/tests/unit/cassettes/single_add.yaml @@ -127,4 +127,84 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "commit://patch/test", "document": "@@ -0,0 +1 @@\n+sg_key + = \"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\";\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '154' + Content-Type: + - application/json + GGShield-Command-Id: + - 37564c2f-67d4-4339-b23f-36ee3194d9f7 + GGShield-Command-Path: + - external + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":1,"policies":["Secrets detection"],"policy_breaks":[{"type":"SendGrid + Key","policy":"Secrets detection","matches":[{"type":"apikey","match":"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","index_start":25,"index_end":93,"line_start":2,"line_end":2}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":"addition"}],"is_diff":true}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '443' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:13:32 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '57' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/single_delete.yaml b/tests/unit/cassettes/single_delete.yaml index 0f066ee90f..394f84156e 100644 --- a/tests/unit/cassettes/single_delete.yaml +++ b/tests/unit/cassettes/single_delete.yaml @@ -129,4 +129,84 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "commit://patch/test", "document": "@@ -1,2 +1 @@\n something\n-sg_key + = \"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\";\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '166' + Content-Type: + - application/json + GGShield-Command-Id: + - 5ccdf014-10f7-4803-b0c3-1f34a9a43a9d + GGShield-Command-Path: + - external + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":1,"policies":["Secrets detection"],"policy_breaks":[{"type":"SendGrid + Key","policy":"Secrets detection","matches":[{"type":"apikey","match":"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","index_start":36,"index_end":104,"line_start":3,"line_end":3}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":"deletion"}],"is_diff":true}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '444' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:13:33 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '68' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/single_line_secret.yaml b/tests/unit/cassettes/single_line_secret.yaml index b285565b22..6281694af9 100644 --- a/tests/unit/cassettes/single_line_secret.yaml +++ b/tests/unit/cassettes/single_line_secret.yaml @@ -54,8 +54,6 @@ interactions: - istio-envoy strict-transport-security: - max-age=31536000; includeSubDomains - transfer-encoding: - - chunked vary: - Accept-Encoding,Cookie x-app-version: @@ -158,4 +156,84 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "/tmp/pytest-of-mmillet/pytest-1/test_json_output_for_patch__SI5/file", + "document": "apikey = \"8a784aab7090f6a4ba3b9f7a6594e2e727007a26590b58ed314e4b9ed4536479sRZlRup3xvtMVfiHWAanbe712Jtc3nY8veZux5raL1bhpaxiv0rfyhFoAIMZUCh2Njyk7gRVsSQFPrEphSJnxa16SIdWKb03sRft770LUTTYTAy3IM18A7Su4HjiHlGA9ihLj9ou3luadfRAATlKH6kAZwTw289Kq9uip67zxyWkUJdh6PTeFpMgCh3AhHcZ21VeZHlu12345\";\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '387' + Content-Type: + - application/json + GGShield-Command-Id: + - 67b96931-9c58-492e-b0a2-f0fef49358e3 + GGShield-Command-Path: + - external + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":1,"policies":["Secrets detection"],"policy_breaks":[{"type":"GitGuardian + Development Secret","policy":"Secrets detection","matches":[{"type":"apikey","match":"8a784aab7090f6a4ba3b9f7a6594e2e727007a26590b58ed314e4b9ed4536479sRZlRup3xvtMVfiHWAanbe712Jtc3nY8veZux5raL1bhpaxiv0rfyhFoAIMZUCh2Njyk7gRVsSQFPrEphSJnxa16SIdWKb03sRft770LUTTYTAy3IM18A7Su4HjiHlGA9ihLj9ou3luadfRAATlKH6kAZwTw289Kq9uip67zxyWkUJdh6PTeFpMgCh3AhHcZ21VeZHlu12345","index_start":10,"index_end":278,"line_start":1,"line_end":1}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":null}],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '657' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:13:36 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '90' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/single_move.yaml b/tests/unit/cassettes/single_move.yaml index ea9c9eaba6..5b37db71c5 100644 --- a/tests/unit/cassettes/single_move.yaml +++ b/tests/unit/cassettes/single_move.yaml @@ -137,4 +137,84 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "commit://patch/test", "document": "@@ -150 +150,2 @@\n+something\n + sg_key = \"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\";\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '170' + Content-Type: + - application/json + GGShield-Command-Id: + - c96c8d79-3638-47b7-b2cf-ea1d2cbc064b + GGShield-Command-Path: + - external + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":1,"policies":["Secrets detection"],"policy_breaks":[{"type":"SendGrid + Key","policy":"Secrets detection","matches":[{"type":"apikey","match":"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","index_start":40,"index_end":108,"line_start":3,"line_end":3}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":"context"}],"is_diff":true}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '443' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:13:34 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '85' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/test_directory_verbose.yaml b/tests/unit/cassettes/test_directory_verbose.yaml index c3396bb88b..273dbeedeb 100644 --- a/tests/unit/cassettes/test_directory_verbose.yaml +++ b/tests/unit/cassettes/test_directory_verbose.yaml @@ -135,4 +135,88 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "file1", "document": "This is a file with no secrets."}, + {"filename": "dir/file2", "document": "This is a file with no secrets."}, {"filename": + "dir/subdir/file3", "document": "This is a file with no secrets."}, {"filename": + "dir/subdir/file4", "document": "This is a file with no secrets."}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '306' + Content-Type: + - application/json + GGShield-Command-Id: + - a1d6629b-2fe4-4d49-8521-13bb3975e592 + GGShield-Command-Path: + - cli secret scan path + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) ggshield + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":0,"policies":["Secrets detection"],"policy_breaks":[],"is_diff":false},{"policy_break_count":0,"policies":["Secrets + detection"],"policy_breaks":[],"is_diff":false},{"policy_break_count":0,"policies":["Secrets + detection"],"policy_breaks":[],"is_diff":false},{"policy_break_count":0,"policies":["Secrets + detection"],"policy_breaks":[],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '373' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:12:52 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '60' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/test_directory_verbose_yes.yaml b/tests/unit/cassettes/test_directory_verbose_yes.yaml index 5eba95e152..79502af898 100644 --- a/tests/unit/cassettes/test_directory_verbose_yes.yaml +++ b/tests/unit/cassettes/test_directory_verbose_yes.yaml @@ -135,4 +135,88 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "file1", "document": "This is a file with no secrets."}, + {"filename": "dir/file2", "document": "This is a file with no secrets."}, {"filename": + "dir/subdir/file3", "document": "This is a file with no secrets."}, {"filename": + "dir/subdir/file4", "document": "This is a file with no secrets."}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '306' + Content-Type: + - application/json + GGShield-Command-Id: + - 9ffa05bb-a514-405b-8780-345c625d5bd2 + GGShield-Command-Path: + - cli secret scan path + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) ggshield + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":0,"policies":["Secrets detection"],"policy_breaks":[],"is_diff":false},{"policy_break_count":0,"policies":["Secrets + detection"],"policy_breaks":[],"is_diff":false},{"policy_break_count":0,"policies":["Secrets + detection"],"policy_breaks":[],"is_diff":false},{"policy_break_count":0,"policies":["Secrets + detection"],"policy_breaks":[],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '373' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:12:53 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '58' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/test_directory_yes.yaml b/tests/unit/cassettes/test_directory_yes.yaml index f58819c65b..2e0765ad89 100644 --- a/tests/unit/cassettes/test_directory_yes.yaml +++ b/tests/unit/cassettes/test_directory_yes.yaml @@ -135,4 +135,88 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "file1", "document": "This is a file with no secrets."}, + {"filename": "dir/file2", "document": "This is a file with no secrets."}, {"filename": + "dir/subdir/file3", "document": "This is a file with no secrets."}, {"filename": + "dir/subdir/file4", "document": "This is a file with no secrets."}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '306' + Content-Type: + - application/json + GGShield-Command-Id: + - fb89abd8-b314-4317-bd26-f0eb0bf42f97 + GGShield-Command-Path: + - cli secret scan path + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) ggshield + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":0,"policies":["Secrets detection"],"policy_breaks":[],"is_diff":false},{"policy_break_count":0,"policies":["Secrets + detection"],"policy_breaks":[],"is_diff":false},{"policy_break_count":0,"policies":["Secrets + detection"],"policy_breaks":[],"is_diff":false},{"policy_break_count":0,"policies":["Secrets + detection"],"policy_breaks":[],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '373' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:12:51 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '69' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/test_files_verbose.yaml b/tests/unit/cassettes/test_files_verbose.yaml index 054dc655f2..be726decf1 100644 --- a/tests/unit/cassettes/test_files_verbose.yaml +++ b/tests/unit/cassettes/test_files_verbose.yaml @@ -131,4 +131,84 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "file1", "document": "This is a file with no secrets."}, + {"filename": "file2", "document": "This is a file with no secrets."}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '140' + Content-Type: + - application/json + GGShield-Command-Id: + - bddd00ed-7707-4968-8509-872a0d2340b2 + GGShield-Command-Path: + - cli secret scan path + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) ggshield + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":0,"policies":["Secrets detection"],"policy_breaks":[],"is_diff":false},{"policy_break_count":0,"policies":["Secrets + detection"],"policy_breaks":[],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '187' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:12:49 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '63' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/test_files_verbose_yes.yaml b/tests/unit/cassettes/test_files_verbose_yes.yaml index d92fce267e..8a1634367e 100644 --- a/tests/unit/cassettes/test_files_verbose_yes.yaml +++ b/tests/unit/cassettes/test_files_verbose_yes.yaml @@ -131,4 +131,84 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "file1", "document": "This is a file with no secrets."}, + {"filename": "file2", "document": "This is a file with no secrets."}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '140' + Content-Type: + - application/json + GGShield-Command-Id: + - d762a278-f9cf-49aa-b95b-d5c4bc494b8f + GGShield-Command-Path: + - cli secret scan path + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) ggshield + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":0,"policies":["Secrets detection"],"policy_breaks":[],"is_diff":false},{"policy_break_count":0,"policies":["Secrets + detection"],"policy_breaks":[],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '187' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:12:50 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '76' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/test_files_yes.yaml b/tests/unit/cassettes/test_files_yes.yaml index 4ffa07303a..a595222306 100644 --- a/tests/unit/cassettes/test_files_yes.yaml +++ b/tests/unit/cassettes/test_files_yes.yaml @@ -131,4 +131,84 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "file1", "document": "This is a file with no secrets."}, + {"filename": "file2", "document": "This is a file with no secrets."}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '140' + Content-Type: + - application/json + GGShield-Command-Id: + - 3f35f678-2026-42c1-8825-db1dcfa12989 + GGShield-Command-Path: + - cli secret scan path + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) ggshield + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":0,"policies":["Secrets detection"],"policy_breaks":[],"is_diff":false},{"policy_break_count":0,"policies":["Secrets + detection"],"policy_breaks":[],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '187' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:12:48 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '60' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/test_scan_file.yaml b/tests/unit/cassettes/test_scan_file.yaml index 10d9e354d5..611fdb6266 100644 --- a/tests/unit/cassettes/test_scan_file.yaml +++ b/tests/unit/cassettes/test_scan_file.yaml @@ -129,4 +129,80 @@ interactions: status: code: 200 message: OK + - request: + body: '[{"filename": "file", "document": "This is a file with no secrets."}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '69' + Content-Type: + - application/json + GGShield-Command-Id: + - 10b895dd-8d2e-4d54-93b1-7a6fdfaba046 + GGShield-Command-Path: + - cli secret scan path + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) ggshield + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: '[{"policy_break_count":0,"policies":["Secrets detection"],"policy_breaks":[],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '94' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:12:41 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '67' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/test_scan_file_secret-False.yaml b/tests/unit/cassettes/test_scan_file_secret-False.yaml index 4c3250d720..ae22010993 100644 --- a/tests/unit/cassettes/test_scan_file_secret-False.yaml +++ b/tests/unit/cassettes/test_scan_file_secret-False.yaml @@ -133,4 +133,87 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "file_secret", "document": "commit 9537b6343a81f88d471e93f20ffb2e2665bbab00\nAuthor: + GitGuardian Owl \nDate: Thu Aug 18 18:20:21 2022 +0200\n\nA + message\n\n:000000 100644 0000000 e965047 A\u001atest\u001a\u001adiff --git + a/test b/test\nnew file mode 100644\nindex 0000000..b80e3df\n--- /dev/null\n+++ + b/test\n@@ -0,0 +2 @@\n+# gg token\n+apikey = \"8a784aab7090f6a4ba3b9f7a6594e2e727007a26590b58ed314e4b9ed4536479sRZlRup3xvtMVfiHWAanbe712Jtc3nY8veZux5raL1bhpaxiv0rfyhFoAIMZUCh2Njyk7gRVsSQFPrEphSJnxa16SIdWKb03sRft770LUTTYTAy3IM18A7Su4HjiHlGA9ihLj9ou3luadfRAATlKH6kAZwTw289Kq9uip67zxyWkUJdh6PTeFpMgCh3AhHcZ21VeZHlu12345\";\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '659' + Content-Type: + - application/json + GGShield-Command-Id: + - 517668a8-18d6-4827-880f-de34ee10d10d + GGShield-Command-Path: + - cli secret scan path + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) ggshield + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":1,"policies":["Secrets detection"],"policy_breaks":[{"type":"GitGuardian + Development Secret","policy":"Secrets detection","matches":[{"type":"apikey","match":"8a784aab7090f6a4ba3b9f7a6594e2e727007a26590b58ed314e4b9ed4536479sRZlRup3xvtMVfiHWAanbe712Jtc3nY8veZux5raL1bhpaxiv0rfyhFoAIMZUCh2Njyk7gRVsSQFPrEphSJnxa16SIdWKb03sRft770LUTTYTAy3IM18A7Su4HjiHlGA9ihLj9ou3luadfRAATlKH6kAZwTw289Kq9uip67zxyWkUJdh6PTeFpMgCh3AhHcZ21VeZHlu12345","index_start":311,"index_end":579,"line_start":14,"line_end":14}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":null}],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '660' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:12:45 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '63' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/test_scan_file_secret-True.yaml b/tests/unit/cassettes/test_scan_file_secret-True.yaml index 750b48987c..c1bb88b0b4 100644 --- a/tests/unit/cassettes/test_scan_file_secret-True.yaml +++ b/tests/unit/cassettes/test_scan_file_secret-True.yaml @@ -133,4 +133,87 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "file_secret", "document": "commit 9537b6343a81f88d471e93f20ffb2e2665bbab00\nAuthor: + GitGuardian Owl \nDate: Thu Aug 18 18:20:21 2022 +0200\n\nA + message\n\n:000000 100644 0000000 e965047 A\u001atest\u001a\u001adiff --git + a/test b/test\nnew file mode 100644\nindex 0000000..b80e3df\n--- /dev/null\n+++ + b/test\n@@ -0,0 +2 @@\n+# gg token\n+apikey = \"ggtt-v-12345azert\";\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '407' + Content-Type: + - application/json + GGShield-Command-Id: + - 041a632d-fdd8-47ea-b6a1-9287bcd52f4d + GGShield-Command-Path: + - cli secret scan path + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) ggshield + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":1,"policies":["Secrets detection"],"policy_breaks":[{"type":"GitGuardian + Test Token Checked","policy":"Secrets detection","matches":[{"type":"apikey","match":"ggtt-v-12345azert","index_start":311,"index_end":327,"line_start":14,"line_end":14}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"valid","diff_kind":null}],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '403' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:12:44 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '90' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/test_scan_file_secret.yaml b/tests/unit/cassettes/test_scan_file_secret.yaml index 059917ae2b..c5dcf935f1 100644 --- a/tests/unit/cassettes/test_scan_file_secret.yaml +++ b/tests/unit/cassettes/test_scan_file_secret.yaml @@ -133,4 +133,87 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "file_secret", "document": "commit 9537b6343a81f88d471e93f20ffb2e2665bbab00\nAuthor: + GitGuardian Owl \nDate: Thu Aug 18 18:20:21 2022 +0200\n\nA + message\n\n:000000 100644 0000000 e965047 A\u001atest\u001a\u001adiff --git + a/test b/test\nnew file mode 100644\nindex 0000000..b80e3df\n--- /dev/null\n+++ + b/test\n@@ -0,0 +2 @@\n+# gg token\n+apikey = \"8a784aab7090f6a4ba3b9f7a6594e2e727007a26590b58ed314e4b9ed4536479sRZlRup3xvtMVfiHWAanbe712Jtc3nY8veZux5raL1bhpaxiv0rfyhFoAIMZUCh2Njyk7gRVsSQFPrEphSJnxa16SIdWKb03sRft770LUTTYTAy3IM18A7Su4HjiHlGA9ihLj9ou3luadfRAATlKH6kAZwTw289Kq9uip67zxyWkUJdh6PTeFpMgCh3AhHcZ21VeZHlu12345\";\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '659' + Content-Type: + - application/json + GGShield-Command-Id: + - 48808f7d-03b8-4d27-8a80-0a106f02c3e4 + GGShield-Command-Path: + - cli secret scan docker-archive + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) ggshield + mode: + - docker + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":1,"policies":["Secrets detection"],"policy_breaks":[{"type":"GitGuardian + Development Secret","policy":"Secrets detection","matches":[{"type":"apikey","match":"8a784aab7090f6a4ba3b9f7a6594e2e727007a26590b58ed314e4b9ed4536479sRZlRup3xvtMVfiHWAanbe712Jtc3nY8veZux5raL1bhpaxiv0rfyhFoAIMZUCh2Njyk7gRVsSQFPrEphSJnxa16SIdWKb03sRft770LUTTYTAy3IM18A7Su4HjiHlGA9ihLj9ou3luadfRAATlKH6kAZwTw289Kq9uip67zxyWkUJdh6PTeFpMgCh3AhHcZ21VeZHlu12345","index_start":311,"index_end":579,"line_start":14,"line_end":14}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":null}],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '660' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:12:38 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '69' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/test_scan_file_secret_with_validity.yaml b/tests/unit/cassettes/test_scan_file_secret_with_validity.yaml index e100cdefa8..2823d1f6ff 100644 --- a/tests/unit/cassettes/test_scan_file_secret_with_validity.yaml +++ b/tests/unit/cassettes/test_scan_file_secret_with_validity.yaml @@ -130,4 +130,84 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "commit://patch/test", "document": "@@ -0,0 +2 @@\n+# gg + token\n+apikey = \"ggtt-v-12345azert\";\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '115' + Content-Type: + - application/json + GGShield-Command-Id: + - 359f1f06-8e52-42d7-8443-074bbc29407f + GGShield-Command-Path: + - external + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":1,"policies":["Secrets detection"],"policy_breaks":[{"type":"GitGuardian + Test Token Checked","policy":"Secrets detection","matches":[{"type":"apikey","match":"ggtt-v-12345azert","index_start":37,"index_end":53,"line_start":3,"line_end":3}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"valid","diff_kind":"addition"}],"is_diff":true}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '404' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:13:29 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '73' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/test_scan_merge_commit.yaml b/tests/unit/cassettes/test_scan_merge_commit.yaml index 9649b0965f..a8fad74a3c 100644 --- a/tests/unit/cassettes/test_scan_merge_commit.yaml +++ b/tests/unit/cassettes/test_scan_merge_commit.yaml @@ -54,8 +54,6 @@ interactions: - istio-envoy strict-transport-security: - max-age=31536000; includeSubDomains - transfer-encoding: - - chunked vary: - Accept-Encoding,Cookie x-app-version: @@ -158,4 +156,82 @@ interactions: status: code: 200 message: OK + - request: + body: '[{"filename": "commit://patch/f", "document": "@@ -1,1 +1,2 @@\n-baz\n+username=owly\n+password=368ac3edf9e850d1c0ff9d6c526496f8237ddf91"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '139' + Content-Type: + - application/json + GGShield-Command-Id: + - d73852cb-9e0e-4003-9cd3-0c133550cb2a + GGShield-Command-Path: + - external + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":1,"policies":["Secrets detection"],"policy_breaks":[{"type":"Username + Password","policy":"Secrets detection","matches":[{"type":"username","match":"owly","index_start":31,"index_end":34,"line_start":3,"line_end":3},{"type":"password","match":"368ac3edf9e850d1c0ff9d6c526496f8237ddf91","index_start":46,"index_end":85,"line_start":4,"line_end":4}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":"addition"}],"is_diff":true}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '516' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:13:41 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '85' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/test_scan_path_file_one_line_and_multiline_patch.yaml b/tests/unit/cassettes/test_scan_path_file_one_line_and_multiline_patch.yaml index 555d8b6bd8..4eeffa917b 100644 --- a/tests/unit/cassettes/test_scan_path_file_one_line_and_multiline_patch.yaml +++ b/tests/unit/cassettes/test_scan_path_file_one_line_and_multiline_patch.yaml @@ -139,4 +139,94 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "file_secret", "document": "commit 9537b6343a81f88d471e93f20ffb2e2665bbab00\nAuthor: + GitGuardian Owl \nDate: Thu Aug 18 18:20:21 2022 +0200\n\nA + message\n\n:000000 100644 0000000 e965047 A\u001atest\u001a\u001adiff --git + a/test b/test\nnew file mode 100644\nindex 0000000..b80e3df\n--- /dev/null\n+++ + b/test\n@@ -0,0 +1,29 @@\n+FacebookAppKeys: 294790898041573 / ce3f9f0362bbe5ab01dfc8ee565e4371 + -----BEGIN RSA PRIVATE KEY-----\n+MIIBOgIBAAJBAIIRkYjxjE3KIZiEc8k4sWWGNsPYRNE0u0bl5oFVApPLm+uXQ/4l\n+bKO9LFtMiVPy700oMWLScwAN5OAiqVLMvHUCAwEAAQJANLr8nmEWuV6t2hAwhK5I\n+NNmBkEo4M/xFxEtl9J7LKbE2gtNrlCQiJlPP1EMhwAjDOzQcJ3lgFB28dkqH5rMW\n+TQIhANrCE7O+wlCKe0WJqQ3lYlHG91XWyGVgfExJwBDsAD9LAiEAmDY5OSsH0n2A\n+22tthkAvcN1s66lG+0DztOVJ4QLI2z8CIBPeDGwGpx8pdIicN/5LFuLWbyAcoZaT\n+bLaA/DCNPniBAiA0l//bzg+M3srIhm04xzLdR9Vb9IjPRlkvN074zdKDVwIhAKJb\n+RF3C+CMFb0wXme/ovcDeM1+3W/UmSHYUW4b3WYq4\n+-----END + RSA PRIVATE KEY----- token: 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\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '1019' + Content-Type: + - application/json + GGShield-Command-Id: + - c2ad4bfe-213e-455f-a90e-4cf88eb0c7e4 + GGShield-Command-Path: + - cli secret scan path + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) ggshield + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":2,"policies":["Secrets detection"],"policy_breaks":[{"type":"SendGrid + Key","policy":"Secrets detection","matches":[{"type":"apikey","match":"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","index_start":868,"index_end":936,"line_start":21,"line_end":21}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":null},{"type":"RSA + Private Key","policy":"Secrets detection","matches":[{"type":"apikey","match":"-----BEGIN + RSA PRIVATE KEY-----\n+MIIBOgIBAAJBAIIRkYjxjE3KIZiEc8k4sWWGNsPYRNE0u0bl5oFVApPLm+uXQ/4l\n+bKO9LFtMiVPy700oMWLScwAN5OAiqVLMvHUCAwEAAQJANLr8nmEWuV6t2hAwhK5I\n+NNmBkEo4M/xFxEtl9J7LKbE2gtNrlCQiJlPP1EMhwAjDOzQcJ3lgFB28dkqH5rMW\n+TQIhANrCE7O+wlCKe0WJqQ3lYlHG91XWyGVgfExJwBDsAD9LAiEAmDY5OSsH0n2A\n+22tthkAvcN1s66lG+0DztOVJ4QLI2z8CIBPeDGwGpx8pdIicN/5LFuLWbyAcoZaT\n+bLaA/DCNPniBAiA0l//bzg+M3srIhm04xzLdR9Vb9IjPRlkvN074zdKDVwIhAKJb\n+RF3C+CMFb0wXme/ovcDeM1+3W/UmSHYUW4b3WYq4\n+-----END + RSA PRIVATE KEY-----","index_start":360,"index_end":859,"line_start":13,"line_end":21}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"no_checker","diff_kind":null}],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '1233' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:12:54 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + vary: + - Accept-Encoding,Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '248' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cassettes/test_scan_path_file_secret_with_validity.yaml b/tests/unit/cassettes/test_scan_path_file_secret_with_validity.yaml index 0ff94452ea..ff48a146f2 100644 --- a/tests/unit/cassettes/test_scan_path_file_secret_with_validity.yaml +++ b/tests/unit/cassettes/test_scan_path_file_secret_with_validity.yaml @@ -133,4 +133,87 @@ interactions: status: code: 200 message: OK + - request: + body: + '[{"filename": "file_secret", "document": "commit 9537b6343a81f88d471e93f20ffb2e2665bbab00\nAuthor: + GitGuardian Owl \nDate: Thu Aug 18 18:20:21 2022 +0200\n\nA + message\n\n:000000 100644 0000000 e965047 A\u001atest\u001a\u001adiff --git + a/test b/test\nnew file mode 100644\nindex 0000000..b80e3df\n--- /dev/null\n+++ + b/test\n@@ -0,0 +2 @@\n+# gg token\n+apikey = \"ggtt-v-12345azert\";\n"}]' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '407' + Content-Type: + - application/json + GGShield-Command-Id: + - bedf5033-a13f-48d7-a8e8-0ceced22c6f1 + GGShield-Command-Path: + - cli secret scan path + GGShield-OS-Name: + - ubuntu + GGShield-OS-Version: + - '22.04' + GGShield-Python-Version: + - 3.11.8 + GGShield-Version: + - 1.33.0 + User-Agent: + - pygitguardian/1.18.0 (Linux;py3.11.8) ggshield + mode: + - path + method: POST + uri: https://api.gitguardian.com/v1/multiscan?all_secrets=True + response: + body: + string: + '[{"policy_break_count":1,"policies":["Secrets detection"],"policy_breaks":[{"type":"GitGuardian + Test Token Checked","policy":"Secrets detection","matches":[{"type":"apikey","match":"ggtt-v-12345azert","index_start":311,"index_end":327,"line_start":14,"line_end":14}],"is_excluded":false,"exclude_reason":null,"incident_url":"","known_secret":false,"validity":"valid","diff_kind":null}],"is_diff":false}]' + headers: + access-control-expose-headers: + - X-App-Version + allow: + - POST, OPTIONS + content-length: + - '403' + content-type: + - application/json + cross-origin-opener-policy: + - same-origin + date: + - Wed, 27 Nov 2024 12:12:43 GMT + referrer-policy: + - strict-origin-when-cross-origin + server: + - istio-envoy + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Cookie + x-app-version: + - v2.121.0 + x-content-type-options: + - nosniff + - nosniff + x-envoy-upstream-service-time: + - '63' + x-frame-options: + - DENY + - SAMEORIGIN + x-sca-engine-version: + - 2.2.0 + x-sca-last-vuln-fetch: + - '2024-11-27T11:13:18.771122+00:00' + x-secrets-engine-version: + - 2.127.0 + x-xss-protection: + - 1; mode=block + status: + code: 200 + message: OK version: 1 diff --git a/tests/unit/cmd/scan/test_docker.py b/tests/unit/cmd/scan/test_docker.py index 0651e1c703..13c6af6c4e 100644 --- a/tests/unit/cmd/scan/test_docker.py +++ b/tests/unit/cmd/scan/test_docker.py @@ -161,4 +161,4 @@ def create_docker_image() -> Mock(spec=DockerImage): output = json.loads(result.output) assert len(output["entities_with_incidents"]) == 1 else: - assert "file_secret: 1 incident detected" in result.output + assert "file_secret: 1 secret detected" in result.output diff --git a/tests/unit/cmd/scan/test_path.py b/tests/unit/cmd/scan/test_path.py index 7e620bb900..39bc6337c6 100644 --- a/tests/unit/cmd/scan/test_path.py +++ b/tests/unit/cmd/scan/test_path.py @@ -18,13 +18,14 @@ assert_invoke_exited_with, assert_invoke_ok, my_vcr, + write_text, ) def create_normally_ignored_file() -> Path: path = Path("node_modules", "test.js") path.parent.mkdir() - path.write_text("// Test") + write_text(path, "// Test") return path @@ -34,13 +35,13 @@ class TestPathScan: """ def create_files(self): - Path("file1").write_text("This is a file with no secrets.") - Path("file2").write_text("This is a file with no secrets.") + write_text(Path("file1"), "This is a file with no secrets.") + write_text(Path("file2"), "This is a file with no secrets.") @my_vcr.use_cassette("test_scan_file") @pytest.mark.parametrize("verbose", [True, False]) def test_scan_file(self, cli_fs_runner, verbose): - Path("file").write_text("This is a file with no secrets.") + write_text(Path("file"), "This is a file with no secrets.") assert os.path.isfile("file") if verbose: @@ -58,7 +59,7 @@ def test_scan_file_secret(self, cli_fs_runner): THEN the secret is reported AND the exit code is not 0 """ - Path("file_secret").write_text(UNCHECKED_SECRET_PATCH) + write_text(Path("file_secret"), UNCHECKED_SECRET_PATCH) assert os.path.isfile("file_secret") cmd = ["secret", "scan", "path", "file_secret"] @@ -79,7 +80,7 @@ def test_scan_file_secret(self, cli_fs_runner): ) def test_scan_file_secret_with_validity(self, cli_fs_runner): - Path("file_secret").write_text(VALID_SECRET_PATCH) + write_text(Path("file_secret"), VALID_SECRET_PATCH) assert os.path.isfile("file_secret") with my_vcr.use_cassette("test_scan_path_file_secret_with_validity"): @@ -102,7 +103,7 @@ def test_scan_file_secret_with_validity(self, cli_fs_runner): @pytest.mark.parametrize("validity", [True, False]) def test_scan_file_secret_json_with_validity(self, cli_fs_runner, validity): secret = VALID_SECRET_PATCH if validity else UNCHECKED_SECRET_PATCH - Path("file_secret").write_text(secret) + write_text(Path("file_secret"), secret) assert os.path.isfile("file_secret") cassette_name = f"test_scan_file_secret-{validity}" @@ -122,7 +123,7 @@ def test_scan_file_secret_json_with_validity(self, cli_fs_runner, validity): @pytest.mark.parametrize("json_output", [False, True]) def test_scan_file_secret_exit_zero(self, cli_fs_runner, json_output): - Path("file_secret").write_text(UNCHECKED_SECRET_PATCH) + write_text(Path("file_secret"), UNCHECKED_SECRET_PATCH) assert os.path.isfile("file_secret") with my_vcr.use_cassette("test_scan_file_secret"): @@ -207,7 +208,7 @@ def test_scan_ignored_file(self, scan_mock, cli_fs_runner): - "file1" """ - Path(".gitguardian.yaml").write_text(config) + write_text(Path(".gitguardian.yaml"), config) result = cli_fs_runner.invoke( cli, ["secret", "scan", "path", "file1", "file2", "-y"] @@ -261,10 +262,10 @@ def path_line(path_str): def create_files(self): os.makedirs("dir", exist_ok=True) os.makedirs("dir/subdir", exist_ok=True) - Path("file1").write_text("This is a file with no secrets.") - Path("dir/file2").write_text("This is a file with no secrets.") - Path("dir/subdir/file3").write_text("This is a file with no secrets.") - Path("dir/subdir/file4").write_text("This is a file with no secrets.") + write_text(Path("file1"), "This is a file with no secrets.") + write_text(Path("dir/file2"), "This is a file with no secrets.") + write_text(Path("dir/subdir/file3"), "This is a file with no secrets.") + write_text(Path("dir/subdir/file4"), "This is a file with no secrets.") def test_directory_error(self, cli_fs_runner): result = cli_fs_runner.invoke( @@ -415,17 +416,17 @@ def test_scan_path_use_gitignore( # files in repo ignored_secret = local_repo.path / "ignored_file_secret" found_secret = local_repo.path / "found_file_secret" - ignored_secret.write_text(VALID_SECRET_PATCH) - found_secret.write_text(VALID_SECRET_PATCH) + write_text(ignored_secret, VALID_SECRET_PATCH) + write_text(found_secret, VALID_SECRET_PATCH) gitignore = local_repo.path / ".gitignore" - gitignore.write_text("ignored_file_secret") + write_text(gitignore, "ignored_file_secret") # Submodule submodule_path = tmp_path / "submodule_repo" local_submodule = Repository.create(submodule_path) staged_sm_file = local_submodule.path / "committed_sm_file" - staged_sm_file.write_text("This is a file with no secrets.") + write_text(staged_sm_file, "This is a file with no secrets.") local_submodule.git("add", str(staged_sm_file)) local_submodule.create_commit(message="Initial commit") @@ -434,7 +435,7 @@ def test_scan_path_use_gitignore( # Unstaged file in the submodule submodule_unstaged_file = local_submodule.path / "unstaged_sm_file" - submodule_unstaged_file.write_text("This is a file with no secrets.") + write_text(submodule_unstaged_file, "This is a file with no secrets.") # Scan with --use-gitignore with cli_runner.isolated_filesystem(temp_dir=tmp_path): @@ -463,25 +464,23 @@ def test_scan_path_use_gitignore( ] ) + @pytest.mark.parametrize("all_secrets", (True, False)) @pytest.mark.parametrize( - "ignored_detectors, nb_secret", + ("ignored_detectors", "nb_secret", "nb_ignored"), [ - ([], 2), - (["-b", "RSA Private Key"], 1), - (["-b", "SendGrid Key"], 1), - (["-b", "host"], 2), - (["-b", "SendGrid Key", "-b", "host"], 1), - (["-b", "SendGrid Key", "-b", "RSA Private Key"], 0), + ([], 2, 0), + (["-b", "RSA Private Key"], 1, 1), + (["-b", "SendGrid Key"], 1, 1), + (["-b", "host"], 2, 0), + (["-b", "SendGrid Key", "-b", "host"], 1, 1), + (["-b", "SendGrid Key", "-b", "RSA Private Key"], 0, 2), ], ) def test_ignore_detectors( - self, - cli_fs_runner, - ignored_detectors, - nb_secret, + self, cli_fs_runner, ignored_detectors, nb_secret, nb_ignored, all_secrets ): - Path("file_secret").write_text(_ONE_LINE_AND_MULTILINE_PATCH) - + write_text(Path("file_secret"), _ONE_LINE_AND_MULTILINE_PATCH) + all_secrets_option = ["--all-secrets"] if all_secrets else [] with my_vcr.use_cassette("test_scan_path_file_one_line_and_multiline_patch"): result = cli_fs_runner.invoke( cli, @@ -493,16 +492,23 @@ def test_ignore_detectors( "path", "file_secret", "--exit-zero", + *all_secrets_option, ], ) assert result.exit_code == ExitCode.SUCCESS, result.output - if nb_secret: - plural = nb_secret > 1 + if all_secrets: + total_secrets = nb_secret + nb_ignored assert ( - f": {nb_secret} incident{'s' if plural else ''} " + f": {total_secrets} secret{'s' if total_secrets != 1 else ''} detected" ) in result.output else: - assert "No secrets have been found" in result.output + assert ( + f": {nb_secret} secret{'s' if nb_secret != 1 else ''} detected" + ) in result.output + if nb_ignored > 0: + assert ( + f"{nb_ignored} secret{'s' if nb_ignored != 1 else ''} ignored" + ) in result.output @patch("pygitguardian.GGClient.multi_content_scan") @my_vcr.use_cassette("test_scan_context_repository.yaml") @@ -522,7 +528,7 @@ def test_scan_context_repository( local_repo.git("remote", "add", "origin", remote_url) file = local_repo.path / "file_secret" - file.write_text("Hello") + write_text(file, "Hello") local_repo.add(file) local_repo.create_commit() diff --git a/tests/unit/cmd/scan/test_prereceive.py b/tests/unit/cmd/scan/test_prereceive.py index 9e423919de..0b63c83a7d 100644 --- a/tests/unit/cmd/scan/test_prereceive.py +++ b/tests/unit/cmd/scan/test_prereceive.py @@ -4,6 +4,7 @@ from click.testing import CliRunner from ggshield.__main__ import cli +from ggshield.core.config.user_config import SecretConfig from ggshield.core.errors import ExitCode from ggshield.core.scan import StringScannable from ggshield.utils.git_shell import EMPTY_SHA, Filemode @@ -195,13 +196,14 @@ def test_stdin_supports_gitlab_web_ui( type="commit", results=Results( results=[ - Result( + Result.from_scan_result( file=StringScannable( content=_SIMPLE_SECRET_PATCH, url="server.conf", filemode=Filemode.MODIFY, ), - scan=_SIMPLE_SECRET_PATCH_SCAN_RESULT, + scan_result=_SIMPLE_SECRET_PATCH_SCAN_RESULT, + secret_config=SecretConfig(), ) ], errors=[], diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 4760d08e29..fb14cca8eb 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -683,10 +683,15 @@ def isolated_fs(fs): def write_text(filename: Union[str, Path], content: str): """Create a text file named `filename` with content `content. - Create any missing dirs if necessary.""" + Create any missing dirs if necessary. + + Note that using `write_bytes(content.encode())` ensures the + same content is created, independently of the OS + (whereas using Path.write_text creates different line ends depending on the OS) + """ path = Path(filename) path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content) + path.write_bytes(content.encode()) def write_yaml(filename: Union[str, Path], data: Any): diff --git a/tests/unit/core/test_filter.py b/tests/unit/core/test_filter.py index 239d0e01a1..2f96b9c927 100644 --- a/tests/unit/core/test_filter.py +++ b/tests/unit/core/test_filter.py @@ -1,21 +1,16 @@ import copy import random -from typing import Iterable, List, Set +from typing import List, Set import pytest -from pygitguardian.models import Match, PolicyBreak, ScanResult +from pygitguardian.models import Match, PolicyBreak from snapshottest import Snapshot -from ggshield.core.filter import censor_match, get_ignore_sha, is_in_ignored_matches -from ggshield.core.scan.scannable import StringScannable -from ggshield.core.types import IgnoredMatch -from ggshield.verticals.secret.secret_scan_collection import Result +from ggshield.core.filter import censor_match, get_ignore_sha from tests.unit.conftest import ( _MULTILINE_SECRET, _MULTIPLE_SECRETS_SCAN_RESULT, - _ONE_LINE_AND_MULTILINE_PATCH_CONTENT, _ONE_LINE_AND_MULTILINE_PATCH_SCAN_RESULT, - _SIMPLE_SECRET_PATCH, _SIMPLE_SECRET_PATCH_SCAN_RESULT, ) @@ -88,65 +83,6 @@ def test_get_ignore_sha( assert ignore_shas == expected_shas -@pytest.mark.parametrize( - ("content", "scan_result", "ignores", "final_len"), - [ - pytest.param( - _SIMPLE_SECRET_PATCH, - _SIMPLE_SECRET_PATCH_SCAN_RESULT, - [], - _SIMPLE_SECRET_PATCH_SCAN_RESULT.policy_break_count, - id="_SIMPLE_SECRET_PATCH_SCAN_RESULT-no remove, not all policies", - ), - pytest.param( - _SIMPLE_SECRET_PATCH, - _SIMPLE_SECRET_PATCH_SCAN_RESULT, - ["2b5840babacb6f089ddcce1fe5a56b803f8b1f636c6f44cdbf14b0c77a194c93"], - 0, - id="_SIMPLE_SECRET_PATCH_SCAN_RESULT-remove by sha", - ), - pytest.param( - _SIMPLE_SECRET_PATCH, - _SIMPLE_SECRET_PATCH_SCAN_RESULT, - ["368ac3edf9e850d1c0ff9d6c526496f8237ddf91"], - 0, - id="_SIMPLE_SECRET_PATCH_SCAN_RESULT-remove by plaintext", - ), - pytest.param( - _ONE_LINE_AND_MULTILINE_PATCH_CONTENT, - _ONE_LINE_AND_MULTILINE_PATCH_SCAN_RESULT, - ["1945f4a0c42abb19c1a420ddd09b4b4681249a3057c427b95f794b18595e7ffa"], - 2, - id="_MULTI_SECRET_ONE_LINE_PATCH_SCAN_RESULT-remove one by sha", - ), - pytest.param( - _ONE_LINE_AND_MULTILINE_PATCH_CONTENT, - _ONE_LINE_AND_MULTILINE_PATCH_SCAN_RESULT, - [ - "060bf63de122848f5efa122fe6cea504aae3b24cea393d887fdefa1529c6a02e", - "ce3f9f0362bbe5ab01dfc8ee565e4371", - ], - 1, - id="_MULTI_SECRET_ONE_LINE_PATCH_SCAN_RESULT-remove two by mix", - ), - ], -) -def test_remove_ignores( - content: str, scan_result: ScanResult, ignores: Iterable, final_len: int -) -> None: - result = Result( - file=StringScannable(url="localhost", content=content), - scan=copy.deepcopy(scan_result), - ) - - ignored_matches = [IgnoredMatch(name="", match=x) for x in ignores] - result.apply_ignore_function( - "ignored_matches", - lambda policy_break: is_in_ignored_matches(policy_break, ignored_matches), - ) - assert len(result.policy_breaks) == final_len - - @pytest.mark.parametrize( "input_match, expected_value", [ diff --git a/tests/unit/verticals/secret/output/snapshots/snap_test_text_output.py b/tests/unit/verticals/secret/output/snapshots/snap_test_text_output.py index ca461b184c..5b92944e65 100644 --- a/tests/unit/verticals/secret/output/snapshots/snap_test_text_output.py +++ b/tests/unit/verticals/secret/output/snapshots/snap_test_text_output.py @@ -8,7 +8,7 @@ snapshots[ "test_leak_message[_MULTI_SECRET_ONE_LINE_PATCH_OVERLAY_SCAN_RESULT-clip_long_lines-hide_secrets] 1" ] = """> This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -26,7 +26,7 @@ snapshots[ "test_leak_message[_MULTI_SECRET_ONE_LINE_PATCH_OVERLAY_SCAN_RESULT-clip_long_lines-show_secrets] 1" ] = """> This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -46,7 +46,7 @@ ] = """ secrets-engine-version: 3.14.159 > This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -66,7 +66,7 @@ ] = """ secrets-engine-version: 3.14.159 > This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -84,7 +84,7 @@ snapshots[ "test_leak_message[_MULTI_SECRET_ONE_LINE_PATCH_SCAN_RESULT-clip_long_lines-hide_secrets] 1" ] = """> This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -102,7 +102,7 @@ snapshots[ "test_leak_message[_MULTI_SECRET_ONE_LINE_PATCH_SCAN_RESULT-clip_long_lines-show_secrets] 1" ] = """> This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -122,7 +122,7 @@ ] = """ secrets-engine-version: 3.14.159 > This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -142,7 +142,7 @@ ] = """ secrets-engine-version: 3.14.159 > This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -160,7 +160,7 @@ snapshots[ "test_leak_message[_MULTI_SECRET_TWO_LINES_PATCH_SCAN_RESULT-clip_long_lines-hide_secrets] 1" ] = """> This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -178,7 +178,7 @@ snapshots[ "test_leak_message[_MULTI_SECRET_TWO_LINES_PATCH_SCAN_RESULT-clip_long_lines-show_secrets] 1" ] = """> This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -198,7 +198,7 @@ ] = """ secrets-engine-version: 3.14.159 > This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -218,7 +218,7 @@ ] = """ secrets-engine-version: 3.14.159 > This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -236,7 +236,7 @@ snapshots[ "test_leak_message[_ONE_LINE_AND_MULTILINE_PATCH_CONTENT-clip_long_lines-hide_secrets] 1" ] = """> This is an example header -> leak.txt: 3 incidents detected +> leak.txt: 3 secrets detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -285,7 +285,7 @@ snapshots[ "test_leak_message[_ONE_LINE_AND_MULTILINE_PATCH_CONTENT-clip_long_lines-show_secrets] 1" ] = """> This is an example header -> leak.txt: 3 incidents detected +> leak.txt: 3 secrets detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -336,7 +336,7 @@ ] = """ secrets-engine-version: 3.14.159 > This is an example header -> leak.txt: 3 incidents detected +> leak.txt: 3 secrets detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -387,7 +387,7 @@ ] = """ secrets-engine-version: 3.14.159 > This is an example header -> leak.txt: 3 incidents detected +> leak.txt: 3 secrets detected >> Secret detected: Facebook Access Tokens Occurrences: 1 @@ -436,7 +436,7 @@ snapshots[ "test_leak_message[_SIMPLE_SECRET_MULTILINE_PATCH_SCAN_RESULT-clip_long_lines-hide_secrets] 1" ] = """> This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: RSA Private Key Occurrences: 1 @@ -461,7 +461,7 @@ snapshots[ "test_leak_message[_SIMPLE_SECRET_MULTILINE_PATCH_SCAN_RESULT-clip_long_lines-show_secrets] 1" ] = """> This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: RSA Private Key Occurrences: 1 @@ -488,7 +488,7 @@ ] = """ secrets-engine-version: 3.14.159 > This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: RSA Private Key Occurrences: 1 @@ -515,7 +515,7 @@ ] = """ secrets-engine-version: 3.14.159 > This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: RSA Private Key Occurrences: 1 @@ -540,7 +540,7 @@ snapshots[ "test_leak_message[_SIMPLE_SECRET_PATCH_SCAN_RESULT-clip_long_lines-hide_secrets] 1" ] = """> This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: GitHub Token Occurrences: 1 @@ -556,7 +556,7 @@ snapshots[ "test_leak_message[_SIMPLE_SECRET_PATCH_SCAN_RESULT-clip_long_lines-show_secrets] 1" ] = """> This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: GitHub Token Occurrences: 1 @@ -574,7 +574,7 @@ ] = """ secrets-engine-version: 3.14.159 > This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: GitHub Token Occurrences: 1 @@ -592,7 +592,7 @@ ] = """ secrets-engine-version: 3.14.159 > This is an example header -> leak.txt: 1 incident detected +> leak.txt: 1 secret detected >> Secret detected: GitHub Token Occurrences: 1 diff --git a/tests/unit/verticals/secret/output/test_json_output.py b/tests/unit/verticals/secret/output/test_json_output.py index 26a1041f75..9536c165dd 100644 --- a/tests/unit/verticals/secret/output/test_json_output.py +++ b/tests/unit/verticals/secret/output/test_json_output.py @@ -342,8 +342,18 @@ def check_occurrences_indices( ("one_line_and_multiline_patch", _ONE_LINE_AND_MULTILINE_PATCH, 1, True), ("no_secret", _NO_SECRET_PATCH, 0, True), ("single_add", _SINGLE_ADD_PATCH, 1, True), - ("single_delete", _SINGLE_DELETE_PATCH, 1, True), - ("single_move", _SINGLE_MOVE_PATCH, 1, True), + ( + "single_delete", + _SINGLE_DELETE_PATCH, + 0, + True, + ), # no issue because secret is removed + ( + "single_move", + _SINGLE_MOVE_PATCH, + 0, + True, + ), # no issue because secret is not added ("multiline_secret", _MULTILINE_SECRET_FILE, 1, False), ("single_line_secret", _SINGLE_LINE_SECRET_FILE, 1, False), ("one_line_and_multiline_secrets", _ONE_LINE_AND_MULTILINE_FILE, 1, False), @@ -438,13 +448,14 @@ def test_ignore_known_secrets(verbose, ignore_known_secrets, secrets_types): verbose=verbose, secret_config=secret_config ) - result: Result = Result( + result: Result = Result.from_scan_result( StringScannable( content=_ONE_LINE_AND_MULTILINE_PATCH_CONTENT, url="leak.txt", filemode=Filemode.NEW, ), - scan=deepcopy(TWO_POLICY_BREAKS), # 2 policy breaks + scan_result=deepcopy(TWO_POLICY_BREAKS), + secret_config=SecretConfig(), # 2 policy breaks ) all_policy_breaks = result.policy_breaks @@ -531,13 +542,14 @@ def test_with_incident_details( verbose=True, secret_config=secret_config, client=client_mock ) - result: Result = Result( + result: Result = Result.from_scan_result( StringScannable( content=_ONE_LINE_AND_MULTILINE_PATCH_CONTENT, url="leak.txt", filemode=Filemode.NEW, ), - scan=deepcopy(TWO_POLICY_BREAKS), # 2 policy breaks + scan_result=deepcopy(TWO_POLICY_BREAKS), + secret_config=SecretConfig(), # 2 policy breaks ) all_policy_breaks = result.policy_breaks diff --git a/tests/unit/verticals/secret/output/test_sarif_output.py b/tests/unit/verticals/secret/output/test_sarif_output.py index fb7e404e93..e1dc8ea2b5 100644 --- a/tests/unit/verticals/secret/output/test_sarif_output.py +++ b/tests/unit/verticals/secret/output/test_sarif_output.py @@ -192,7 +192,9 @@ def test_sarif_output_for_flat_scan_with_secrets( policy_break.known_secret = False policy_break.incident_url = None - result = Result(file=scannable, scan=scan_result) + result = Result.from_scan_result( + file=scannable, scan_result=scan_result, secret_config=SecretConfig() + ) results = Results(results=[result]) scan = SecretScanCollection(id="path", type="test", results=results) @@ -248,7 +250,9 @@ def test_sarif_output_for_nested_scan(init_secrets_engine_version): scannable = next(commit.get_files()) contents.append(scannable.content) - result = Result(file=scannable, scan=scan_result) + result = Result.from_scan_result( + file=scannable, scan_result=scan_result, secret_config=SecretConfig() + ) results = Results(results=[result]) scan = SecretScanCollection(id=f"nested{idx}", type="test", results=results) nested_scans.append(scan) diff --git a/tests/unit/verticals/secret/output/test_text_output.py b/tests/unit/verticals/secret/output/test_text_output.py index 07b322ac58..bbf9d1825f 100644 --- a/tests/unit/verticals/secret/output/test_text_output.py +++ b/tests/unit/verticals/secret/output/test_text_output.py @@ -41,68 +41,74 @@ "result_input", [ pytest.param( - Result( + Result.from_scan_result( StringScannable( content=_SIMPLE_SECRET_PATCH, url="leak.txt", filemode=Filemode.NEW, ), - scan=_SIMPLE_SECRET_PATCH_SCAN_RESULT, + scan_result=_SIMPLE_SECRET_PATCH_SCAN_RESULT, + secret_config=SecretConfig(), ), id="_SIMPLE_SECRET_PATCH_SCAN_RESULT", ), pytest.param( - Result( + Result.from_scan_result( StringScannable( content=_MULTI_SECRET_ONE_LINE_PATCH, url="leak.txt", filemode=Filemode.NEW, ), - scan=_MULTI_SECRET_ONE_LINE_PATCH_SCAN_RESULT, + scan_result=_MULTI_SECRET_ONE_LINE_PATCH_SCAN_RESULT, + secret_config=SecretConfig(), ), id="_MULTI_SECRET_ONE_LINE_PATCH_SCAN_RESULT", ), pytest.param( - Result( + Result.from_scan_result( StringScannable( content=_MULTI_SECRET_ONE_LINE_PATCH_OVERLAY, url="leak.txt", filemode=Filemode.NEW, ), - scan=_MULTI_SECRET_ONE_LINE_PATCH_OVERLAY_SCAN_RESULT, + scan_result=_MULTI_SECRET_ONE_LINE_PATCH_OVERLAY_SCAN_RESULT, + secret_config=SecretConfig(), ), id="_MULTI_SECRET_ONE_LINE_PATCH_OVERLAY_SCAN_RESULT", ), pytest.param( - Result( + Result.from_scan_result( StringScannable( content=_MULTI_SECRET_TWO_LINES_PATCH, url="leak.txt", filemode=Filemode.NEW, ), - scan=_MULTI_SECRET_TWO_LINES_PATCH_SCAN_RESULT, + scan_result=_MULTI_SECRET_TWO_LINES_PATCH_SCAN_RESULT, + secret_config=SecretConfig(), ), id="_MULTI_SECRET_TWO_LINES_PATCH_SCAN_RESULT", ), pytest.param( - Result( + Result.from_scan_result( StringScannable( content=_SIMPLE_SECRET_MULTILINE_PATCH, url="leak.txt", filemode=Filemode.NEW, ), - scan=_SIMPLE_SECRET_MULTILINE_PATCH_SCAN_RESULT, + scan_result=_SIMPLE_SECRET_MULTILINE_PATCH_SCAN_RESULT, + secret_config=SecretConfig(), ), id="_SIMPLE_SECRET_MULTILINE_PATCH_SCAN_RESULT", ), pytest.param( - Result( + Result.from_scan_result( StringScannable( content=_ONE_LINE_AND_MULTILINE_PATCH_CONTENT, url="leak.txt", filemode=Filemode.NEW, ), - scan=_ONE_LINE_AND_MULTILINE_PATCH_SCAN_RESULT, + scan_result=_ONE_LINE_AND_MULTILINE_PATCH_SCAN_RESULT, + secret_config=SecretConfig(), ), id="_ONE_LINE_AND_MULTILINE_PATCH_CONTENT", ), diff --git a/tests/unit/verticals/secret/test_scan_repo.py b/tests/unit/verticals/secret/test_scan_repo.py index 9f94be9a11..862d37653d 100644 --- a/tests/unit/verticals/secret/test_scan_repo.py +++ b/tests/unit/verticals/secret/test_scan_repo.py @@ -121,13 +121,15 @@ def test_scan_2_commits_same_content(secret_scanner_mock): secret_scanner_mock.return_value.scan.return_value = Results( results=[ - Result( + Result.from_scan_result( commit_1_files[0], - scan=deepcopy(TWO_POLICY_BREAKS), + scan_result=deepcopy(TWO_POLICY_BREAKS), + secret_config=SecretConfig(), ), - Result( + Result.from_scan_result( commit_2_files[0], - scan=deepcopy(TWO_POLICY_BREAKS), + scan_result=deepcopy(TWO_POLICY_BREAKS), + secret_config=SecretConfig(), ), ], errors=[], @@ -206,17 +208,20 @@ def test_scan_2_commits_file_association(secret_scanner_mock): policy_breaks_file_2_1 = deepcopy(TWO_POLICY_BREAKS) secret_scanner_mock.return_value.scan.return_value = Results( results=[ - Result( + Result.from_scan_result( file1_3, - scan=policy_breaks_file_1_3, + scan_result=policy_breaks_file_1_3, + secret_config=SecretConfig(), ), - Result( + Result.from_scan_result( file2_1, - scan=policy_breaks_file_2_1, + scan_result=policy_breaks_file_2_1, + secret_config=SecretConfig(), ), - Result( + Result.from_scan_result( file1_1, - scan=policy_breaks_file_1_1, + scan_result=policy_breaks_file_1_1, + secret_config=SecretConfig(), ), ], errors=[], @@ -239,13 +244,19 @@ def test_scan_2_commits_file_association(secret_scanner_mock): scan_collection.scans[0].results.results, key=lambda f: f.filename ) == sorted( [ - Result(file1_3, policy_breaks_file_1_3), - Result(file1_1, policy_breaks_file_1_1), + Result.from_scan_result( + file1_3, policy_breaks_file_1_3, secret_config=SecretConfig() + ), + Result.from_scan_result( + file1_1, policy_breaks_file_1_1, secret_config=SecretConfig() + ), ], key=lambda f: f.filename, ) # out of 2 files, only 1 result was returned assert scan_collection.scans[1].results.results == [ - Result(file2_1, policy_breaks_file_2_1), + Result.from_scan_result( + file2_1, policy_breaks_file_2_1, secret_config=SecretConfig() + ), ] diff --git a/tests/unit/verticals/secret/test_secret_scan_collection.py b/tests/unit/verticals/secret/test_secret_scan_collection.py index daefc72ff8..e172e32558 100644 --- a/tests/unit/verticals/secret/test_secret_scan_collection.py +++ b/tests/unit/verticals/secret/test_secret_scan_collection.py @@ -1,4 +1,25 @@ +from collections.abc import Iterable + +import pytest +from pygitguardian.models import ScanResult + +from ggshield.core.config.user_config import SecretConfig +from ggshield.core.filter import get_ignore_sha +from ggshield.core.scan import StringScannable +from ggshield.core.types import IgnoredMatch from ggshield.verticals.secret import Results +from ggshield.verticals.secret.secret_scan_collection import ( + IgnoreReason, + Result, + compute_ignore_reason, +) +from tests.factories import PolicyBreakFactory, ScannableFactory, ScanResultFactory +from tests.unit.conftest import ( + _ONE_LINE_AND_MULTILINE_PATCH_CONTENT, + _ONE_LINE_AND_MULTILINE_PATCH_SCAN_RESULT, + _SIMPLE_SECRET_PATCH, + _SIMPLE_SECRET_PATCH_SCAN_RESULT, +) class MyException(Exception): @@ -19,3 +40,147 @@ def test_results_from_exception(): assert error.description == "MyException: Hello" assert results.results == [] + + +@pytest.mark.parametrize( + ("content", "scan_result", "ignores", "final_len"), + [ + pytest.param( + _SIMPLE_SECRET_PATCH, + _SIMPLE_SECRET_PATCH_SCAN_RESULT, + [], + _SIMPLE_SECRET_PATCH_SCAN_RESULT.policy_break_count, + id="_SIMPLE_SECRET_PATCH_SCAN_RESULT-no remove, not all policies", + ), + pytest.param( + _SIMPLE_SECRET_PATCH, + _SIMPLE_SECRET_PATCH_SCAN_RESULT, + ["2b5840babacb6f089ddcce1fe5a56b803f8b1f636c6f44cdbf14b0c77a194c93"], + 0, + id="_SIMPLE_SECRET_PATCH_SCAN_RESULT-remove by sha", + ), + pytest.param( + _SIMPLE_SECRET_PATCH, + _SIMPLE_SECRET_PATCH_SCAN_RESULT, + ["368ac3edf9e850d1c0ff9d6c526496f8237ddf91"], + 0, + id="_SIMPLE_SECRET_PATCH_SCAN_RESULT-remove by plaintext", + ), + pytest.param( + _ONE_LINE_AND_MULTILINE_PATCH_CONTENT, + _ONE_LINE_AND_MULTILINE_PATCH_SCAN_RESULT, + ["1945f4a0c42abb19c1a420ddd09b4b4681249a3057c427b95f794b18595e7ffa"], + 2, + id="_MULTI_SECRET_ONE_LINE_PATCH_SCAN_RESULT-remove one by sha", + ), + pytest.param( + _ONE_LINE_AND_MULTILINE_PATCH_CONTENT, + _ONE_LINE_AND_MULTILINE_PATCH_SCAN_RESULT, + [ + "060bf63de122848f5efa122fe6cea504aae3b24cea393d887fdefa1529c6a02e", + "ce3f9f0362bbe5ab01dfc8ee565e4371", + ], + 1, + id="_MULTI_SECRET_ONE_LINE_PATCH_SCAN_RESULT-remove two by mix", + ), + ], +) +def test_create_result_removes_ignored_matches( + content: str, scan_result: ScanResult, ignores: Iterable, final_len: int +) -> None: + result = Result.from_scan_result( + file=StringScannable(url="localhost", content=content), + scan_result=scan_result, + secret_config=SecretConfig( + ignored_matches=[IgnoredMatch(name="", match=x) for x in ignores] + ), + ) + assert len(result.policy_breaks) == final_len + + +@pytest.mark.parametrize("all_secrets", (True, False)) +def test_create_result_removes_ignored_matches_bis(all_secrets): + """ + GIVEN two different policy breaks + WHEN ignoring the first one + THEN it is ignored iff all_secrets is false + + Note: this test could replace the one above + """ + scannable = ScannableFactory() + policy_breaks = PolicyBreakFactory.create_batch(2, content=scannable.content) + + # ensure policy breaks are different + if policy_breaks[0].matches[0].match_type == policy_breaks[1].matches[0].match_type: + policy_breaks[0].matches[0].match_type += "a" + + config = SecretConfig( + ignored_matches=[ + IgnoredMatch(name="x", match=get_ignore_sha(policy_breaks[0])) + ], + all_secrets=all_secrets, + ) + result = Result.from_scan_result( + scannable, ScanResultFactory(policy_breaks=policy_breaks), config + ) + if all_secrets: + assert len(result.policy_breaks) == 2 + assert result.policy_breaks[0].is_excluded is True + assert result.policy_breaks[1].is_excluded is False + else: + assert len(result.policy_breaks) == 1 + assert result.policy_breaks[0].is_excluded is False + assert ( + result.ignored_policy_breaks_count_by_reason[IgnoreReason.IGNORED_MATCH] + == 1 + ) + + +class TestComputeIgnoreReason: + def test_ignore_excluded(self): + """ + GIVEN an policy break excluded from the backend + WHEN computing the ignore reason + THEN it contains the original exclusion reason (and is not None) + """ + policy_break = PolicyBreakFactory( + is_excluded=True, exclude_reason="BACKEND_REASON" + ) + assert "BACKEND_REASON" in compute_ignore_reason(policy_break, SecretConfig()) + + def test_ignore_ignored_match(self): + """ + GIVEN an policy break matching an ignored sha in config + WHEN computing the ignore reason + THEN it's not None + """ + policy_break = PolicyBreakFactory() + config = SecretConfig( + ignored_matches=[ + IgnoredMatch(name="x", match=get_ignore_sha(policy_break)) + ], + ) + assert compute_ignore_reason(policy_break, config) is not None + + def test_ignore_ignored_detector(self): + """ + GIVEN a policy break matching an ignored detector in config + WHEN computing the ignore reason + THEN it's not None + """ + policy_break = PolicyBreakFactory() + config = SecretConfig( + ignored_detectors=[policy_break.break_type], + ) + assert compute_ignore_reason(policy_break, config) is not None + + @pytest.mark.parametrize("ignore_known", (True, False)) + def test_known_secret(self, ignore_known): + """ + GIVEN a known policy break + WHEN computing the ignore reason + THEN it's not None iff ignore_secret is enabled in config + """ + policy_break = PolicyBreakFactory(known_secret=True) + config = SecretConfig(ignore_known_secrets=ignore_known) + assert bool(compute_ignore_reason(policy_break, config)) is ignore_known diff --git a/tests/unit/verticals/secret/test_secret_scanner.py b/tests/unit/verticals/secret/test_secret_scanner.py index ef1cb978d9..4aa8304e66 100644 --- a/tests/unit/verticals/secret/test_secret_scanner.py +++ b/tests/unit/verticals/secret/test_secret_scanner.py @@ -48,10 +48,12 @@ ) -ExpectedScan = namedtuple("expectedScan", "exit_code matches first_match want") +ExpectedScan = namedtuple( + "expectedScan", ("exit_code", "matches", "first_match", "want") +) _EXPECT_NO_SECRET = { "content": "@@ -0,0 +1 @@\n+this is a patch without secret\n", - "filename": "test.txt", + "filename": "commit://patch/test", "filemode": Filemode.NEW, } @@ -123,7 +125,6 @@ def test_scan_patch(client, cache, name: str, input_patch: str, expected: Expect assert result.policy_breaks == [] if expected.want: - assert result.content == expected.want["content"] assert result.filename == expected.want["filename"] assert result.filemode == expected.want["filemode"] @@ -332,8 +333,9 @@ def test_request_headers(scan_mock: Mock, client): "GGShield-OS-Version": os_version, "GGShield-Python-Version": platform.python_version(), "mode": "path", + "scan_options": ANY, }, - ignore_known_secrets=True, + all_secrets=True, ) @@ -427,3 +429,57 @@ def test_scan_unexpected_error(scan_mock: Mock, client): ) with pytest.raises(UnexpectedError, match="Scanning failed.*"): scanner.scan([scannable], scanner_ui=Mock()) + + +@patch("pygitguardian.GGClient.multi_content_scan") +def test_all_secrets_is_used(scan_mock: Mock, client): + """ + GIVEN one secret ignored in backend, and one not ignored + WHEN calling scanner.scan with the all_secrets option set to False + THEN secrets excluded by the backend are ignored + """ + scannable = StringScannable(url="localhost", content="known\nunknown") + matches = [ + Match( + match="known", + match_type="apikey", + line_start=0, + line_end=0, + index_start=0, + index_end=1, + ) + ] + secret = PolicyBreak( + break_type="not-excluded", + policy="Secrets detection", + validity="valid", + matches=matches, + ) + excluded_secret = PolicyBreak( + break_type="excluded", + policy="Secrets detection", + validity="valid", + matches=matches, + is_excluded=True, + exclude_reason="dummy", + ) + + scan_result = ScanResult( + policy_break_count=1, policy_breaks=[secret, excluded_secret], policies=[] + ) + multi_scan_result = MultiScanResult([scan_result]) + multi_scan_result.status_code = 200 + scan_mock.return_value = multi_scan_result + + scanner = SecretScanner( + client=client, + cache=Cache(), + scan_context=ScanContext( + scan_mode=ScanMode.PATH, + command_path="ggshield", + ), + check_api_key=False, + secret_config=SecretConfig(), + ) + results = scanner.scan([scannable], scanner_ui=Mock()) + assert results.results[0].policy_breaks == [secret]