@@ -459,25 +459,60 @@ def _print_ci_secrets_masks(
459459 _print_ci_secrets_masks_for_config (config = config_dict )
460460
461461
462+ def _print_ci_secret_mask_for_string (secret : str ) -> None :
463+ """Print GitHub CI mask for a single secret string.
464+
465+ We expect single-line secrets, but we also handle the case where the secret contains newlines.
466+ For multi-line secrets, we must print a secret mask for each line separately.
467+ """
468+ for line in secret .splitlines ():
469+ if line .strip (): # Skip empty lines
470+ print (f"::add-mask::{ line !s} " )
471+
472+
473+ def _print_ci_secret_mask_for_value (value : Any ) -> None :
474+ """Print GitHub CI mask for a single secret value.
475+
476+ Call this function for any values identified as secrets, regardless of type.
477+ """
478+ if isinstance (value , dict ):
479+ # For nested dicts, we call recursively on each value
480+ for v in value .values ():
481+ _print_ci_secret_mask_for_value (v )
482+
483+ return
484+
485+ if isinstance (value , list ):
486+ # For lists, we call recursively on each list item
487+ for list_item in value :
488+ _print_ci_secret_mask_for_value (list_item )
489+
490+ return
491+
492+ # For any other types besides dict and list, we convert to string and mask each line
493+ # separately to handle multi-line secrets (e.g. private keys).
494+ for line in str (value ).splitlines ():
495+ if line .strip (): # Skip empty lines
496+ _print_ci_secret_mask_for_string (line )
497+
498+
462499def _print_ci_secrets_masks_for_config (
463500 config : dict [str , str ] | list [Any ] | Any ,
464501) -> None :
465502 """Print GitHub CI mask for secrets config, navigating child nodes recursively."""
466503 if isinstance (config , list ):
504+ # Check each item in the list to look for nested dicts that may contain secrets:
467505 for item in config :
468506 _print_ci_secrets_masks_for_config (item )
469507
470- if isinstance (config , dict ):
508+ elif isinstance (config , dict ):
471509 for key , value in config .items ():
472510 if _is_secret_property (key ):
473511 logger .debug (f"Masking secret for config key: { key } " )
474- print (f"::add-mask::{ value !s} " )
475- if isinstance (value , dict ):
476- # For nested dicts, we also need to mask the json-stringified version
477- print (f"::add-mask::{ json .dumps (value )!s} " )
478-
479- if isinstance (value , (dict , list )):
480- _print_ci_secrets_masks_for_config (config = value )
512+ _print_ci_secret_mask_for_value (value )
513+ elif isinstance (value , (dict , list )):
514+ # Recursively check nested dicts and lists
515+ _print_ci_secrets_masks_for_config (value )
481516
482517
483518def _is_secret_property (property_name : str ) -> bool :
0 commit comments