@@ -146,6 +146,11 @@ def mask_value(value: Any, reveal: int = _DEFAULT_REVEAL,
146146# such as ``# see get_secret()`` disable masking for the value in front of it.
147147_CALL_EXPRESSION = re .compile (r'^[\w.\[\]]+\s*\(' )
148148
149+ # Optional prefixes on a quoted literal. ``r"""`` / ``f"..."`` / ``b'...'``
150+ # still open a value the literal pass can find; the quote, not the prefix,
151+ # is what that pass matches.
152+ _STRING_PREFIX = re .compile (r'^[bBfFrRuU]*' )
153+
149154# Rule-name fragments whose finding *is* the credential. ``hardcoded-ip`` and
150155# the password-policy rules deliberately do not appear: their snippets are
151156# logic, and masking them would remove the reason the finding was raised.
@@ -252,8 +257,16 @@ def _mask_statement(code: str, offset: int, in_literal) -> str:
252257 # nothing to act on. A quoted value is the literal pass's job -- but only
253258 # where the quote opens a literal that pass can find. A snippet cut mid
254259 # string has an opening quote and no closing one, so nothing matches and
255- # the value would survive untouched.
256- opens_literal = value .startswith (('"' , "'" , '`' )) and in_literal (offset + len (head ))
260+ # the value would survive untouched. A prefix such as ``r`` or ``f`` is
261+ # not the quote, but the quote after it still is: starring the opener
262+ # would drop those quotes, later lines would stay marked in-literal, and
263+ # the body would survive the literal pass.
264+ prefix_len = _STRING_PREFIX .match (value ).end ()
265+ opens_literal = (
266+ prefix_len < len (value )
267+ and value [prefix_len ] in '"\' `'
268+ and in_literal (offset + len (head ) + prefix_len )
269+ )
257270 if opens_literal or _CALL_EXPRESSION .match (value ):
258271 return code
259272
@@ -292,10 +305,10 @@ def redact_literals(text: Any) -> str:
292305 finding actionable, while the value does not.
293306
294307 Where the shape of the value is not recognized the whole value is masked
295- rather than guessed at, so a subscript, a ternary or a prefixed literal
296- (``f"..."``, ``r'...'``) loses more of the line than a plain assignment
297- does. That direction is deliberate: the rule ID, file and line still
298- identify the finding, and the alternative is leaving a credential in place.
308+ rather than guessed at, so a subscript or a ternary loses more of the
309+ line than a plain assignment does. That direction is deliberate: the
310+ rule ID, file and line still identify the finding, and the alternative
311+ is leaving a credential in place.
299312 """
300313 if not isinstance (text , str ) or not text :
301314 return text if isinstance (text , str ) else ''
0 commit comments