Skip to content

Commit aa2f330

Browse files
committed
fix(redaction): keep backslash-continued string spans
A " or ' match that wraps with \ is a real literal, not a pairing artifact. Dropping it starred the first-line value, including the opener, so a credential on the continuation line survived masking.
1 parent 6626bec commit aa2f330

2 files changed

Lines changed: 24 additions & 2 deletions

File tree

socket_basics/core/utils/redaction.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ def mask_value(value: Any, reveal: int = _DEFAULT_REVEAL,
170170
# Triple-quote delimiters, counted to detect a block that never closes.
171171
_TRIPLE_QUOTE = re.compile(r'\"\"\"|\'\'\'')
172172

173+
# A newline that is not a backslash continuation. Distinguishes a pairing
174+
# artifact (an unclosed ``"`` / ``'`` that matched a stray quote later) from a
175+
# legitimate line-continued string, which most bundled languages allow.
176+
_UNESCAPED_NEWLINE = re.compile(r'(?<!\\)(?:\\\\)*\n')
177+
173178
# Rule-name fragments whose finding *is* the credential. ``hardcoded-ip`` and
174179
# the password-policy rules deliberately do not appear: their snippets are
175180
# logic, and masking them would remove the reason the finding was raised.
@@ -355,12 +360,15 @@ def _mask_literal(match: 're.Match[str]') -> str:
355360
# rules cover, so a match that does is not a literal -- it is an unclosed
356361
# quote that paired with a stray one further down, and the span between them
357362
# would hide whatever it covers, including a real assignment on a later
358-
# line. Backticks and triple quotes span lines legitimately and are kept.
363+
# line. A backslash-escaped newline is line continuation, which those
364+
# languages do allow; OpenGrep's ``extra.lines`` includes every line of
365+
# that match, so the span has to stay or the continuation is never masked.
366+
# Backticks and triple quotes span lines without escaping and are kept.
359367
spans = [
360368
match.span() for match in _STRING_LITERAL.finditer(text)
361369
if len(match.group('quote')) > 1
362370
or match.group('quote') == '`'
363-
or '\n' not in match.group(0)
371+
or not _UNESCAPED_NEWLINE.search(match.group(0))
364372
]
365373

366374
def in_literal(position: int) -> bool:

tests/test_secret_redaction.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,20 @@ def test_an_unterminated_literal_is_masked_rather_than_deferred(self):
316316
for snippet in ('password = "hunter2\n# broken', "password = 'hunter2\n-- sql"):
317317
assert "hunter2" not in redact_literals(snippet), snippet
318318

319+
def test_a_backslash_continued_literal_is_masked(self):
320+
"""A ``\\`` newline is a real literal, not a pairing artifact.
321+
322+
Dropping the span stars the first-line value, including the opener,
323+
so the masking pass cannot see the rest of the string. A credential
324+
on the continuation line would then survive.
325+
"""
326+
for snippet in (
327+
'password = "\\\nhunter2"',
328+
"password = 'first\\\nhunter2'",
329+
'password = r"\\\nhunter2"',
330+
):
331+
assert "hunter2" not in redact_literals(snippet), snippet
332+
319333
def test_a_literal_spanning_lines_keeps_the_line_structure(self):
320334
redacted = redact_literals(self.TRIPLE_DOUBLE)
321335
assert redacted.count("\n") == 2

0 commit comments

Comments
 (0)