'boolean', 'sanitize_callback' => 'rest_sanitize_boolean', 'default' => false, ) ); } /** * Add admin menu */ public static function add_admin_menu() { add_submenu_page( 'abilities-bridge', __( 'Ability Permissions', 'abilities-bridge' ), __( 'Ability Permissions', 'abilities-bridge' ), 'manage_options', 'abilities-bridge-permissions', array( __CLASS__, 'render_permissions_page' ) ); } /** * Enqueue admin assets. * * @param string $hook The current admin page hook. * @return void */ public static function enqueue_admin_assets( $hook ) { if ( strpos( $hook, 'abilities-bridge-permissions' ) === false ) { return; } wp_enqueue_style( 'abilities-bridge-permissions', ABILITIES_BRIDGE_PLUGIN_URL . 'admin/css/ability-permissions.css', array(), filemtime( ABILITIES_BRIDGE_PLUGIN_DIR . 'admin/css/ability-permissions.css' ) ); wp_enqueue_script( 'abilities-bridge-permissions', ABILITIES_BRIDGE_PLUGIN_URL . 'admin/js/ability-permissions.js', array( 'jquery' ), filemtime( ABILITIES_BRIDGE_PLUGIN_DIR . 'admin/js/ability-permissions.js' ), true ); wp_localize_script( 'abilities-bridge-permissions', 'abilitiesBridgeAbilities', array( 'abilitiesConsentGiven' => (bool) get_option( 'abilities_bridge_abilities_api_consent', false ), 'abilitiesApiAvailable' => function_exists( 'wp_get_ability' ), 'availableAbilities' => Abilities_Bridge_Ability_Permissions::get_registered_ability_names(), ) ); } /** * Verify the page navigation nonce. * * Returns true if nonce is valid or not present (first page load). * Dies with error if nonce is present but invalid. * * @return bool True if nonce is valid, false if no nonce present. */ private static function verify_page_nonce() { $nonce = filter_input( INPUT_GET, '_wpnonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS ); // No nonce present = first page load, allow with defaults. if ( empty( $nonce ) ) { return false; } // Nonce present - verify it. if ( ! wp_verify_nonce( $nonce, 'abilities_bridge_permissions_nav' ) ) { wp_die( esc_html__( 'Security verification failed. Please use the navigation links provided.', 'abilities-bridge' ), esc_html__( 'Security Check Failed', 'abilities-bridge' ), array( 'response' => 403 ) ); } return true; } /** * Get a sanitized GET parameter for display/routing purposes. * * Verifies nonce before accessing parameters. If no nonce is present * (first page load), returns the default value. * * @param string $key Parameter key. * @param string $default Default value. * @return string Sanitized value. */ private static function get_url_param( $key, $default = '' ) { // Verify nonce - returns false if no nonce (first load). if ( ! self::verify_page_nonce() ) { return $default; } $value = filter_input( INPUT_GET, $key, FILTER_SANITIZE_FULL_SPECIAL_CHARS ); return ! empty( $value ) ? sanitize_key( $value ) : $default; } /** * Get a sanitized integer GET parameter for display purposes. * * Verifies nonce before accessing parameters. If no nonce is present * (first page load), returns the default value. * * @param string $key Parameter key. * @param int $default Default value. * @return int Sanitized integer value. */ private static function get_url_int_param( $key, $default = 0 ) { // Verify nonce - returns false if no nonce (first load). if ( ! self::verify_page_nonce() ) { return $default; } $value = filter_input( INPUT_GET, $key, FILTER_VALIDATE_INT ); return false !== $value && null !== $value ? max( 1, absint( $value ) ) : $default; } /** * Render permissions page */ public static function render_permissions_page() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'abilities-bridge' ) ); } $active_tab = self::get_url_param( 'tab', 'list' ); ?>

🔐 Hardcoded Security

đŸ›Ąī¸ Security Architecture: Every ability execution must pass ALL 7 hardcoded permission gates. Default is DENY, explicit ALLOW only.

â„šī¸

esc_like( 'ability_executed_' ) . '%'; $usage_counts = $wpdb->get_results( $wpdb->prepare( "SELECT REPLACE(action, 'ability_executed_', '') as ability_name, COUNT(*) as count FROM %i WHERE action LIKE %s AND DATE(created_at) = %s GROUP BY action", Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ), $like_pattern, $today ), OBJECT_K ); foreach ( $permissions as $perm ) : // Get count from pre-loaded array (no query in loop). $today_count = isset( $usage_counts[ $perm['ability_name'] ] ) ? $usage_counts[ $perm['ability_name'] ]->count : 0; $usage_percent = $perm['max_per_day'] > 0 ? ( $today_count / $perm['max_per_day'] ) * 100 : 0; $usage_class = $usage_percent >= 90 ? 'high' : ( $usage_percent >= 70 ? 'medium' : 'low' ); ?>
✓ Enabled ✗ Disabled
0 ) { echo esc_html( $perm['max_per_day'] . '/day' ); } else { echo 'Disabled'; } ?>
'abilities-bridge-permissions', 'tab' => 'register', 'edit' => rawurlencode( $perm['ability_name'] ), '_wpnonce' => wp_create_nonce( 'abilities_bridge_permissions_nav' ), '_edit_nonce' => wp_create_nonce( 'abilities_bridge_edit_ability' ), ), admin_url( 'admin.php' ) ); ?>
403 ) ); } // Now safe to use user input after nonce verified. $ability_name = sanitize_text_field( wp_unslash( $_GET['edit'] ) ); $editing = true; global $wpdb; $edit_data = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE ability_name = %s', Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_ABILITY_PERMISSIONS ), $ability_name ), ARRAY_A ); // If ability not found, show error and stop editing mode. if ( ! $edit_data ) { $editing = false; ?>

