Skip to content

Commit 317e736

Browse files
committed
Fix false-positive policy failures by ignoring templated DB URL credentials in regex/context detectors
1 parent 3c1cc98 commit 317e736

File tree

4 files changed

+51
-0
lines changed

4 files changed

+51
-0
lines changed

src/leaklens/detectors/context.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ def scan_line(self, file_path: str, line_number: int, line: str) -> list[Detecti
4747

4848
for match in CONNECTION_STRING_PATTERN.finditer(line):
4949
value = match.group(0)
50+
if _is_templated_connection_string(value):
51+
continue
5052
hits.append(
5153
DetectionMatch(
5254
finding_type="Connection String Credential",
@@ -233,6 +235,19 @@ def _should_skip_assignment_value(value: str) -> bool:
233235
return False
234236

235237

238+
def _is_templated_connection_string(value: str) -> bool:
239+
lowered = value.lower()
240+
if "${" in value or "{{" in value or "{" in value or "}" in value:
241+
return True
242+
if "<" in value or ">" in value or "..." in value:
243+
return True
244+
if "os.getenv(" in lowered or "process.env" in lowered:
245+
return True
246+
if "example" in lowered or "placeholder" in lowered:
247+
return True
248+
return False
249+
250+
236251
def _looks_secret_like_literal(value: str) -> bool:
237252
if len(value) < 16:
238253
return False

src/leaklens/detectors/regex.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ def _passes_rule_heuristics(rule_name: str, value: str, line: str, line_lower: s
175175
marker = "-----BEGIN RSA PRIVATE KEY-----"
176176
return _is_unquoted_marker_line(line, marker)
177177

178+
if rule_name == "db_url_with_creds":
179+
return not _is_templated_connection_string(normalized)
180+
178181
return True
179182

180183

@@ -232,6 +235,19 @@ def _looks_like_jwt_header(token: str) -> bool:
232235
return "alg" in payload or "typ" in payload
233236

234237

238+
def _is_templated_connection_string(value: str) -> bool:
239+
lowered = value.lower()
240+
if "${" in value or "{{" in value or "{" in value or "}" in value:
241+
return True
242+
if "<" in value or ">" in value or "..." in value:
243+
return True
244+
if "os.getenv(" in lowered or "process.env" in lowered:
245+
return True
246+
if "example" in lowered or "placeholder" in lowered:
247+
return True
248+
return False
249+
250+
235251
def _decode_base64url(segment: str) -> str | None:
236252
padding = "=" * (-len(segment) % 4)
237253
try:

tests/test_context.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,13 @@ def test_context_detector_ignores_auth_keyword_text_literal() -> None:
3939
matches = detector.scan_line("pyproject.toml", 8, line)
4040

4141
assert not any(match.finding_type == "Auth Context Secret Literal" for match in matches)
42+
43+
44+
def test_context_detector_ignores_templated_connection_strings() -> None:
45+
detector = ContextDetector()
46+
line = (
47+
'autofix = "postgresql://{os.getenv(\'DB_USER\')}:{os.getenv(\'DB_PASSWORD\')}@..."'
48+
)
49+
matches = detector.scan_line("detector.py", 21, line)
50+
51+
assert not any(match.finding_type == "Connection String Credential" for match in matches)

tests/test_regex.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,13 @@ def test_regex_detector_requires_jwt_context_keyword() -> None:
4545
matches = detector.scan_line("app.py", 5, line)
4646

4747
assert not any(match.finding_type == "JWT Token" for match in matches)
48+
49+
50+
def test_regex_detector_ignores_templated_connection_strings() -> None:
51+
detector = RegexDetector(builtin_rules())
52+
line = (
53+
'autofix = "postgresql://{os.getenv(\'DB_USER\')}:{os.getenv(\'DB_PASSWORD\')}@..."'
54+
)
55+
matches = detector.scan_line("detector.py", 6, line)
56+
57+
assert not any(match.finding_type == "Database URL Credentials" for match in matches)

0 commit comments

Comments
 (0)