$value ) { if ( self::is_secret_key( $key ) && self::is_revealing( $value ) ) { $data[ $key ] = self::MASK; continue; } $data[ $key ] = self::redact( $value ); } return $data; } /** * A secret-looking name in source text: any name containing password, passwd, passphrase, secret, token, salt, * credential or an api/private/access/auth/license/secure/encryption/signing key (any case), or an upper-case * constant ending in `_KEY`. Lower-case `meta_key` or `cache_key` are ordinary code and are kept. */ const TEXT_NAME = '(? $quoted( 3 ), // A quoted value after a secret-looking name and =, => or : (PHP variables and array keys, JSON, JS, YAML). '/((?:[\'"])?' . self::TEXT_NAME . '[\'"]?\s*(?:=>|=|:)\s*)(' . self::TEXT_QUOTED . ')/' => $quoted( 2 ), // NAME=value and NAME: value lines (env, ini, YAML), unquoted. '/^([ \t]*(?:export[ \t]+)?' . self::TEXT_NAME . '[ \t]*[:=][ \t]*)(?![\'"\s])[^\r\n]+/m' => '$1' . self::MASK, '/-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----.*?(?:-----END [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----|\z)/s' => self::MASK, '/\b([a-z][a-z0-9+.\-]*:\/\/)[^\s\/@:]+:[^\s\/@]+@/i' => '$1' . self::MASK . '@', '/\b(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{8,}/' => self::MASK, '/\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})/' => self::MASK, '/\bAKIA[0-9A-Z]{16}\b/' => self::MASK, '/\beyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+/' => self::MASK, '/\b(Authorization[ \t]*:[ \t]*)[^\r\n]+/i' => '$1' . self::MASK, '/\b(Bearer[ \t]+)[A-Za-z0-9+\/_.=~\-]{8,}/' => '$1' . self::MASK, ); foreach ( $rules as $pattern => $replacement ) { $text = is_string( $replacement ) ? preg_replace( $pattern, $replacement, $text ) : preg_replace_callback( $pattern, $replacement, $text ); if ( null === $text ) { return null; } } return $text; } /** * Whether a value could reveal a secret. * * @param mixed $value Value. * @return bool */ private static function is_revealing( $value ): bool { if ( is_string( $value ) ) { return '' !== $value; } return is_array( $value ) || is_object( $value ); } }