â„šī¸

â„šī¸ About Authorization: Authorize an existing WordPress ability for Claude to execute. Abilities must first be registered in code via wp_register_ability() by plugin developers. This form configures security permissions and rate limits for the ability.

âš ī¸ Important: Only authorize abilities that you trust and understand. All executions are logged and subject to rate limits.

list="" required>

get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE action LIKE %s', Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ), $wpdb->esc_like( 'ability_executed_' ) . '%' ) ); $today_executions = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE action LIKE %s AND DATE(created_at) = %s', Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ), $wpdb->esc_like( 'ability_executed_' ) . '%', current_time( 'Y-m-d' ) ) ); $violations = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM %i WHERE action = 'permission_violation'", Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ) ) ); // Get source breakdown. $admin_executions = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE action LIKE %s AND source = %s', Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ), $wpdb->esc_like( 'ability_executed_' ) . '%', 'admin' ) ); $mcp_executions = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE action LIKE %s AND source = %s', Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ), $wpdb->esc_like( 'ability_executed_' ) . '%', 'mcp' ) ); // Get top abilities. $top_abilities = $wpdb->get_results( $wpdb->prepare( "SELECT REPLACE(action, 'ability_executed_', '') as ability_name, COUNT(*) as execution_count, MAX(created_at) as last_executed FROM %i WHERE action LIKE %s AND created_at > DATE_SUB(NOW(), INTERVAL 30 DAY) GROUP BY action ORDER BY execution_count DESC LIMIT 10", Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ), $wpdb->esc_like( 'ability_executed_' ) . '%' ) ); ?>

ability_name ); ?> execution_count ) ); ?> last_executed ), time() ) ); ?> ago
get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE action LIKE %s OR action = %s ORDER BY created_at DESC LIMIT %d OFFSET %d', Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ), $wpdb->esc_like( 'ability_' ) . '%', 'permission_violation', $per_page, $offset ) ); $total_logs = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE action LIKE %s OR action = %s', Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ), $wpdb->esc_like( 'ability_' ) . '%', 'permission_violation' ) ); $total_pages = ceil( $total_logs / $per_page ); ?>

