*/ public function run(): array { $claimed = $this->agents->all_app_password_uuids(); $findings = array(); foreach ( $this->site_user_ids() as $user_id ) { foreach ( $this->passwords_for( $user_id ) as $password ) { $uuid = isset( $password['uuid'] ) ? (string) $password['uuid'] : ''; if ( '' === $uuid || in_array( $uuid, $claimed, true ) ) { continue; } $name = isset( $password['name'] ) ? (string) $password['name'] : ''; // Only the name and the owner travel. The stored hash is // never read, so there is no path by which it could. $findings[] = new Finding( $this->id(), Severity::ATTENTION, __( 'An application password no agent claims', 'agentic-daisy-ai-agent-firewall' ), __( 'Anything holding this password can write over the REST API without an agent identity, so no policy, scope or rate limit applies to it. Bind it to an agent, or revoke it in the user\'s profile.', 'agentic-daisy-ai-agent-firewall' ), sprintf( /* translators: 1: application password name, 2: the user who owns it. */ __( '%1$s (%2$s)', 'agentic-daisy-ai-agent-firewall' ), '' === $name ? __( 'Unnamed password', 'agentic-daisy-ai-agent-firewall' ) : $name, $this->owner_name( $user_id ) ), 'agents', $this->id() . ':unclaimed:' . $uuid ); } } return $findings; } /** * Everyone who could hold an application password. * * Ids only: most users hold no application password at all, and * resolving a display name for every account on the site to label the * handful that do is work nobody asked for. * * @return list */ private function site_user_ids(): array { $users = get_users( array( 'fields' => 'ID' ) ); if ( ! is_array( $users ) ) { return array(); } $ids = array(); foreach ( $users as $user ) { if ( is_scalar( $user ) ) { $ids[] = (int) $user; } } return $ids; } /** * Who owns a password, named as the site names them. * * @param int $user_id User id. */ private function owner_name( int $user_id ): string { $user = get_userdata( $user_id ); return $user instanceof \WP_User ? (string) $user->display_name : (string) $user_id; } /** * One user's stored passwords, defensively. * * @param int $user_id User id. * @return list> */ private function passwords_for( int $user_id ): array { $stored = get_user_meta( $user_id, self::META_KEY, true ); if ( ! is_array( $stored ) ) { return array(); } return array_values( array_filter( $stored, 'is_array' ) ); } }