Dlaczego kod wtyczek WordPress z AI ma luki
Wykorzystanie modeli językowych (LLM), takich jak GPT-4, Claude 3.5 Sonnet czy Gemini Pro, do pisania kodu wtyczek dla WordPressa stało się codziennością wielu programistów i właścicieli stron. Wizja jest kusząca: opisujesz słownie potrzebną funkcjonalność, a sztuczna inteligencja generuje gotowy plik PHP w kilkanaście sekund.
Problem polega na tym, że modele językowe doskonale znają strukturę składniową PHP, ale nie rozumieją intencji bezpieczeństwa ani specyfiki działania ekosystemu WordPressa. Uczą się one na gigantycznych zbiorach publicznego kodu, w których znajdują się miliony przestarzałych, niezabezpieczonych wtyczek napisanych w ciągu ostatnich dwudziestu lat. W rezultacie AI generuje kod, który wygląda na poprawny, przechodzi testy działania (funkcjonalność działa), ale pod spodem zawiera krytyczne luki bezpieczeństwa.
Jako inżynierowie WordPressa musimy stosować zasadę ograniczonego zaufania (Zero Trust) wobec każdego fragmentu kodu wygenerowanego przez AI. Niniejszy przewodnik opisuje pięć najpowszechniejszych wzorców błędów bezpieczeństwa, które regularnie wykrywamy podczas audytów kodu stworzonego przez sztuczną inteligencję, wraz z konkretnymi przykładami podatnego kodu oraz metodami ich naprawy zgodnie z WordPress Coding Standards (WPCS).
1. Mylenie is_admin() z weryfikacją uprawnień
To prawdopodobnie najczęstszy błąd logiczny popełniany przez AI przy próbie zabezpieczenia kodu. Kiedy poprosisz model o zabezpieczenie funkcji tak, aby tylko administrator miał do niej dostęp, AI bardzo często generuje warunek bazujący na funkcji is_admin().
Dlaczego to nie działa
Wbrew swojej nazwie, funkcja is_admin() nie weryfikuje, czy aktualnie zalogowany użytkownik jest administratorem systemu. Sprawdza ona jedynie, czy aktualne zapytanie dotyczy strony wewnątrz panelu administracyjnego (np. dowolnego adresu URL zaczynającego się od /wp-admin/).
Każde żądanie AJAX wysyłane do /wp-admin/admin-ajax.php - niezależnie od tego, czy wysyła je administrator, zalogowany subskrybent o najniższych uprawnieniach, czy nawet niezalogowany użytkownik - sprawi, że funkcja is_admin() zwróci wartość true.
is_admin() jako jedyna kontrola przy zapisie
// Przykład kodu wygenerowanego przez AI do obsługi zapisu konfiguracji wtyczki
add_action( 'admin_init', 'ai_save_plugin_settings' );
function ai_save_plugin_settings() {
// AI błędnie uważa, że to zabezpiecza przed nieautoryzowanym zapisem
if ( is_admin() ) {
if ( isset( $_POST['my_plugin_option'] ) ) {
update_option( 'my_plugin_option', $_POST['my_plugin_option'] );
}
}
}Sprawdzanie uprawnień przez current_user_can()
Aby poprawnie zweryfikować uprawnienia użytkownika, należy zastosować funkcję current_user_can(), przekazując do niej odpowiednią rolę lub - co jest znacznie bardziej zalecane - konkretną zdolność (capability), np. manage_options.
add_action( 'admin_init', 'secure_save_plugin_settings' );
function secure_save_plugin_settings() {
// 1. Zawsze sprawdzaj konkretne uprawnienia zalogowanego użytkownika
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Nie masz uprawnień do wykonania tej akcji.', 'secure-plugin' ) );
}
if ( isset( $_POST['my_plugin_option'] ) ) {
// Dodatkowo wprowadzamy sanityzację (patrz sekcja 4)
$sanitized_value = sanitize_text_field( wp_unslash( $_POST['my_plugin_option'] ) );
update_option( 'my_plugin_option', $sanitized_value );
}
}2. Weryfikacja nonce w WordPress przeciw CSRF
Ataki typu Cross-Site Request Forgery (CSRF) polegają na zmuszeniu przeglądarki uwierzytelnionego użytkownika (np. administratora) do wykonania niechcianej akcji na stronie bez jego wiedzy. W ekosystemie WordPressa podstawową linią obrony przed CSRF są tokeny bezpieczeństwa zwane nonces.
Sztuczna inteligencja niemal nagminnie pomija generowanie i weryfikację nonces w formularzach administracyjnych, żądaniach AJAX oraz przy obsłudze endpointów REST API. Dzieje się tak, ponieważ weryfikacja nonces wymaga dodatkowego kodu i spójnego połączenia widoku (formularza HTML) z kontrolerem (odbiorcą żądania w PHP), co dla modeli LLM bywa trudne do zsynchronizowania w ramach jednej odpowiedzi.
Handler AJAX bez weryfikacji nonce
// Rejestracja endpointu AJAX przez AI
add_action( 'wp_ajax_ai_delete_post', 'ai_delete_post_handler' );
function ai_delete_post_handler() {
// AI sprawdza uprawnienia, ale całkowicie ignoruje CSRF (brak nonce)
if ( ! current_user_can( 'delete_posts' ) ) {
wp_send_json_error( 'Brak uprawnień.' );
}
$post_id = intval( $_POST['post_id'] );
wp_delete_post( $post_id );
wp_send_json_success( 'Wpis usunięty.' );
}Weryfikacja nonce przez check_ajax_referer()
Formularz wyjściowy musi generować pole nonce za pomocą funkcji wp_nonce_field(), a obsługa żądania PHP musi zweryfikować ten token przy użyciu check_ajax_referer() lub wp_verify_nonce().
// W widoku formularza lub skrypcie JS przekazującym dane
// wp_nonce_field( 'delete_post_action', 'my_nonce_field' );
// Rejestracja bezpiecznego endpointu AJAX
add_action( 'wp_ajax_secure_delete_post', 'secure_delete_post_handler' );
function secure_delete_post_handler() {
// 1. Walidacja CSRF przy użyciu check_ajax_referer
check_ajax_referer( 'delete_post_action', 'my_nonce_field' );
// 2. Walidacja uprawnień użytkownika
if ( ! current_user_can( 'delete_posts' ) ) {
wp_send_json_error( 'Brak uprawnień.' );
}
// 3. Walidacja i rzutowanie typu danych wejściowych
if ( ! isset( $_POST['post_id'] ) ) {
wp_send_json_error( 'Brak ID wpisu.' );
}
$post_id = absint( $_POST['post_id'] );
if ( wp_delete_post( $post_id ) ) {
wp_send_json_success( 'Wpis został bezpiecznie usunięty.' );
} else {
wp_send_json_error( 'Błąd podczas usuwania wpisu.' );
}
}Przed czym nonce w WordPressie nie chroni
Nonce potwierdza tylko, że żądanie pochodzi z formularza albo linku, który WordPress wygenerował dla tego użytkownika. Rozdział o nonce w dokumentacji API bezpieczeństwa WordPressa mówi wprost, że na nonce nie wolno opierać uwierzytelniania, autoryzacji ani kontroli dostępu, dlatego sprawdzenie uprawnień obok niego zostaje obowiązkowe. W przeglądzie liczą się trzy cechy. Nonce powstaje dla konkretnego użytkownika i sesji, więc token skopiowany z jednego konta nie zadziała na innym. Domyślny czas życia to 24 godziny podzielone na dwa takty (ticki), a WordPress akceptuje bieżący i poprzedni takt, więc dany token działa od 12 do 24 godzin. Nonce nie jest też jednorazowy: tę samą wartość można wysłać ponownie w tym oknie, więc nie chroni przed powtórzeniem żądania (replay). Wygenerowany handler, który traktuje poprawny nonce jako dowód tożsamości, ma ochronę przed CSRF, ale nadal nie ma kontroli dostępu.
3. Podatności SQL Injection w zapytaniach do $wpdb
Gdy wtyczka potrzebuje komunikacji z bazą danych poza standardową klasą WP_Query, AI sięga po obiekt $wpdb. Niestety, mechanizmy LLM bardzo często zapominają o konieczności parametryzacji zapytań SQL, wstrzykując zmienne z tablic superglobalnych ($_GET, $_POST) bezpośrednio do zapytań SQL poprzez konkatenację ciągów tekstowych.
Dlaczego to niebezpieczne
Bezpośrednie wstrzyknięcie wartości do zapytania SQL pozwala napastnikowi na zmianę logiki zapytania bazy danych. Może to prowadzić do odczytania haseł użytkowników, usunięcia całej bazy danych lub eskalacji uprawnień do poziomu administratora.
Zapytanie SQL ze sklejaną zmienną
// Funkcja wyszukiwania użytkowników napisana przez AI
function ai_find_users_by_city( $city_name ) {
global $wpdb;
// Krytyczny błąd: bezpośrednie wstrzyknięcie zmiennej do zapytania SQL
$query = "SELECT * FROM {$wpdb->prefix}custom_users WHERE city = '" . $city_name . "'";
return $wpdb->get_results( $query );
}Zabezpieczenie zapytań przez $wpdb->prepare()
Do bazy danych nie może trafić żadne zapytanie zawierające dynamiczne zmienne, które nie zostało wcześniej przetworzone przez metodę $wpdb->prepare(). Metoda ta działa jak sparametryzowane zapytanie, podstawiając wartości pod znaczniki formatu i automatycznie je zabezpieczając.
function secure_find_users_by_city( $city_name ) {
global $wpdb;
// 1. Zabezpieczamy zapytanie używając prepare() ze znacznikami formatu
// %s oznacza ciąg znaków, %d oznacza liczbę całkowitą, %f oznacza zmiennoprzecinkową
$query = $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}custom_users WHERE city = %s",
$city_name
);
return $wpdb->get_results( $query );
}Dodatkowo, jeśli zmienna $city_name pochodzi bezpośrednio z żądania użytkownika, przed przekazaniem jej do funkcji należy ją oczyścić (np. za pomocą sanitize_text_field()).
4. Sanityzacja i escapowanie danych w WordPress (XSS)
Cross-Site Scripting (XSS) to jedna z najpowszechniejszych podatności we wtyczkach WordPressa. Polega ona na wstrzyknięciu złośliwego kodu JavaScript na stronę internetową, który jest następnie wykonywany w przeglądarce innych użytkowników.
Modele AI często mylą sanityzację (czyszczenie danych przed zapisem) z eskapowaniem (zabezpieczanie danych podczas wyświetlania). Czasami AI stosuje sanityzację na wejściu, ale całkowicie zapomina o eskapowaniu na wyjściu, co i tak pozostawia otwartą furtkę dla ataków typu Stored XSS.
Wyświetlanie danych bez escapowania
// Przykład formularza opinii generowanego przez AI
function ai_display_user_feedback() {
$feedbacks = get_option( 'ai_user_feedback_list', [] );
echo '<div class="feedback-list">';
foreach ( $feedbacks as $feedback ) {
// Krytyczny błąd: bezpośrednie wyświetlenie danych bez eskapowania
echo '<p class="feedback-item">' . $feedback['user_comment'] . '</p>';
}
echo '</div>';
}Escapowanie wyjścia przez esc_html()
Złota zasada bezpieczeństwa WordPressa brzmi: Sanitize Early, Escape Late (sanityzuj jak najwcześniej, eskapuj jak najpóźniej - tuż przed samym wyrenderowaniem kodu HTML).
Do eskapowania danych na wyjściu stosujemy wyspecjalizowane funkcje:
esc_html()- dla zwykłego tekstu wewnątrz tagów HTML.esc_attr()- dla wartości atrybutów HTML (np.value="",alt="").esc_url()- dla adresów URL.wp_kses()- gdy dopuszczamy bezpieczne tagi HTML (np. pogrubienie, linki).
function secure_display_user_feedback() {
$feedbacks = get_option( 'secure_user_feedback_list', [] );
echo '<div class="feedback-list">';
foreach ( $feedbacks as $feedback ) {
// 1. Zabezpieczamy dane wyjściowe odpowiednią funkcją eskapującą
$comment = isset( $feedback['user_comment'] ) ? $feedback['user_comment'] : '';
echo '<p class="feedback-item">';
echo esc_html( $comment );
echo '</p>';
}
echo '</div>';
}5. Podatności typu Arbitrary File Upload (niebezpieczne przesyłanie plików)
Umożliwienie użytkownikom przesyłania plików (np. formularze kontaktowe z załącznikami, systemy wgrywania avatarów) to jedno z najbardziej krytycznych miejsc na stronie. Błąd w tym obszarze pozwala na wgranie na serwer pliku o rozszerzeniu .php, co skutkuje całkowitym przejęciem kontroli nad serwerem (Remote Code Execution - RCE).
Sztuczna inteligencja generując kod przesyłania plików, często opiera się na natywnych funkcjach PHP takich jak move_uploaded_file(), sprawdzając rozszerzenie pliku za pomocą prostych operacji na ciągach znaków, które można łatwo obejść (np. poprzez przesyłanie plików z rozszerzeniami file.php.jpg lub file.phtml).
Upload przez move_uploaded_file() ze sprawdzaniem rozszerzenia
// Obsługa przesyłania plików napisana przez AI
function ai_handle_avatar_upload() {
if ( isset( $_FILES['avatar'] ) ) {
$file_name = $_FILES['avatar']['name'];
$ext = pathinfo( $file_name, PATHINFO_EXTENSION );
// AI uważa, że to weryfikuje typ pliku
if ( in_array( $ext, ['jpg', 'jpeg', 'png'] ) ) {
$target = wp_upload_dir()['path'] . '/' . $file_name;
move_uploaded_file( $_FILES['avatar']['tmp_name'], $target );
}
}
}Bezpieczny upload plików przez wp_handle_upload()
W WordPressie należy bezwzględnie unikać bezpośredniego korzystania z move_uploaded_file(). Zamiast tego należy używać wbudowanych funkcji bezpieczeństwa, takich jak wp_handle_upload(), która automatycznie weryfikuje prawdziwy typ MIME pliku (nie tylko rozszerzenie), sprawdza poprawność przesyłania oraz integruje się z systemem uprawnień WordPressa.
function secure_handle_avatar_upload() {
// 1. Weryfikacja Nonce dla bezpieczeństwa formularza
if ( ! isset( $_POST['avatar_upload_nonce'] ) || ! wp_verify_nonce( $_POST['avatar_upload_nonce'], 'upload_avatar_action' ) ) {
wp_die( esc_html__( 'Błąd autoryzacji tokena.', 'secure-plugin' ) );
}
// 2. Sprawdzenie uprawnień zalogowanego użytkownika
if ( ! current_user_can( 'upload_files' ) ) {
wp_die( esc_html__( 'Nie masz uprawnień do wgrywania plików.', 'secure-plugin' ) );
}
if ( isset( $_FILES['avatar'] ) ) {
// Wymagane pliki biblioteki WordPress do obsługi uploadu
if ( ! function_exists( 'wp_handle_upload' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
// 3. Definiujemy dozwolone typy plików (MIME)
$allowed_mimes = [
'jpg|jpeg' => 'image/jpeg',
'png' => 'image/png'
];
$upload_overrides = [
'test_form' => false,
'mimes' => $allowed_mimes // Nadpisujemy dozwolone formaty
];
// 4. Bezpiecznie przetwarzamy plik przez rdzeń WordPressa
$movefile = wp_handle_upload( $_FILES['avatar'], $upload_overrides );
if ( $movefile && ! isset( $movefile['error'] ) ) {
// Plik wgrany poprawnie, zwraca adres URL pliku
$avatar_url = $movefile['url'];
update_user_meta( get_current_user_id(), 'user_avatar', $avatar_url );
} else {
// Obsługa błędu przesyłania
error_log( 'Błąd wgrywania pliku: ' . $movefile['error'] );
}
}
}6. Nieistniejące funkcje WordPress i fałszywe pakiety Composer
Osobną klasą błędów, właściwą wyłącznie modelom generatywnym, jest pewne siebie wymyślanie kodu, który nie istnieje. Ponieważ LLM przewiduje prawdopodobne tokeny zamiast sięgać do rzeczywistej dokumentacji WordPressa, bez wahania wywołuje funkcje, które nigdy nie były częścią rdzenia, takie jak wp_verify_admin(), get_current_user_role() czy sanitize_all_input(). Nazwy te wyglądają wiarygodnie, bo są zgodne z konwencją nazewnictwa WordPressa, a błąd krytyczny w czasie wykonania jest często pierwszym sygnałem, że coś jest nie tak. Gorzej: programista pod presją terminu “naprawia” błąd, pisząc własną cienką nakładkę wokół halucynowanej nazwy i po cichu przywraca dokładnie tę lukę w sanityzacji lub kontroli uprawnień, którą zmyślona funkcja miała rzekomo zamykać.
Ten sam wzorzec dotyczy zależności zewnętrznych. Gdy AI szkicuje plik composer.json lub proponuje linie require, potrafi wskazać pakiety, których nie ma na Packagist, albo, co groźniejsze, takie, których nazwa różni się od prawdziwej biblioteki o jeden znak. Napastnicy wykorzystują to zjawisko “slopsquattingu”, rejestrując halucynowane nazwy pakietów z wyprzedzeniem, tak że niezweryfikowane composer install wciąga złośliwy kod prosto do wtyczki.
Widzieliśmy wtyczkę, w której model wygenerował wywołanie nieistniejącej funkcji wp_kses_all(), a autor obszedł błąd krytyczny, deklarując własną pustą funkcję o tej samej nazwie, przez co filtrowanie HTML w całym formularzu przestało cokolwiek robić. Dwie konkretne kontrole zamykają tę lukę. Po pierwsze: każdą nieznaną funkcję zestaw z oficjalną dokumentacją WordPress Code Reference pod adresem developer.wordpress.org, zanim jej zaufasz; jeśli nie ma wpisu, potraktuj wywołanie jako zmyślone. Po drugie: przypnij i zaudytuj każdą zależność, potwierdź, że pakiet istnieje dokładnie w tej postaci, pochodzi od oczekiwanego opiekuna i jest przypięty do sprawdzonej wersji, a nie do ruchomego zakresu.
// Halucynacja: taka funkcja rdzenia nie istnieje
if ( wp_current_user_is_admin() ) { /* ... */ }
// Prawdziwe API WordPressa
if ( current_user_can( 'manage_options' ) ) { /* ... */ }wp plugin verify-checksums porównuje pliki wtyczki z sumami kontrolnymi opublikowanymi na WordPress.org (dokumentacja WP-CLI), więc pomaga tylko przy wtyczkach z katalogu, a nie przy wtyczce napisanej przez AI. Dla zależności Composera kontrolą jest zatwierdzony i przejrzany w diffie composer.lock, a composer why <pakiet> pokazuje, które wymaganie wciągnęło nieoczekiwany pakiet.
7. Jak zablokować bezpośredni dostęp do plików PHP wtyczki
Każdy plik PHP we wtyczce lub motywie powinien odmówić wykonania, jeśli WordPress nie został załadowany. Podręcznik wtyczek zaleca do tego sprawdzenie stałej ABSPATH na początku pliku. Wygenerowany kod często tę linię pomija, zwłaszcza w plikach pomocniczych, które model wydzielił dla czytelności.
Plik pomocniczy bez sprawdzenia ABSPATH
<?php
function fjord_get_export_rows() {
global $wpdb;
return $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}fjord_records" );
}Bezpośrednie wywołanie /wp-content/plugins/fjord-exporter/helpers.php może uruchomić plik poza WordPressem i, zależnie od konfiguracji serwera, całkowicie ominąć logowanie.
Sprawdzenie ABSPATH w plikach PHP wtyczki
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}W przeglądzie obowiązuje prosta reguła: każdy plik .php w katalogu wtyczki potrzebuje tych linii, łącznie z uninstall.php i skopiowanymi plikami bibliotek. Podejrzane są też wygenerowane pliki “API”, które same ładują WordPressa przez require 'wp-load.php'; i potem nie sprawdzają uwierzytelnienia.
8. Trasy REST API bez permission_callback
Wygenerowane wtyczki często udostępniają dane przez register_rest_route() zamiast przez admin-ajax.php. Decyzja o dostępie do trasy REST siedzi w jednym argumencie, permission_callback, a modele psują go na dwa sposoby: pomijają go albo ustawiają na __return_true, żeby błąd 401 zniknął podczas testów.
Podręcznik REST API opisuje oba przypadki. Od WordPressa 5.5 trasa zarejestrowana bez permission_callback wywołuje komunikat _doing_it_wrong z nazwą trasy, więc brakujący callback widać w logu debugowania każdej strony z włączonym WP_DEBUG. __return_true to udokumentowana wartość dla endpointów, które mają być publiczne. Dla niej żaden komunikat się nie pojawia, więc trasa zapisu przez pomyłkę oznaczona jako publiczna przechodzi niezauważona.
Trasa zapisu otwarta dla każdego
register_rest_route( 'fjord/v1', '/settings', array(
'methods' => 'POST',
'callback' => 'fjord_update_settings',
'permission_callback' => '__return_true',
) );Trasa zapisu ze sprawdzeniem uprawnień i walidacją argumentów
register_rest_route( 'fjord/v1', '/settings', array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => 'fjord_update_settings',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
'args' => array(
'label' => array(
'type' => 'string',
'required' => true,
'validate_callback' => function ( $value ) {
return is_string( $value ) && strlen( $value ) <= 100;
},
'sanitize_callback' => 'sanitize_text_field',
),
),
) );Dla każdego argumentu validate_callback zwraca true albo false, a serwer REST odrzuca niepoprawne dane z błędem czytelnym dla klienta, natomiast sanitize_callback czyści wartość, zanim trafi do Twojego callbacku. W przeglądzie wypisz każde wywołanie register_rest_route() i najpierw przeczytaj jego permission_callback: __return_true na trasie, która przyjmuje coś więcej niż GET, wymaga pisemnego uzasadnienia. Dla żądań z przeglądarki uwierzytelnianych ciasteczkiem rozdział o uwierzytelnianiu dodaje jeszcze jedną regułę: bez nonce wp_rest w nagłówku X-WP-Nonce API ustawia bieżącego użytkownika na 0. Skrypt frontendowy, który “działa” tylko dlatego, że pomija nonce, w rzeczywistości wywołuje trasę jako niezalogowany gość.
Jak zrobić audyt kodu wtyczki wygenerowanej przez AI
Aby uchronić się przed podatnościami generowanymi przez sztuczną inteligencję, każdy zespół programistyczny powinien wdrożyć ustrukturyzowany proces weryfikacji. Oto kroki, które należy podjąć przed wdrożeniem jakiegokolwiek wygenerowanego kodu na środowisko produkcyjne:
Krok 1: Statyczna analiza kodu (lintery i PHPCS)
Pierwszą linią obrony jest automatyzacja. Skonfiguruj WordPress Coding Standards (WPCS) dla narzędzia PHP_CodeSniffer. Narzędzie to automatycznie przeskanuje pliki PHP wygenerowane przez AI i wskaże brakujące funkcje sanityzacji, eskapowania czy weryfikacji nonces.
# Uruchomienie PHPCS z regułami WordPress-Extra na wygenerowanym pliku wtyczki
vendor/bin/phpcs --standard=WordPress-Extra path/to/generated-plugin.phpKtóre reguły WPCS wyłapują te błędy bezpieczeństwa
WordPress Coding Standards 3.x instaluje się wyłącznie przez Composera i wymaga PHP 7.2 lub nowszego; repozytorium WordPressCS opublikowało w lipcu 2026 wersję 3.4.1.
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer require --dev wp-coding-standards/wpcs:"^3.0"WordPress-Extra zawiera WordPress-Core oraz zalecane praktyki. Reguły (sniffy) bezpieczeństwa są w dwóch grupach. WordPress.Security.NonceVerification zgłasza odczyty $_POST i $_GET bez wcześniejszego sprawdzenia nonce, WordPress.Security.ValidatedSanitizedInput zgłasza zmienne superglobalne użyte bez isset(), wp_unslash() i funkcji sanityzującej, WordPress.Security.EscapeOutput zgłasza nieescapowane wyjście, a WordPress.Security.SafeRedirect zgłasza wp_redirect() tam, gdzie powinno być bezpieczne przekierowanie. Po stronie bazy danych WordPress.DB.PreparedSQL i WordPress.DB.PreparedSQLPlaceholders zgłaszają SQL ze wstawionymi zmiennymi i błędy placeholderów, a WordPress.DB.DirectDatabaseQuery oznacza bezpośrednie zapytania do przejrzenia. Tak uruchomisz tylko reguły bezpieczeństwa na wygenerowanej wtyczce:
vendor/bin/phpcs --standard=WordPress --sniffs=WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput,WordPress.Security.EscapeOutput,WordPress.DB.PreparedSQL path/to/generated-plugin/Żadna reguła nie sprawdzi, czy handler wymaga właściwego uprawnienia, więc kontrola current_user_can() w kroku 2 zostaje ręczna.
Krok 2: Ręczny przegląd kodu (checklista zero trust)
Każda funkcja obsługująca żądanie HTTP, wywołanie AJAX, REST API lub interakcję z bazą danych musi przejść manualny audyt według poniższej listy kontrolnej:
graph TD
A[Otrzymanie kodu wtyczki od AI] --> B{Czy kod obsługuje wejście użytkownika?}
B -- Tak --> C[Sprawdź obecność Nonce i Capability Checks]
B -- Nie --> D{Czy kod modyfikuje bazę danych?}
C --> C1[Wdrożenie weryfikacji nonce za pomocą check_ajax_referer]
C --> C2[Wdrożenie current_user_can do kontroli uprawnień]
C1 --> D
C2 --> D
D -- Tak --> E[Sprawdź użycie metody $wpdb->prepare]
D -- Nie --> F{Czy kod wyświetla zmienne w przeglądarce?}
E --> E1[Wdrożenie sparametryzowanych zapytań SQL]
E1 --> F
F -- Tak --> G[Sprawdź obecność funkcji esc_html, esc_attr, esc_url]
F -- Nie --> H[Kod gotowy do testów integracyjnych]
G --> G1[Wdrożenie poprawnego eskapowania na wyjściu]
G1 --> HKrok 3: Testy funkcjonalne z kontami o niskich uprawnieniach
Zanim uznasz wtyczkę za bezpieczną, zaloguj się na konto testowe o roli “Subskrybent” (Subscriber) i spróbuj wywołać endpointy AJAX/REST zdefiniowane we wtyczce za pomocą narzędzi takich jak Postman lub curl. Jeśli wtyczka pozwoli na wykonanie operacji modyfikacji danych lub odczytania chronionych informacji, weryfikacja uprawnień leży u podstaw.
Jeśli część tego przeglądu robi agent AI, otwarty pakiet security-audit od Cloudflare, udostępniony na licencji MIT (README czytane w październiku 2026), pokazuje, jak trzymać jego wyniki w ryzach. Każde podejrzenie trafia do nowego agenta weryfikującego, który próbuje je obalić, więc znaleziska nigdy nie sprawdza ten sam agent, który je zgłosił. Trop, którego nie da się udowodnić, zostaje oznaczony jako needs_validation, bez oceny wagi, i nie trafia do raportu jako podatność. Brak drugiej warstwy ochrony, gdy pierwsza już blokuje atak, liczy się jako uwaga o utwardzeniu. Cloudflare dodaje, że w jego testach pojedyncze uruchomienie znalazło mniej więcej połowę podatności wykrytych łącznie w powtarzanych przebiegach. Przy wygenerowanej wtyczce jeden przebieg AI jest więc punktem wyjścia, a PHPCS, lista kontrolna dla funkcji obsługujących żądania i testy z kontem o niskich uprawnieniach dalej obowiązują.
Aktualizacja, . Do tej listy kontrolnej należą jeszcze dwa sprawdzenia, oba potrzebne we wtyczkach do WooCommerce. Pierwsze dotyczy wyświetlania wpisu lub produktu po ID. Każda funkcja, która bierze ID wpisu lub produktu z żądania albo z atrybutu shortcode’u, musi przed wypisaniem czegokolwiek sprawdzić status, widoczność i hasło, bo inaczej szkic, produkt prywatny albo chroniony hasłem pokaże nazwę, cenę lub zdjęcie przez shortcode, szybki podgląd albo listę porównania. Przeszukaj PHP pod kątem get_post(, wc_get_product( i shortcode_atts(, prześledź każde ID aż do wyjścia i znajdź po drodze kontrolę statusu i widoczności (na przykład current_user_can( 'read_post', $id ) albo $product->is_visible()) oraz post_password_required(). Drugie sprawdzenie dotyczy JavaScriptu. WooCommerce wysyła zdarzenia wariantów i koszyka przez jQuery, więc natywny addEventListener na nazwy takie jak found_variation czy updated_cart_totals nigdy się nie uruchomi. Wyszukaj te nazwy w JavaScripcie wtyczki i sprawdź, czy nasłuchiwacze idą przez jQuery, na przykład jQuery( document.body ).on( 'updated_cart_totals', ... ).
Jak uruchomić Plugin Check na wtyczce wygenerowanej przez AI
Plugin Check to narzędzie, którym WordPress.org sprawdza wtyczki pod kątem wymagań katalogu, i równie dobrze działa na prywatnej wtyczce, która nigdy tam nie trafi. Wersja 2.1.0 ukazała się w sierpniu 2026. Działa w dwóch trybach: kontrole statyczne czytają kod, częściowo przez reguły PHP_CodeSniffera, częściowo przez własne reguły, a kontrole w czasie działania aktywują wtyczkę i wykonują jej fragmenty. Projekt odradza uruchamianie go na stronie produkcyjnej, więc instaluj go na stagingu.
wp plugin check fjord-exporter
wp plugin check fjord-exporter --categories=security --format=csv --fields=code,type,severity,message
wp plugin check fjord-exporter --require=./wp-content/plugins/plugin-check/cli.phpZ WP-CLI domyślnie uruchamiają się tylko kontrole statyczne; trzecie polecenie ładuje cli.php, żeby uruchomić też kontrole w czasie działania. Lista kontroli przypisuje pięć z nich do kategorii security i pokrywają się one z wzorcami z tego poradnika:
| Kontrola | Co zgłasza | Wzorzec w tym poradniku |
|---|---|---|
direct_file_access | pliki PHP bez sprawdzenia ABSPATH | bezpośredni dostęp do pliku |
late_escaping | wyjście do przeglądarki bez escapowania | XSS |
direct_db | nieescapowane wartości w bezpośrednich zapytaniach | SQL Injection |
direct_db_queries | bezpośrednie zapytania do bazy w ogóle | SQL Injection |
safe_redirect | wp_redirect() zamiast wp_safe_redirect() | otwarte przekierowanie |
Zestaw reguł PHPCS za kontrolą plugin_review_phpcs ładuje dodatkowo reguły WPCS dla nonce, sanityzacji wejścia i przygotowanego SQL, ale należy do kategorii plugin_repo, więc --categories=security ukrywa te wyniki. Jedna kontrola dotyczy wprost kodu generowanego: file_type zgłasza katalogi narzędzi AI, takie jak .cursor, .claude, .aider, .continue, .windsurf i .ai, które zostały we wtyczce, bo jej folder był katalogiem roboczym asystenta kodowania. Każdy wynik ma typ (ERROR albo WARNING) i wagę od 1 do 10. Plugin Check nie wie, jakiego uprawnienia powinien wymagać handler, więc skraca ręczny przegląd, ale go nie zastępuje.
Kiedy łatać wtyczkę z AI, a kiedy ratować całą stronę
Lokalne luki (jeden handler AJAX, jeden formularz w panelu) warto załatać po przeglądzie. Gdy te same pięć wzorców powtarza się w wielu wygenerowanych plikach, gdy zmyślone funkcje są podpięte pod ścieżki produkcyjne albo gdy wtyczki wybrane przez AI mają aktywne CVE obok słabego własnego kodu, przestań łatać pojedyncze miejsca. Audyt w ramach naprawy stron zrobionych przez AI daje jawny podział na to, co zostaje, co do przepisania i co do usunięcia. Dzięki temu nie płacisz za pełną przebudowę, gdy wystarczy naprawa, i nie łatasz bez końca, gdy fundament się nie trzyma. Jeśli stack ma do tego ponad trzydzieści wtyczek i zepsute Core Web Vitals, połącz audyt kodu z naprawą Core Web Vitals na stronach z AI.
Cena naprawy i audytu jest ustalana indywidualnie, po tej inwentaryzacji. Napisz do nas, podając adres wtyczki lub strony i sposób, w jaki wygenerowano kod.
Podsumowanie i rekomendacje dla inżynierów
Sztuczna inteligencja jest potężnym narzędziem przyspieszającym pisanie kodu, ale nie zastąpi odpowiedzialności inżynieryjnej. Traktuj każdy fragment kodu wygenerowany przez LLM jak kod napisany przez niedoświadczonego praktykanta.
Zawsze pamiętaj o wdrożeniu poprawnych capability checks (current_user_can()), weryfikacji mechanizmów anty-CSRF (wp_verify_nonce()), zabezpieczaniu zapytań SQL za pomocą $wpdb->prepare(), a także o bezwzględnym czyszczeniu danych na wejściu (sanitize_*) i zabezpieczaniu ich przed wyrenderowaniem na wyjściu (esc_*).
Wdrażając te zasady oraz rygorystyczny proces automatycznego i manualnego audytu kodu, możesz bezpiecznie czerpać korzyści z szybkości pracy z AI, zachowując jednocześnie stabilność, szczelność i bezpieczeństwo aplikacji WordPress swoich klientów.