created_at ), time() ) ); ?> ago username ); ?> action, 'success' ) !== false ) { $action_class .= ' log-success'; } elseif ( strpos( $log->action, 'failed' ) !== false || 'permission_violation' === $log->action ) { $action_class .= ' log-error'; } ?> action, '_' ) ) ); ?> function_name ); ?> error_message ) : ?> error_message ); ?> function_output ) : ?> ✓
1 ) : ?>
$base_url, 'format' => '', 'prev_text' => __( '«', 'abilities-bridge' ), 'next_text' => __( '»', 'abilities-bridge' ), 'total' => (int) $total_pages, 'current' => $paged, ) ) ); ?>
$enabled, 'max_per_day' => $max_per_day, 'max_per_hour' => $max_per_hour, 'risk_level' => $risk_level, 'min_capability' => $min_capability, 'description' => $description, 'reason_for_approval' => $reason_for_approval, ) ); if ( is_wp_error( $result ) ) { wp_safe_redirect( add_query_arg( array( 'page' => 'abilities-bridge-permissions', 'tab' => 'register', 'error' => rawurlencode( $result->get_error_message() ), ), admin_url( 'admin.php' ) ) ); } else { wp_safe_redirect( add_query_arg( array( 'page' => 'abilities-bridge-permissions', 'tab' => 'list', 'success' => 'registered', ), admin_url( 'admin.php' ) ) ); } exit; } /** * Handle ability toggle */ public static function handle_toggle_ability() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions.', 'abilities-bridge' ) ); } check_admin_referer( 'abilities_bridge_toggle_ability' ); $ability_name = isset( $_POST['ability'] ) ? sanitize_text_field( wp_unslash( $_POST['ability'] ) ) : ''; $enable = isset( $_POST['enable'] ) ? intval( wp_unslash( $_POST['enable'] ) ) : 0; if ( $enable ) { Abilities_Bridge_Ability_Permissions::enable_ability( $ability_name ); } else { Abilities_Bridge_Ability_Permissions::disable_ability( $ability_name ); } wp_safe_redirect( add_query_arg( array( 'page' => 'abilities-bridge-permissions', 'success' => $enable ? 'enabled' : 'disabled', ), admin_url( 'admin.php' ) ) ); exit; } /** * Handle ability deletion */ public static function handle_delete_ability() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions.', 'abilities-bridge' ) ); } check_admin_referer( 'abilities_bridge_delete_ability' ); $ability_name = isset( $_POST['ability'] ) ? sanitize_text_field( wp_unslash( $_POST['ability'] ) ) : ''; global $wpdb; $wpdb->delete( Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_ABILITY_PERMISSIONS ), array( 'ability_name' => $ability_name ), array( '%s' ) ); wp_safe_redirect( add_query_arg( array( 'page' => 'abilities-bridge-permissions', 'success' => 'deleted', ), admin_url( 'admin.php' ) ) ); exit; } /** * Handle enable/disable abilities API toggle */ public static function handle_toggle_abilities_api() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions.', 'abilities-bridge' ) ); } check_admin_referer( 'abilities_bridge_toggle_abilities_api' ); $enabled = isset( $_POST['abilities_bridge_enable_abilities_api'] ) && sanitize_text_field( wp_unslash( $_POST['abilities_bridge_enable_abilities_api'] ) ) === '1'; $consent_submitted = isset( $_POST['abilities_bridge_abilities_api_consent'] ) && sanitize_text_field( wp_unslash( $_POST['abilities_bridge_abilities_api_consent'] ) ) === '1'; $consent_already_given = get_option( 'abilities_bridge_abilities_api_consent', false ); // Validate: If trying to enable, consent must be checked (or already given). if ( $enabled && ! $consent_submitted && ! $consent_already_given ) { wp_safe_redirect( add_query_arg( array( 'page' => 'abilities-bridge-permissions', 'tab' => 'list', 'error' => 'abilities_consent_required', ), admin_url( 'admin.php' ) ) ); exit; } // Persist consent if newly given (one-time consent). if ( $consent_submitted && ! $consent_already_given ) { update_option( 'abilities_bridge_abilities_api_consent', true ); } // Update abilities API setting. update_option( 'abilities_bridge_enable_abilities_api', $enabled ); wp_safe_redirect( add_query_arg( array( 'page' => 'abilities-bridge-permissions', 'tab' => 'list', 'success' => $enabled ? 'abilities_enabled' : 'abilities_disabled', ), admin_url( 'admin.php' ) ) ); exit; } /** * Handle core abilities enable/disable toggle */ public static function handle_toggle_core_abilities() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions.', 'abilities-bridge' ) ); } check_admin_referer( 'abilities_bridge_toggle_core_abilities', 'toggle_core_abilities_nonce' ); $core_abilities = array( 'core/get-site-info', 'core/get-user-info', 'core/get-environment-info' ); $all_permissions = Abilities_Bridge_Ability_Permissions::get_all_permissions( false ); $changes_made = array(); foreach ( $core_abilities as $ability_name ) { $field_name = 'core_ability_' . $ability_name; if ( isset( $_POST[ $field_name ] ) ) { $enable = intval( wp_unslash( $_POST[ $field_name ] ) ); // Check if ability is already registered. $permission = null; foreach ( $all_permissions as $perm ) { if ( $perm['ability_name'] === $ability_name ) { $permission = $perm; break; } } if ( ! $permission ) { // Auto-register with safe defaults if not exists. Abilities_Bridge_Ability_Permissions::register_ability( $ability_name, array( 'enabled' => $enable, 'max_per_day' => 100, 'max_per_hour' => 50, 'risk_level' => 'low', 'min_capability' => null, 'description' => self::get_core_ability_description( $ability_name ), 'reason_for_approval' => 'Core read-only ability enabled via UI', ) ); $changes_made[] = $ability_name . ' registered and ' . ( $enable ? 'enabled' : 'disabled' ); } elseif ( $enable && ! $permission['enabled'] ) { // Enable existing ability. Abilities_Bridge_Ability_Permissions::enable_ability( $ability_name ); $changes_made[] = $ability_name . ' enabled'; } elseif ( ! $enable && $permission['enabled'] ) { // Disable existing ability. Abilities_Bridge_Ability_Permissions::disable_ability( $ability_name ); $changes_made[] = $ability_name . ' disabled'; } } } $message = ! empty( $changes_made ) ? 'core_abilities_updated' : 'no_changes'; wp_safe_redirect( add_query_arg( array( 'page' => 'abilities-bridge-permissions', 'tab' => 'list', 'success' => $message, ), admin_url( 'admin.php' ) ) ); exit; } /** * Get description for core abilities. * * @param string $ability_name The ability name. * @return string The ability description. */ private static function get_core_ability_description( $ability_name ) { $descriptions = array( 'core/get-site-info' => 'Get WordPress site information', 'core/get-user-info' => 'Get current user information', 'core/get-environment-info' => 'Get server environment information', ); return isset( $descriptions[ $ability_name ] ) ? $descriptions[ $ability_name ] : 'Core read-only ability'; } } // Initialize admin interface. Abilities_Bridge_Ability_Permissions_Admin::init();