admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( self::NONCE_ACTION ), 'version' => ACHWPA_VERSION, 'stages' => array( 'Bootstrap', 'MU-plugin', 'Database', 'Cron', '.htaccess', 'Users', 'REST' ), 'filesystem' => 'Filesystem', 'correlation' => 'Correlation', 'defaultBudget' => 1500, 'defaultTraversal' => 'standard', 'messages' => array( 'running' => __( 'Scanning…', 'aiutocomputerhelp-persistence-audit' ), 'complete' => __( 'Scan completed', 'aiutocomputerhelp-persistence-audit' ), 'failed' => __( 'The audit stopped because a scan request failed.', 'aiutocomputerhelp-persistence-audit' ), 'entries' => __( 'Candidate files inspected', 'aiutocomputerhelp-persistence-audit' ), 'directories' => __( 'Directories traversed', 'aiutocomputerhelp-persistence-audit' ), 'ignored' => __( 'PHP-like files skipped by metadata rules', 'aiutocomputerhelp-persistence-audit' ), 'filesSeen' => __( 'PHP-like files encountered', 'aiutocomputerhelp-persistence-audit' ), 'elapsed' => __( 'Elapsed time', 'aiutocomputerhelp-persistence-audit' ), 'stopping' => __( 'Stopping scan…', 'aiutocomputerhelp-persistence-audit' ), 'stopped' => __( 'Scan stopped by user', 'aiutocomputerhelp-persistence-audit' ), 'budget' => __( 'Budget', 'aiutocomputerhelp-persistence-audit' ), 'currentCheck' => __( 'Current check', 'aiutocomputerhelp-persistence-audit' ), 'filterFindings'=> __( 'Filter findings', 'aiutocomputerhelp-persistence-audit' ), 'traversalMode' => __( 'Traversal mode', 'aiutocomputerhelp-persistence-audit' ), ), ) ); } public function render_page() { if ( ! current_user_can( self::CAPABILITY ) ) { return; } ?>

'achwpa-run-button' ) ); ?>
ajax_capability_guard(); $category = isset( $_POST['category'] ) ? sanitize_text_field( wp_unslash( $_POST['category'] ) ) : ''; $scanner = new ACHWPA_Scanner(); $allowed = array_keys( ACHWPA_Scanner::scanner_map() ); if ( ! in_array( $category, $allowed, true ) ) { wp_send_json_error( array( 'message' => 'Invalid scan stage.' ), 400 ); } wp_send_json_success( array( 'findings' => $this->finding_arrays( $scanner->run_stage( $category ) ) ) ); } public function ajax_filesystem_init() { check_ajax_referer( self::NONCE_ACTION, 'nonce' ); $this->ajax_capability_guard(); $budget = isset( $_POST['budget'] ) ? absint( $_POST['budget'] ) : 1500; $traversal = isset( $_POST['traversal'] ) ? sanitize_key( wp_unslash( $_POST['traversal'] ) ) : 'standard'; if ( ! in_array( $traversal, array( 'standard', 'deep', 'deep-plus' ), true ) ) { $traversal = 'standard'; } $filesystem = new ACHWPA_Scan_Filesystem(); wp_send_json_success( array( 'state' => $filesystem->initial_state( $budget, $traversal ) ) ); } public function ajax_filesystem_batch() { check_ajax_referer( self::NONCE_ACTION, 'nonce' ); $this->ajax_capability_guard(); $state_json = isset( $_POST['state'] ) ? sanitize_textarea_field( wp_unslash( $_POST['state'] ) ) : ''; $state = $this->decode_json_value( $state_json ); if ( ! is_array( $state ) ) { wp_send_json_error( array( 'message' => 'Invalid filesystem state.' ), 400 ); } $batch_size = isset( $_POST['batch_size'] ) ? absint( $_POST['batch_size'] ) : 300; $filesystem = new ACHWPA_Scan_Filesystem(); $batch = $filesystem->run_batch( $state, $batch_size ); $findings = $batch['findings']; if ( ! empty( $batch['done'] ) && ! empty( $batch['state']['budget_reached'] ) ) { $findings[] = $filesystem->budget_finding( $batch['state'] ); } wp_send_json_success( array( 'state' => $batch['state'], 'done' => (bool) $batch['done'], 'progress' => absint( $batch['progress'] ), 'findings' => $this->finding_arrays( $findings ), 'summary' => array( 'candidates' => isset( $batch['state']['candidates_inspected'] ) ? absint( $batch['state']['candidates_inspected'] ) : 0, 'directories' => isset( $batch['state']['directories_seen'] ) ? absint( $batch['state']['directories_seen'] ) : 0, 'files_seen' => isset( $batch['state']['files_seen'] ) ? absint( $batch['state']['files_seen'] ) : 0, 'ignored' => isset( $batch['state']['files_ignored'] ) ? absint( $batch['state']['files_ignored'] ) : 0, ), ) ); } public function ajax_correlate() { check_ajax_referer( self::NONCE_ACTION, 'nonce' ); $this->ajax_capability_guard(); $findings_b64 = isset( $_POST['findings_b64'] ) ? sanitize_text_field( wp_unslash( $_POST['findings_b64'] ) ) : ''; $findings_json = base64_decode( $findings_b64, true ); $raw = is_string( $findings_json ) ? $this->decode_json_value( $findings_json ) : null; if ( ! is_array( $raw ) ) { wp_send_json_error( array( 'message' => 'Invalid findings payload.' ), 400 ); } $findings = array(); foreach ( array_slice( $raw, 0, 1000 ) as $item ) { $finding = $this->finding_from_client( $item ); if ( $finding instanceof ACHWPA_Finding ) { $findings[] = $finding; } } $scanner = new ACHWPA_Scanner(); wp_send_json_success( array( 'findings' => $this->finding_arrays( $scanner->correlate( $findings ) ) ) ); } private function ajax_capability_guard() { if ( ! current_user_can( self::CAPABILITY ) ) { wp_send_json_error( array( 'message' => 'Forbidden.' ), 403 ); } } private function decode_json_value( $json ) { if ( ! is_string( $json ) || '' === $json ) { return null; } $decoded = json_decode( $json, true ); return is_array( $decoded ) ? $decoded : null; } private function finding_arrays( array $findings ) { $out = array(); foreach ( $findings as $finding ) { if ( $finding instanceof ACHWPA_Finding ) { $out[] = $finding->to_array(); } } return $out; } private function finding_from_client( $item ) { if ( ! is_array( $item ) ) { return null; } $allowed_categories = array( 'Bootstrap', 'MU-plugin', 'Database', 'Cron', '.htaccess', 'Users', 'REST', 'Filesystem' ); $allowed_levels = array( 'Pass', 'Info', 'Review', 'Warning' ); $category = isset( $item['category'] ) ? sanitize_text_field( $item['category'] ) : ''; $level = isset( $item['level'] ) ? sanitize_text_field( $item['level'] ) : ''; if ( ! in_array( $category, $allowed_categories, true ) || ! in_array( $level, $allowed_levels, true ) ) { return null; } return new ACHWPA_Finding( array( 'category' => $category, 'level' => $level, 'location' => isset( $item['location'] ) ? sanitize_text_field( $item['location'] ) : '', 'indicator' => isset( $item['indicator'] ) ? sanitize_text_field( $item['indicator'] ) : '', 'explanation' => isset( $item['explanation'] ) ? sanitize_textarea_field( $item['explanation'] ) : '', 'preview' => isset( $item['preview'] ) ? sanitize_textarea_field( $item['preview'] ) : '', 'artifacts' => $this->sanitize_string_array( isset( $item['artifacts'] ) ? $item['artifacts'] : array(), 100 ), 'identifiers' => $this->sanitize_string_array( isset( $item['identifiers'] ) ? $item['identifiers'] : array(), 100 ), 'relationships' => $this->sanitize_string_array( isset( $item['relationships'] ) ? $item['relationships'] : array(), 100 ), ) ); } private function sanitize_string_array( $values, $limit ) { $out = array(); foreach ( array_slice( (array) $values, 0, $limit ) as $value ) { if ( is_scalar( $value ) ) { $clean = sanitize_text_field( (string) $value ); if ( '' !== $clean ) { $out[] = $clean; } } } return array_values( array_unique( $out ) ); } }