Poprosiłeś ChatGPT, Claude albo Cursora o wtyczkę. Po kwadransie masz plik ZIP, wgrywasz go na stronę testową i wszystko działa. Formularz się zapisuje, przycisk robi, co miał robić.
I tu jest pułapka. To, że kod działa, nie mówi nic o tym, czy jest bezpieczny. Przed wdrożeniem sprawdzasz siedem konkretnych miejsc, puszczasz dwa darmowe narzędzia (Plugin Check i PHPCS) i testujesz na kopii strony. A potem sprawdzasz ręcznie to, czego narzędzia nie widzą. Bo sprawdziłem: kilku poważnych dziur nie wyłapują.
Czy wtyczka napisana przez AI jest bezpieczna?
Czasem tak, czasem nie. I nie zgadniesz, patrząc na to, czy działa.
Co mówią badania o kodzie z AI
- Veracode (raport z 30.07.2025, ponad 100 modeli) podaje, że 45% próbek kodu nie przeszło testów bezpieczeństwa, a przed atakiem XSS nie było zabezpieczonych 86% próbek. Bezpieczeństwo nie rosło razem z rozmiarem modelu. Uwaga: badali Javę, Pythona, C# i JavaScript, nie PHP. Veracode sprzedaje narzędzia do testów bezpieczeństwa. (raport)
- Perry i współautorzy ze Stanford (konferencja ACM CCS 2023): osoby z asystentem AI pisały mniej bezpieczny kod i częściej wierzyły, że jest bezpieczny. Mniej błędów robili ci, którzy ufali AI mniej. Badanie dotyczyło starszego modelu. (publikacja)
- Uczciwy kontrprzykład: autor na dev.to w 32 próbach nie zdołał zmusić AI do napisania niebezpiecznego prostego formularza WordPress. To nieformalny test prostego przypadku, ale pokazuje, że przy prostych zadaniach modele często trzymają się zasad.
Wniosek? Nie zakładaj, że kod z AI jest zły. Zakładaj, że nie wiesz, dopóki nie sprawdzisz.
Co mówi WordPress.org
Zespół przeglądający wtyczki w katalogu WordPress.org napisał w maju 2025, że zgłoszeń nowych wtyczek jest o 87% więcej niż rok wcześniej i że AI ma na to wpływ. Główne problemy bezpieczeństwa wynikają według nich z braku sanityzacji, escapowania i nonce.
Katalog nie przyjmuje nowych wtyczek, które pozwalają wstawiać albo wykonywać dowolny kod, w tym narzędzi AI generujących kod do uruchomienia na stronie (FAQ dla twórców wtyczek).
A zasady korzystania z AI przy wkładzie w projekt WordPress mówią krótko: „Understand every line of code and documentation you submit”. Rozumiej każdą linię, którą oddajesz. Te zasady dotyczą wkładu w sam WordPress, ale to dobra reguła także dla Twojej prywatnej wtyczki.
Szersze tło: według raportu Patchstack za 2025 rok wykryto 11 334 nowe podatności w ekosystemie WordPressa, 91% z nich we wtyczkach. Najczęściej wykorzystywanym typem była błędna kontrola dostępu.
7 miejsc, w których kod z AI otwiera dziury w WordPressie
Przykłady poniżej są skrócone. Zły kod oznaczam wyraźnie. Nie kopiuj go na stronę.
1. Uprawnienia, czyli kto może kliknąć
Najczęstszy i najgroźniejszy brak. Akcja zapisu działa dla każdego zalogowanego użytkownika. A „zalogowany” to także Subskrybent albo Klient WooCommerce.
// ZŁE, nie używaj: AJAX bez nonce i bez uprawnień, $_POST prosto do bazy
add_action( 'wp_ajax_wm_zapisz', 'wm_zapisz' );
function wm_zapisz() {
update_option( 'wm_ustawienia', $_POST['ustawienia'] );
wp_send_json_success();
}
// DOBRE: nonce, uprawnienie, sanityzacja
function wm_zapisz() {
check_ajax_referer( 'wm_zapisz', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( null, 403 );
}
$ustawienia = isset( $_POST['ustawienia'] ) ? sanitize_text_field( wp_unslash( $_POST['ustawienia'] ) ) : '';
update_option( 'wm_ustawienia', $ustawienia );
wp_send_json_success();
}
Jak sprawdzić: przy każdym wp_ajax_, admin_post_ i formularzu w panelu szukasz current_user_can() z jak najwęższym uprawnieniem. Więcej o rolach znajdziesz we wpisie o uprawnieniach użytkowników.
2. Nonce, czyli ochrona przed podstawionym żądaniem
Nonce to token, który WordPress generuje dla bieżącego użytkownika i konkretnej akcji. Sprawdzając go przy zapisie, upewniasz się, że żądanie przyszło z formularza albo linku, który Twoja strona wygenerowała dla tego użytkownika i tej akcji. To chroni przed sytuacją, w której ktoś podstawia użytkownikowi link albo formularz wykonujący akcję w jego imieniu (CSRF). Nonce nie dowodzi, że użytkownik świadomie chciał coś zrobić, i nie sprawdza jego uprawnień. Dwie rzeczy, które warto wiedzieć z dokumentacji WordPressa:
- nonce w WordPressie nie jest jednorazowy. Domyślnie jest ważny od 12 do 24 godzin i w tym czasie można go użyć ponownie,
- „Nonces should never be relied on for authentication, authorization, or access control”. Nonce to nie są uprawnienia.
Czyli zawsze nonce i current_user_can(). Jedno nie zastępuje drugiego.
3. Sanityzacja na wejściu, escapowanie na wyjściu
Zasada z dokumentacji bezpieczeństwa: nigdy nie ufaj danym od użytkownika i escapuj jak najpóźniej, w chwili wypisania. Lepiej odrzucić złe dane niż próbować je czyścić.
// ZŁE, nie używaj: dane z adresu prosto na stronę (XSS)
echo '<h2>Witaj, ' . $_GET['imie'] . '</h2>';
// DOBRE: sanityzacja na wejściu, escapowanie przy wypisaniu
$imie = isset( $_GET['imie'] ) ? sanitize_text_field( wp_unslash( $_GET['imie'] ) ) : '';
echo '<h2>' . esc_html( sprintf( 'Witaj, %s', $imie ) ) . '</h2>';
Na wyjściu używasz esc_html, esc_attr, esc_url albo wp_kses, zależnie od miejsca. Na wejściu wp_unslash() i funkcji sanityzujących albo walidacji.
4. SQL bez $wpdb->prepare()
// ZŁE, nie używaj: SQL sklejany z $_GET
$wpdb->get_results( "SELECT * FROM {$wpdb->posts} WHERE post_title LIKE '%" . $_GET['q'] . "%'" );
// DOBRE: prepare i esc_like
$fraza = isset( $_GET['q'] ) ? sanitize_text_field( wp_unslash( $_GET['q'] ) ) : '';
$like = '%' . $wpdb->esc_like( $fraza ) . '%';
$wpdb->get_results( $wpdb->prepare( "SELECT ID, post_title FROM {$wpdb->posts} WHERE post_title LIKE %s", $like ) );
$wpdb->prepare() używa placeholderów bez cudzysłowów (%s, %d, %f, a dla nazw tabel i kolumn %i od WordPressa 6.2).
5. REST API bez permission_callback
Tę dziurę sprawdziłem w działaniu na testowym WordPressie. Od WordPressa 5.5 brak permission_callback daje tylko ostrzeżenie dla programisty (_doing_it_wrong) (dokumentacja). Punkt API i tak działa.
// ZŁE, nie używaj: REST bez permission_callback, usuwa wpis na stałe
register_rest_route( 'wm/v1', '/usun/(?P<id>\d+)', array(
'methods' => 'DELETE',
'callback' => function ( $r ) { return wp_delete_post( $r['id'], true ); },
) );
// DOBRE: uprawnienie do konkretnego wpisu
'permission_callback' => function ( $r ) {
return current_user_can( 'delete_post', (int) $r['id'] );
},
W moim teście niezalogowane żądanie do złej wersji zwróciło kod 200 i usunęło wpis. Poprawiona wersja odpowiedziała 401 i wpis został.
Czy __return_true w permission_callback to zawsze błąd? Nie. Jeśli punkt ma być publiczny (na przykład zapis zgody cookies od niezalogowanego gościa), to świadoma decyzja. Ale wtedy ograniczasz go inaczej. W naszej wtyczce publiczny punkt przyjmuje maksymalnie 512 bajtów, sprawdza poprawność JSON i ma limit zapisów na minutę.
6. Upload plików bez kontroli typu
// ZŁE, nie używaj: można wgrać plik .php
move_uploaded_file( $_FILES['plik']['tmp_name'], WP_CONTENT_DIR . '/uploads/' . $_FILES['plik']['name'] );
// DOBRE: nonce, uprawnienie upload_files, wp_handle_upload z listą typów
check_admin_referer( 'wm_upload' );
if ( ! current_user_can( 'upload_files' ) || empty( $_FILES['plik'] ) ) { wp_die(); }
wp_handle_upload( $_FILES['plik'], array(
'test_form' => false,
'mimes' => array( 'jpg|jpeg' => 'image/jpeg', 'png' => 'image/png', 'pdf' => 'application/pdf' ),
) );
Czy wp_handle_upload z listą mimes sprawdza też treść pliku? Częściowo. Zajrzałem do kodu funkcji wp_check_filetype_and_ext(), której używa. Najpierw sprawdza rozszerzenie z Twoją listą. Przy obrazach odczytuje prawdziwy typ obrazu (PNG z końcówką .jpg dostaje poprawne rozszerzenie), a przy innych plikach porównuje typ z rozszerzeniem PHP fileinfo, jeśli serwer je ma. W moim teście skrypt PHP przemianowany na .jpg i na .pdf został odrzucony. Ale plik z losowymi bajtami i końcówką .pdf przeszedł jako PDF, bo fileinfo nie rozpoznało typu. To dobra bariera, nie skaner antywirusowy.
7. Klucze API i hasła w kodzie
// ZŁE, nie używaj: klucz w kodzie wtyczki trafi do repozytorium, paczki ZIP i kopii
define( 'WM_OPENAI_KEY', 'sk-...' );
// DOBRE: stała w wp-config.php poza repozytorium, w kodzie tylko odczyt
$klucz = defined( 'WM_OPENAI_KEY' ) ? WM_OPENAI_KEY : '';
Stałą definiujesz w pliku wp-config.php, który nie trafia do repozytorium. Pamiętaj też, że klucz zapisany w ustawieniach WordPressa widzi każdy administrator i każde narzędzie z dostępem administratora do API.
Bonus - funkcja, której nie ma w Twojej wersji WordPressa
AI zna najnowsze API i chętnie z niego korzysta. Problem w tym, że Twoja wtyczka deklaruje starszą minimalną wersję WordPressa. Efekt: błąd krytyczny na stronach ze starszym WordPressem. Opisuję to niżej na naszym przykładzie.
Nasza wtyczka napisana z Claude Code - 34 znaleziska
Nie piszę tego z teorii. Naszą wtyczkę do zgód cookies napisaliśmy z Claude Code. Każda zmiana w kodzie powstała we współpracy z modelem.
Zanim uznaliśmy ją za gotową, kod przeszedł kilka rund niezależnej recenzji. Recenzentami były modele AI uruchamiane z czystym kontekstem, bez wiedzy o tym, jak powstawał kod (głównie Codex, w części rund także Claude). W rundach dla kolejnych wersji recenzenci znaleźli łącznie 34 błędy i słabości. Ostatnia runda zakończyła się bez żadnego błędu blokującego.
Trzy, które najlepiej pokazują, czego szukać:
- XSS przez ponowne dekodowanie atrybutów. Kod dekodował zawartość atrybutu ramki, a potem wypisywał ją przez
esc_attr(). Tyle żeesc_attr()nie koduje ponownie encji, które już istnieją. Nieszkodliwy tekst po kliknięciu zamieniał się w skrypt. Potwierdziliśmy to w przeglądarce. Wniosek: „użyłemesc_attr” nie zawsze znaczy „jest bezpiecznie”. - Błąd krytyczny na WordPressie 6.3 i 6.4. Kod używał funkcji
next_token(), która istnieje dopiero od WordPressa 6.5. A wtyczka deklarowała niższą minimalną wersję. - Wyrażenia regularne zamiast parsera HTML. Nietypowy, ale poprawny zapis ramki omijał blokadę, a bardzo długie atrybuty potrafiły zawiesić dopasowanie. Poprawka: HTML przetwarzamy parserem WordPressa, a zasada brzmi „jeśli nie rozpoznajesz, zablokuj, nie przepuszczaj”.
Dwie lekcje z tych rund. Po pierwsze, dwie niezależne recenzje (Codex i Claude z czystym kontekstem) znalazły w dużej części różne błędy. Po drugie, kryterium zakończenia trzeba ustalić z góry. Inaczej każda runda znajduje coraz rzadsze przypadki i praca nigdy się nie kończy.
Jak sprawdzić wtyczkę przed wdrożeniem - krok po kroku
Kopia i środowisko testowe
Pierwsza instalacja nigdy na produkcji. Zrób kopię zapasową i testuj na stronie testowej albo na lokalnym WordPressie. Włącz tam WP_DEBUG i log błędów.
Plugin Check
Plugin Check to oficjalna wtyczka WordPress.org, która sprawdza kod pod kątem wymagań katalogu, w tym bezpieczeństwa. Działa z panelu albo z WP-CLI:
wp plugin install plugin-check --activate
wp plugin check moja-wtyczka
Sam producent pisze uczciwie, że narzędzie „is not perfect, and never will be” i nie zastępuje ręcznego przeglądu.

