Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!--
A new scriv changelog fragment.

Uncomment the section that is right (remove the HTML comment wrapper).
-->

<!--
### Removed

- A bullet item for the Removed category.

-->

### Added

- `ggshield secret scan` output now contains a link to the detector documentation for each secret found.

<!--
### Changed

- A bullet item for the Changed category.

-->
<!--
### Deprecated

- A bullet item for the Deprecated category.

-->
<!--
### Fixed

- A bullet item for the Fixed category.

-->
<!--
### Security

- A bullet item for the Security category.

-->
1 change: 1 addition & 0 deletions ggshield/verticals/secret/output/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class FlattenedPolicyBreak(BaseSchema):
policy = fields.String(required=True)
occurrences = fields.List(fields.Nested(ExtendedMatchSchema), required=True)
detector = fields.String(data_key="type", required=True)
detector_documentation = fields.String(required=False, allow_none=True)
validity = fields.String(required=False, allow_none=True)
ignore_sha = fields.String(required=True)
total_occurrences = fields.Integer(required=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def format_secret(secret: Secret) -> str:
f'{x.match_type}: "{censor_match(x)}"' for x in secret.matches
)
validity = translate_validity(secret.validity)
return f"{secret.detector} (Validity: {validity}, {match_str})"
return f"{secret.detector_display_name} (Validity: {validity}, {match_str})"


class SecretGitLabWebUIOutputHandler(SecretOutputHandler):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,13 @@ def serialized_secret(
"occurrences": [],
"ignore_sha": ignore_sha,
"policy": secrets[0].policy,
"detector": secrets[0].detector,
"detector": secrets[0].detector_display_name,
"total_occurrences": len(secrets),
}

if secrets[0].documentation_url:
flattened_dict["detector_documentation"] = secrets[0].documentation_url

if secrets[0].validity:
flattened_dict["validity"] = secrets[0].validity

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,18 @@
f"- [{m.match_type}]({id})" for id, m in enumerate(secret.matches)
)
extended_matches = cast(List[ExtendedMatch], secret.matches)
message = f"Secret detected: {secret.detector}.\nMatches: {matches_str}"
markdown_message = f"Secret detected: {secret.detector}\nMatches:\n{matches_li}"
message = (
f"Secret detected: {secret.detector_display_name}.\nMatches: {matches_str}"
)
if secret.documentation_url:
markdown_message = f"Secret detected: [{secret.detector_display_name}]({secret.documentation_url})"
else:
markdown_message = f"Secret detected: {secret.detector_display_name}"

Check warning on line 85 in ggshield/verticals/secret/output/secret_sarif_output_handler.py

View check run for this annotation

Codecov / codecov/patch

ggshield/verticals/secret/output/secret_sarif_output_handler.py#L85

Added line #L85 was not covered by tests
markdown_message += f"\nMatches:\n{matches_li}"

# Create dict
dct = {
"ruleId": secret.detector,
"ruleId": secret.detector_display_name,
"level": "error",
"message": {
"text": message,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ def secret_header(
)

start_line = format_text(">>", STYLE["detector_line_start"])
secret_type = format_text(secret.detector, STYLE["secret_type"])
secret_type = format_text(secret.detector_display_name, STYLE["secret_type"])
number_occurrences = format_text(str(len(secrets)), STYLE["occurrence_count"])
ignore_sha = format_text(ignore_sha, STYLE["ignore_sha"])

Expand All @@ -308,6 +308,8 @@ def secret_header(
{indent}Incident URL: {secrets[0].incident_url if known_secret and secret.incident_url else "N/A"}
{indent}Secret SHA: {ignore_sha}
"""
if secret.documentation_url is not None:
message += f"{indent}Detector documentation: {secret.documentation_url}\n"
if secret.ignore_reason is not None:
message += f"{indent}Ignored: {secret.ignore_reason.to_human_readable()}\n"

Expand Down
10 changes: 8 additions & 2 deletions ggshield/verticals/secret/secret_scan_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ class Secret:
Named Secret since we are dropping other kind of policy breaks.
"""

detector: str
detector_display_name: str
detector_name: str
detector_group_name: str
documentation_url: Optional[str]
validity: str
known_secret: bool
incident_url: Optional[str]
Expand Down Expand Up @@ -186,7 +189,10 @@ def from_scan_result(
validity=policy_break.validity,
known_secret=policy_break.known_secret,
incident_url=policy_break.incident_url,
detector=policy_break.break_type,
detector_display_name=policy_break.break_type,
detector_name=policy_break.detector_name,
detector_group_name=policy_break.detector_group_name,
documentation_url=policy_break.documentation_url,
matches=[
ExtendedMatch.from_match(match, lines, result.is_on_patch)
for match in policy_break.matches
Expand Down
2 changes: 1 addition & 1 deletion ggshield/verticals/secret/secret_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ def _collect_results(
result = Result.from_scan_result(file, scan_result, self.secret_config)
for secret in result.secrets:
self.cache.add_found_policy_break(
secret.detector,
secret.detector_display_name,
secret.get_ignore_sha(),
file.filename,
)
Expand Down
9 changes: 4 additions & 5 deletions pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ dependencies = [
"marshmallow~=3.18.0",
"marshmallow-dataclass~=8.5.8",
"oauthlib~=3.2.1",
"pygitguardian~=1.20.0",
"pygitguardian @ git+https://github.com/GitGuardian/py-gitguardian.git@933318c6a3fc7a5cce1fdbb663b4e90a2b7992a2",
"pyjwt~=2.6.0",
"python-dotenv~=0.21.0",
"pyyaml~=6.0.1",
Expand Down
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ inline-quotes = double
max-line-length = 120
ignore = E203, E704, W503
exclude = **/snapshots/*.py, .venv, build
per-file-ignores =
tests/unit/conftest.py: E501
10 changes: 9 additions & 1 deletion tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ class Meta:

break_type = factory.lazy_attribute(lambda obj: random.choice(DETECTOR_NAMES))
policy = "Secrets detection"
detector_name = factory.lazy_attribute(lambda obj: obj.break_type)
detector_group_name = factory.lazy_attribute(lambda obj: obj.break_type)
documentation_url = None
validity = "valid"
known_secret = False
incident_url = None
Expand Down Expand Up @@ -94,7 +97,12 @@ class SecretFactory(factory.Factory):
class Meta:
model = Secret

detector = factory.lazy_attribute(lambda obj: random.choice(DETECTOR_NAMES))
detector_display_name = factory.lazy_attribute(
lambda obj: random.choice(DETECTOR_NAMES)
)
detector_name = factory.lazy_attribute(lambda obj: obj.detector_display_name)
detector_group_name = factory.lazy_attribute(lambda obj: obj.detector_display_name)
documentation_url = None
validity = "valid"
known_secret = True
incident_url = None
Expand Down
Loading