> */ private array $mask_log = array(); /** * Processes (sanitizes + masks) a validated workflow data array. * * @param array $data Validated workflow data from WorkflowValidator. * @return array{ data: array, mask_log: array } Processed data and mask log. */ public function process( array $data ): array { $this->mask_log = array(); // Pass 1: Sanitize all strings. $sanitized = $this->sanitize_recursive( $data ); // Pass 2: Mask secrets in nodes. if ( isset( $sanitized['nodes'] ) && is_array( $sanitized['nodes'] ) ) { foreach ( $sanitized['nodes'] as &$node ) { $node = $this->mask_node( $node ); } unset( $node ); } return array( 'data' => $sanitized, 'mask_log' => $this->mask_log, ); } /** * Recursively sanitizes all string values in the data. * jsCode is preserved as-is (displayed with esc_html(), never executed). * * @since 1.0.0 * @param mixed $value Value to sanitize. * @param string $parent_key Parent array key for context. * @return mixed Sanitized value. */ private function sanitize_recursive( $value, string $parent_key = '' ): mixed { if ( is_array( $value ) ) { $result = array(); foreach ( $value as $key => $item ) { $result[ $key ] = $this->sanitize_recursive( $item, (string) $key ); } return $result; } if ( is_string( $value ) ) { // Preserve jsCode as-is; it will be displayed with esc_html(). if ( 'jsCode' === $parent_key ) { return $value; } return sanitize_text_field( $value ); } if ( is_int( $value ) || is_float( $value ) ) { return $value; } if ( is_bool( $value ) ) { return $value; } return null; } /** * Applies masking rules to a single node's parameters. * * @since 1.0.0 * @param array $node Single workflow node array. * @return array Node with masked parameter values. */ private function mask_node( array $node ): array { if ( ! isset( $node['parameters'] ) || ! is_array( $node['parameters'] ) ) { return $node; } $node['parameters'] = $this->mask_parameters_recursive( $node['parameters'] ); return $node; } /** * Recursively applies masking rules to parameter values. * * @since 1.0.0 * @param array $params Parameters array to process. * @return array Parameters with sensitive values masked. */ private function mask_parameters_recursive( array $params ): array { foreach ( $params as $key => &$value ) { if ( is_string( $value ) && 'jsCode' !== $key ) { $value = MaskingRules::apply( $value, (string) $key, $this->mask_log ); // Condition rightValue heuristic. if ( 'rightValue' === $key && '[REDACTED]' !== $value ) { $value = MaskingRules::apply_condition_heuristic( $value, $this->mask_log ); } } elseif ( is_array( $value ) ) { // {name, value} pair pattern — e.g. HTTP header/body parameters. // If the 'name' field indicates a sensitive header, mask its 'value'. if ( isset( $value['name'], $value['value'] ) && is_string( $value['value'] ) ) { $this->mask_name_value_pair( $value ); } $value = $this->mask_parameters_recursive( $value ); } } unset( $value ); return $params; } /** * Masks the 'value' of a {name, value} pair when the name implies sensitive data. * Covers HTTP headers like Authorization, API-Key, X-Auth-Token, etc. * * @param array $item Passed by reference — modifies $item['value'] in place. */ private function mask_name_value_pair( array &$item ): void { if ( '[REDACTED]' === $item['value'] ) { return; } $name_lower = strtolower( (string) $item['name'] ); $sensitive = array( 'authorization', 'token', 'api-key', 'apikey', 'x-api-key', 'x-auth', 'secret', 'password', 'bearer' ); foreach ( $sensitive as $keyword ) { if ( str_contains( $name_lower, $keyword ) ) { $this->mask_log[] = array( 'reason' => 'sensitive_header_name', 'key' => 'value', 'note' => 'Parameter name "' . esc_html( $item['name'] ) . '" indicates sensitive data.', ); $item['value'] = '[REDACTED]'; return; } } } }