PHPCS z WordPress Coding Standards
PHP_CodeSniffer z zestawem reguł WordPress Coding Standards uruchamiasz z terminala:
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer require --dev wp-coding-standards/wpcs:"^3.0"
vendor/bin/phpcs --standard=WordPress moja-wtyczka/
Używaj pełnego zestawu WordPress, nie samego WordPress-Extra. W moim teście WordPress-Extra nie zgłosił ani jednego braku sanityzacji, bo reguła sprawdzająca dane wejściowe jest tylko w pełnym zestawie. Pełny zestaw zgłasza też brakujące komentarze w kodzie, ale to łatwo odsiać.

Każde phpcs:ignore w kodzie przejrzyj osobno. Powinno mieć uzasadnienie, a nie być sposobem na ucieszenie narzędzia.
Czego automaty nie wyłapią - mój test
21.10.2026 zrobiłem prosty test. Na lokalnym WordPressie 7.1.3 (z bazą SQLite) przygotowałem dwie małe wtyczki: złą, z dziurami opisanymi wyżej, i dobrą, z poprawkami. Puściłem na nie Plugin Check 2.1.0 (z WP-CLI) oraz PHPCS 3.13.6 z WordPress Coding Standards 3.4.1 (pełny zestaw WordPress i dla porównania WordPress-Extra).
| Dziura w złej wtyczce | PHPCS (zestaw WordPress) | Plugin Check 2.1.0 |
|---|---|---|
| AJAX bez nonce | wykrył (błąd) | wykrył (ostrzeżenie) |
AJAX bez current_user_can | nie wykrył | nie wykrył |
Brak sanityzacji $_POST i $_GET | wykrył (błąd); WordPress-Extra nie wykrył | wykrył (ostrzeżenie) |
| Wypisanie bez escapowania | wykrył | wykrył (błąd) |
SQL bez prepare | wykrył | wykrył (błąd) |
REST bez permission_callback | nie wykrył | nie wykrył (WordPress ostrzega dopiero w działaniu) |
| Upload bez kontroli typu | tylko brak nonce i sanityzacji | pośrednio (zakazana funkcja move_uploaded_file()) |
| Klucz API w kodzie | nie wykrył | nie wykrył |
Dobra wersja: PHPCS bez błędów (tylko ostrzeżenia o bezpośrednim zapytaniu do bazy bez cache i o odczycie $_GET bez nonce), Plugin Check tylko z uwagami do nagłówka i pliku readme oraz z tymi samymi ostrzeżeniami.
Co z tego wynika? Narzędzia łapią większość błędów na wejściu i wyjściu danych. Nie łapią trzech rzeczy: braku sprawdzenia uprawnień, otwartego punktu REST i kluczy w kodzie. A to właśnie te, które pozwalają obcej osobie coś na stronie zmienić albo przejąć dostęp do usług.
Zastrzeżenie: to test na jednym małym pliku, nie badanie. Ale pokazuje kierunek.
Te trzy rzeczy sprawdzisz ręcznie, wyszukując w kodzie: register_rest_route, wp_ajax_, admin_post_, current_user_can, sk-, api_key. Do kluczy dorzuć narzędzie typu gitleaks.
Druga para oczu
Poproś o recenzję inny model z czystym kontekstem albo człowieka, który zna WordPressa. Ale propozycji poprawki recenzenta też nie przyjmuj w ciemno. Sprawdź ją testem.
Test ręczny jako zwykły użytkownik i bez logowania
Zaloguj się jako Subskrybent, a w sklepie jako Klient WooCommerce. Spróbuj wywołać akcje administratora. Potem to samo bez logowania. Jeśli coś się udało, masz dziurę z punktu 1 albo 5.
Checklista przed wgraniem wtyczki z AI na stronę
Przed testami
- Kopia strony (pliki i baza) i miejsce testów: staging albo lokalny WordPress.
- Minimalne wersje WordPressa i PHP w nagłówku wtyczki. Sprawdź, czy kod nie używa nowszego API.
- Lista wszystkich wejść:
wp_ajax_,wp_ajax_nopriv_,admin_post_,register_rest_route, shortcode, formularze,$_GET,$_POST,$_FILES,$_COOKIE.
Kod
- Każda akcja zapisu: nonce i
current_user_can()z najwęższym uprawnieniem. - Każdy punkt REST ma
permission_callback.__return_truetylko świadomie, z walidacją i limitem. - Wejście:
wp_unslash()i sanityzacja albo walidacja (lepiej odrzucić niż czyścić). - Wyjście:
esc_html,esc_attr,esc_url,wp_ksesw chwili wypisania. - SQL tylko przez
$wpdb->prepare()albo API WordPressa. LIKE przezesc_like. - Upload przez
wp_handle_uploadz listą typów i uprawnieniemupload_files. - Zero kluczy i haseł w kodzie. Stała w
wp-config.phppoza repozytorium. - Brak
evaloraz pobierania i uruchamiania kodu z zewnątrz. - HTML przetwarzany parserem WordPressa, nie wyrażeniami regularnymi.
Narzędzia
- Plugin Check: zero błędów w kategorii bezpieczeństwa.
- PHPCS z pełnym zestawem
WordPress: każdephpcs:ignorez uzasadnieniem. WP_DEBUGna stagingu bez ostrzeżeń, w tym_doing_it_wrong.- Druga para oczu: inny model z czystym kontekstem albo człowiek.
Test ręczny
- Niezalogowany, Subskrybent, Klient WooCommerce: czy da się wywołać akcje administratora.
- Po wdrożeniu: sprawdź, jak strona wygląda i jak się ją edytuje, przejrzyj log błędów z 24 godzin, miej pod ręką poprzednią wersję ZIP.
Checklista zmniejsza ryzyko. Nie gwarantuje, że wtyczka jest bezpieczna.
Prompt, który zmniejsza liczbę poprawek
Dobry prompt nie zastąpi kontroli, ale oszczędzi Ci kilku rund. Dopisz do swojego polecenia coś w tym stylu:
Pisz wtyczkę WordPress zgodnie z WordPress Coding Standards.
Minimalna wersja WordPressa: 6.5, PHP: 7.4. Nie używaj funkcji nowszych niż te wersje.
Każda akcja zapisu: nonce i current_user_can() z najwęższym uprawnieniem.
Każdy register_rest_route ma permission_callback.
Dane wejściowe: wp_unslash() i sanityzacja. Wyjście: escapowanie w chwili wypisania.
SQL tylko przez $wpdb->prepare(). HTML przetwarzaj parserem WordPressa, nie regexem.
Żadnych kluczy ani haseł w kodzie. Bez eval i bez pobierania kodu z zewnątrz.
Na końcu wypisz listę wszystkich punktów wejścia i sposób ich zabezpieczenia.
Wersje WordPressa i PHP wpisz swoje.
Kiedy nie wgrywać i oddać kod do przeglądu
Sam sprawdzisz prostą wtyczkę, która coś wyświetla. Ale oddaj kod komuś, kto zna WordPressa, gdy wtyczka:
- zbiera albo przetwarza dane osobowe,
- dotyka płatności lub zamówień w WooCommerce,
- przyjmuje pliki od użytkowników,
- ma publiczny punkt zapisu (formularz, REST, AJAX dla niezalogowanych),
- przechowuje klucze do zewnętrznych usług.
AI potrafi zrobić proste rozwiązanie w chwilę. Skomplikowane systemy nadal wymagają czasu i testów.
FAQ
Czy wtyczkę z ChatGPT można bezpiecznie wgrać na stronę?
Tak, ale dopiero po kontroli z checklisty i najpierw na kopii strony. Samo to, że działa, niczego nie dowodzi.
Czy Plugin Check wystarczy?
Nie. Sam producent pisze, że nie zastępuje ręcznego przeglądu. W moim teście nie wykrył braku sprawdzenia uprawnień, otwartego punktu REST ani klucza API w kodzie.
Czym różni się nonce od sprawdzenia uprawnień?
Nonce chroni przed CSRF: pozwala sprawdzić, że żądanie przyszło z formularza albo linku wygenerowanego przez Twoją stronę dla tego użytkownika i tej akcji. Nie dowodzi świadomego zamiaru użytkownika. current_user_can() sprawdza, czy użytkownik ma prawo wykonać akcję. WordPress wprost pisze, że nonce nie służy do uwierzytelniania, autoryzacji ani kontroli dostępu.
Czy __return_true w permission_callback to błąd?
Nie zawsze. Gdy punkt ma być publiczny, to świadoma decyzja, ale wtedy trzeba go ograniczyć walidacją danych i limitem zapisów.
Czy WordPress.org przyjmie wtyczkę napisaną przez AI?
Katalog nie przyjmuje nowych wtyczek, które generują i uruchamiają kod na stronie. Pozostałe przechodzą ten sam przegląd, w tym automatyczny Plugin Check.
Czy nowsze modele piszą bezpieczniejszy kod?
Według Veracode bezpieczeństwo kodu nie rosło razem z rozmiarem modelu. Ich badanie nie obejmowało jednak PHP.
Jak sprawdzić wtyczkę bez znajomości PHP?
Puść Plugin Check z panelu, przetestuj stronę jako Subskrybent i bez logowania, a przy wtyczkach z listy „kiedy nie wgrywać” oddaj kod do przeglądu.
Podsumowanie
AI przyspiesza pisanie wtyczek. Ale błędy też robi szybciej. Kod, który działa, może mieć otwarty punkt REST, brak sprawdzenia uprawnień albo klucz w środku, a darmowe narzędzia tego nie wyłapią. Kopia, staging, Plugin Check, PHPCS, ręczne sprawdzenie trzech ślepych punktów i test jako zwykły użytkownik. Dopiero potem produkcja.
Nie chcesz sam pilnować kodu, aktualizacji i zabezpieczeń? Zajmujemy się tym w ramach opieki nad stroną. A jeśli po wgraniu jakiejś wtyczki strona zachowuje się podejrzanie, zobacz odwirusowanie WordPressa. Więcej o ogólnym zabezpieczaniu strony przeczytasz w poradniku jak zabezpieczać strony na WordPressie.


