{"id":20859,"date":"2026-10-05T20:07:00","date_gmt":"2026-10-05T18:07:00","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/?p=20859"},"modified":"2026-09-30T12:13:32","modified_gmt":"2026-09-30T10:13:32","slug":"sanityzacja-escaping-nonce-capability-wtyczki","status":"publish","type":"post","link":"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/","title":{"rendered":"Sanityzacja, escaping, Nonce i Capability w przegl\u0105dzie wtyczek"},"content":{"rendered":"<p>Uwagi dotycz\u0105ce bezpiecze\u0144stwa, kt\u00f3re wracaj\u0105 z przegl\u0105du wtyczek na WordPress.org, rzadko dotycz\u0105 czego\u015b egzotycznego. Strona \u201eCommon issues\u201d zespo\u0142u Plugin Review zaczyna si\u0119 od sekcji \u201eSecurity\u201d, a jej punkty s\u0105 elementarne: dane wej\u015bciowe bez sanityzacji, dane wyj\u015bciowe bez escapingu, warto\u015bci Nonce przekazywane dalej bez sanityzacji, zapytania SQL budowane bez <code>prepare()<\/code>. Rozdzia\u0142 Security w Developer Handbook uzupe\u0142nia ten obraz o Nonce i Capability. Ostatni z tych punkt\u00f3w naj\u0142atwiej przeoczy\u0107: handler formularza sprawdza Nonce, ale nie sprawdza, czy zalogowany u\u017cytkownik w og\u00f3le ma prawo wykona\u0107 dan\u0105 akcj\u0119, a testy bezpiecze\u0144stwa w Plugin Check tej luki nie zg\u0142aszaj\u0105.<\/p>\n<p>Artyku\u0142 omawia cztery b\u0142\u0119dy na przyk\u0142adzie niewielkiej wtyczki z panelem administracyjnym. Wersja 0.1 zawiera wszystkie b\u0142\u0119dy naraz, ka\u017cda kolejna usuwa jeden z nich, wi\u0119c przy ka\u017cdym b\u0142\u0119dzie stoi kod wadliwy i kod poprawiony. Na ko\u0144cu dochodzi cz\u0119\u015b\u0107 o trasach REST, w kt\u00f3rych kontrola uprawnie\u0144 nazywa si\u0119 <code>permission_callback<\/code> i od WordPressa 5.5 jest formalnie obowi\u0105zkowa. Opisy funkcji pochodz\u0105 z Developer Handbook i Code Reference na developer.wordpress.org, wed\u0142ug stanu dla WordPressa 7.1.2.<\/p>\n<h2>Zasada: sanitize early, escape late, always validate<\/h2>\n<p>Strona \u201eCommon issues\u201d streszcza wymaganie w jednym zdaniu: \u201eSanitize early, Escape Late, Always Validate\u201d. Rozdzia\u0142 Security w Common APIs Handbook ujmuje to samo w postaci zasad przewodnich, w\u015br\u00f3d nich \u201eNever trust user input\u201d, \u201eEscape as late as possible\u201d oraz \u201eEscape everything from untrusted sources\u201d, przy czym do niezaufanych \u017ar\u00f3de\u0142 wprost zalicza baz\u0119 danych. Kolejna zasada m\u00f3wi, \u017ce sanityzacja jest w porz\u0105dku, ale walidacja i odrzucenie danych s\u0105 lepsze.<\/p>\n<p>Te trzy poj\u0119cia oznaczaj\u0105 r\u00f3\u017cne czynno\u015bci. <strong>Walidacja<\/strong> por\u00f3wnuje dane z ustalonym wzorcem i daje jednoznaczny wynik: poprawne albo niepoprawne; podr\u0119cznik zaleca listy dozwolonych warto\u015bci por\u00f3wnywane przez <code>in_array()<\/code> w trybie \u015bcis\u0142ym. <strong>Sanityzacja<\/strong> filtruje dane wej\u015bciowe tak, aby da\u0142o si\u0119 je zapisa\u0107, na przyk\u0142ad przez <code>sanitize_text_field()<\/code>. <strong>Escaping<\/strong> zabezpiecza warto\u015b\u0107 dla dok\u0142adnie jednego kontekstu wyj\u015bcia: tekstu HTML, atrybutu, adresu URL lub pola textarea. Strona \u201eCommon issues\u201d podkre\u015bla, \u017ce obie grupy funkcji nie s\u0105 wymienne: funkcje escapuj\u0105ce nie nadaj\u0105 si\u0119 do sanityzacji, a funkcje sanityzuj\u0105ce do escapingu.<\/p>\n<p>Przy \u017c\u0105daniach zmieniaj\u0105cych dane przed wszystkimi trzema etapami stoi jeszcze bramka: czy ten u\u017cytkownik ma do tego prawo (Capability) i czy \u017c\u0105danie pochodzi z w\u0142asnego formularza wtyczki (Nonce)? Diagram pokazuje drog\u0119 warto\u015bci przez te etapy wraz z funkcjami u\u017cywanymi na ka\u017cdym z nich.<\/p>\n<figure class=\"lw-diagram\">\n<img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/diagrams\/wp-sicherheitsbefunde-pl.png?v=20260930\" width=\"1120\" height=\"580\" alt=\"Jedna warto\u015b\u0107, sze\u015b\u0107 etap\u00f3w: od \u017c\u0105dania do bezpiecznego wyj\u015bcia\"><figcaption>Najpierw bramka, potem wczesna sanityzacja i walidacja, zapis z symbolami zast\u0119pczymi, p\u00f3\u017any escaping<\/figcaption><\/figure>\n<h2>Przyk\u0142adowa wtyczka w wersji 0.1<\/h2>\n<p>Wtyczka \u201eLW Notice Box\u201d dodaje stron\u0119 w menu Ustawienia. Zapisuje etykiet\u0119, link, komunikat mog\u0105cy zawiera\u0107 HTML oraz liczb\u0119 wy\u015bwietlanych wierszy dziennika. Ka\u017cda zmiana trafia jako wiersz do w\u0142asnej tabeli, shortcode wy\u015bwietla komunikat na stronie, a trasa REST pozwala zmieni\u0107 etykiet\u0119. Plik jest kompletny i dzia\u0142a, ale celowo jest niebezpieczny:<\/p>\n<pre><code>&lt;?php\n\/**\n * Plugin Name:       LW Notice Box\n * Description:       Demo plugin for the security review. Version 0.1 contains deliberate flaws.\n * Version:           0.1.0\n * Requires at least: 6.2\n * Requires PHP:      7.4\n * License:           GPL-2.0-or-later\n * Text Domain:       lw-notice-box\n *\/\n\nif ( ! defined( 'ABSPATH' ) ) {\n\texit;\n}\n\nregister_activation_hook( __FILE__, 'lw_nb_install' );\n\n\/**\n * Creates the log table (unchanged in all versions).\n *\/\nfunction lw_nb_install() {\n\tglobal $wpdb;\n\trequire_once ABSPATH . 'wp-admin\/includes\/upgrade.php';\n\n\t$table   = $wpdb-&gt;prefix . 'lw_nb_log';\n\t$charset = $wpdb-&gt;get_charset_collate();\n\n\tdbDelta(\n\t\t\"CREATE TABLE $table (\n\t\t\tid bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n\t\t\tuser_id bigint(20) unsigned NOT NULL DEFAULT 0,\n\t\t\tlabel varchar(100) NOT NULL DEFAULT '',\n\t\t\tsaved_at datetime NOT NULL,\n\t\t\tPRIMARY KEY  (id)\n\t\t) $charset;\"\n\t);\n}\n\n\/**\n * Default values of the stored option (unchanged in all versions).\n *\/\nfunction lw_nb_defaults() {\n\treturn array(\n\t\t'label'   =&gt; '',\n\t\t'link'    =&gt; '',\n\t\t'message' =&gt; '',\n\t\t'rows'    =&gt; 10,\n\t);\n}\n\nadd_action( 'admin_menu', 'lw_nb_menu' );\n\n\/**\n * Adds the settings page (unchanged in all versions).\n *\/\nfunction lw_nb_menu() {\n\tadd_options_page( 'Notice Box', 'Notice Box', 'manage_options', 'lw-notice-box', 'lw_nb_render_page' );\n}\n\n\/\/ FLAW (findings 1 and 2): $_GET value inside SQL, unescaped output.\nfunction lw_nb_render_page() {\n\tglobal $wpdb;\n\t$opt     = wp_parse_args( get_option( 'lw_nb_notice', array() ), lw_nb_defaults() );\n\t$orderby = isset( $_GET['orderby'] ) ? $_GET['orderby'] : 'saved_at';\n\t$rows    = $wpdb-&gt;get_results( \"SELECT user_id, label, saved_at FROM {$wpdb-&gt;prefix}lw_nb_log ORDER BY $orderby DESC LIMIT {$opt['rows']}\" );\n\t?&gt;\n\t&lt;div class=\"wrap\"&gt;\n\t\t&lt;h1&gt;Notice Box&lt;\/h1&gt;\n\t\t&lt;form method=\"post\" action=\"&lt;?php echo admin_url( 'admin-post.php' ); ?&gt;\"&gt;\n\t\t\t&lt;input type=\"hidden\" name=\"action\" value=\"lw_nb_save\"&gt;\n\t\t\t&lt;p&gt;&lt;input type=\"text\" name=\"label\" value=\"&lt;?php echo $opt['label']; ?&gt;\"&gt;&lt;\/p&gt;\n\t\t\t&lt;p&gt;&lt;input type=\"url\" name=\"link\" value=\"&lt;?php echo $opt['link']; ?&gt;\"&gt;&lt;\/p&gt;\n\t\t\t&lt;p&gt;&lt;textarea name=\"message\"&gt;&lt;?php echo $opt['message']; ?&gt;&lt;\/textarea&gt;&lt;\/p&gt;\n\t\t\t&lt;p&gt;&lt;input type=\"number\" name=\"rows\" value=\"&lt;?php echo $opt['rows']; ?&gt;\"&gt;&lt;\/p&gt;\n\t\t\t&lt;p&gt;&lt;button type=\"submit\" class=\"button button-primary\"&gt;Save&lt;\/button&gt;&lt;\/p&gt;\n\t\t&lt;\/form&gt;\n\t\t&lt;ul&gt;\n\t\t\t&lt;?php foreach ( $rows as $row ) : ?&gt;\n\t\t\t\t&lt;li&gt;&lt;?php echo $row-&gt;saved_at . ' | ' . $row-&gt;label; ?&gt;&lt;\/li&gt;\n\t\t\t&lt;?php endforeach; ?&gt;\n\t\t&lt;\/ul&gt;\n\t&lt;\/div&gt;\n\t&lt;?php\n}\n\nadd_action( 'admin_post_lw_nb_save', 'lw_nb_save' );\n\n\/\/ FLAW (findings 1, 3 and 4): no capability, no nonce, raw $_POST, SQL by concatenation.\nfunction lw_nb_save() {\n\tglobal $wpdb;\n\tupdate_option( 'lw_nb_notice', $_POST );\n\t$wpdb-&gt;query( \"INSERT INTO {$wpdb-&gt;prefix}lw_nb_log (user_id, label, saved_at) VALUES (\" . get_current_user_id() . \", '\" . $_POST['label'] . \"', NOW())\" );\n\twp_redirect( admin_url( 'options-general.php?page=lw-notice-box' ) );\n\texit;\n}\n\nadd_shortcode( 'lw_notice', 'lw_nb_shortcode' );\n\n\/\/ FLAW (finding 2): stored values go to the front end unescaped.\nfunction lw_nb_shortcode() {\n\t$opt = wp_parse_args( get_option( 'lw_nb_notice', array() ), lw_nb_defaults() );\n\treturn '&lt;div class=\"lw-notice\"&gt;&lt;a href=\"' . $opt['link'] . '\"&gt;' . $opt['label'] . '&lt;\/a&gt;' . $opt['message'] . '&lt;\/div&gt;';\n}\n\nadd_action( 'rest_api_init', 'lw_nb_register_routes' );\n\n\/\/ FLAW (REST): no permission_callback, the route is public.\nfunction lw_nb_register_routes() {\n\tregister_rest_route(\n\t\t'lw-nb\/v1',\n\t\t'\/notice',\n\t\tarray(\n\t\t\t'methods'  =&gt; 'POST',\n\t\t\t'callback' =&gt; 'lw_nb_rest_update',\n\t\t)\n\t);\n}\n\nfunction lw_nb_rest_update( WP_REST_Request $request ) {\n\t$opt          = wp_parse_args( get_option( 'lw_nb_notice', array() ), lw_nb_defaults() );\n\t$opt['label'] = $request['label'];\n\tupdate_option( 'lw_nb_notice', $opt );\n\treturn rest_ensure_response( $opt );\n}<\/code><\/pre>\n<p>Komentarze \u201eFLAW\u201d wskazuj\u0105, gdzie tkwi kt\u00f3ry b\u0142\u0105d. Funkcje <code>lw_nb_install()<\/code>, <code>lw_nb_defaults()<\/code> i <code>lw_nb_menu()<\/code> pozostaj\u0105 takie same we wszystkich wersjach; pozosta\u0142e zostaj\u0105 w kolejnych sekcjach zast\u0105pione funkcjami o tej samej nazwie. Po podmianie wszystkich poprawionych funkcji i podniesieniu numeru wersji powstaje wersja 1.0.<\/p>\n<h2>B\u0142\u0105d 1: dane wej\u015bciowe przyj\u0119te bez kontroli<\/h2>\n<p>Handler z wersji 0.1 zapisuje w opcji ca\u0142\u0105 tablic\u0119 <code>$_POST<\/code>. Do bazy trafia wi\u0119c tak\u017ce pole <code>action<\/code> i ka\u017cde dodatkowe pole, jakie wy\u015ble atakuj\u0105cy, i to wci\u0105\u017c z uko\u015bnikami, kt\u00f3re WordPress podczas \u0142adowania dokleja w <code>wp_magic_quotes()<\/code> do <code>$_GET<\/code>, <code>$_POST<\/code>, <code>$_COOKIE<\/code> i <code>$_SERVER<\/code>. Strona \u201eCommon issues\u201d zdecydowanie odradza przetwarzanie ca\u0142ego stosu <code>$_POST<\/code>, <code>$_REQUEST<\/code> czy <code>$_GET<\/code>.<\/p>\n<pre><code>\/\/ Version 0.1: the whole, still slashed $_POST array lands in the option,\n\/\/ the label is concatenated into SQL.\nupdate_option( 'lw_nb_notice', $_POST );\n$wpdb-&gt;query( \"INSERT INTO {$wpdb-&gt;prefix}lw_nb_log (user_id, label, saved_at) VALUES (\" . get_current_user_id() . \", '\" . $_POST['label'] . \"', NOW())\" );<\/code><\/pre>\n<p>Wersja 0.2 odczytuje wy\u0142\u0105cznie cztery potrzebne pola. Ka\u017cde przechodzi najpierw przez <code>wp_unslash()<\/code>, kt\u00f3ra wed\u0142ug Code Reference usuwa uko\u015bniki z ci\u0105gu znak\u00f3w albo rekurencyjnie z ci\u0105g\u00f3w w tablicy, a potem przez odpowiedni\u0105 funkcj\u0119 sanityzuj\u0105c\u0105. <code>sanitize_text_field()<\/code> sprawdza poprawno\u015b\u0107 UTF-8, zamienia pojedyncze znaki <code>&lt;<\/code> na encje, usuwa wszystkie znaczniki, a tak\u017ce podzia\u0142y wierszy, tabulatory i nadmiarowe spacje. <code>sanitize_url()<\/code> otrzymuje list\u0119 dozwolonych protoko\u0142\u00f3w, a <code>wp_kses_post()<\/code> zostawia tylko HTML dozwolony w tre\u015bci wpis\u00f3w. Dla liczby wierszy wystarczy <code>absint()<\/code>, kt\u00f3ra zamienia dowoln\u0105 warto\u015b\u0107 na nieujemn\u0105 liczb\u0119 ca\u0142kowit\u0105.<\/p>\n<pre><code>\/**\n * Handles the settings form (version 0.2: input is cleaned and validated).\n * Still missing: nonce (finding 3) and capability check (finding 4).\n *\/\nfunction lw_nb_save() {\n\t\/\/ Sanitize early: read only the fields we need, unslash, then clean.\n\t$label   = isset( $_POST['label'] ) ? sanitize_text_field( wp_unslash( $_POST['label'] ) ) : '';\n\t$link    = isset( $_POST['link'] ) ? sanitize_url( wp_unslash( $_POST['link'] ), array( 'http', 'https' ) ) : '';\n\t$message = isset( $_POST['message'] ) ? wp_kses_post( wp_unslash( $_POST['message'] ) ) : '';\n\t$rows    = isset( $_POST['rows'] ) ? absint( $_POST['rows'] ) : 10;\n\n\t\/\/ Always validate: reject what does not fit instead of storing it.\n\tif ( '' === $label || mb_strlen( $label ) &gt; 100 ) {\n\t\twp_die(\n\t\t\tesc_html__( 'The label must contain 1 to 100 characters.', 'lw-notice-box' ),\n\t\t\t'',\n\t\t\tarray( 'back_link' =&gt; true )\n\t\t);\n\t}\n\tif ( $rows &lt; 1 || $rows &gt; 50 ) {\n\t\t$rows = 10;\n\t}\n\n\tupdate_option(\n\t\t'lw_nb_notice',\n\t\tarray(\n\t\t\t'label'   =&gt; $label,\n\t\t\t'link'    =&gt; $link,\n\t\t\t'message' =&gt; $message,\n\t\t\t'rows'    =&gt; $rows,\n\t\t)\n\t);\n\tlw_nb_log_change( $label );\n\n\twp_safe_redirect( admin_url( 'options-general.php?page=lw-notice-box' ) );\n\texit;\n}\n\n\/**\n * Writes one log row. $wpdb-&gt;insert() prepares the values via the format list.\n *\n * @param string $label Already sanitized label.\n *\/\nfunction lw_nb_log_change( $label ) {\n\tglobal $wpdb;\n\n\t\/\/ phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table without a core API.\n\t$wpdb-&gt;insert(\n\t\t$wpdb-&gt;prefix . 'lw_nb_log',\n\t\tarray(\n\t\t\t'user_id'  =&gt; get_current_user_id(),\n\t\t\t'label'    =&gt; $label,\n\t\t\t'saved_at' =&gt; current_time( 'mysql' ),\n\t\t),\n\t\tarray( '%d', '%s', '%s' )\n\t);\n}<\/code><\/pre>\n<p>Po sanityzacji nast\u0119puje walidacja. Pusta lub zbyt d\u0142uga etykieta nie jest po cichu przycinana, lecz odrzucana. Liczba wierszy musi mie\u015bci\u0107 si\u0119 mi\u0119dzy 1 a 50, w przeciwnym razie obowi\u0105zuje warto\u015b\u0107 domy\u015blna. Te granice to regu\u0142y biznesowe wtyczki i \u017cadna funkcja sanityzuj\u0105ca ich nie zna. Wpis w dzienniku zapisuje teraz <code>$wpdb-&gt;insert()<\/code> z list\u0105 format\u00f3w, kt\u00f3ra okre\u015bla ka\u017cd\u0105 warto\u015b\u0107 jako liczb\u0119 ca\u0142kowit\u0105 lub ci\u0105g znak\u00f3w.<\/p>\n<p>Komentarz <code>phpcs:ignore<\/code> odpowiada na ostrze\u017cenie, kt\u00f3re Plugin Check zg\u0142asza przy ka\u017cdym bezpo\u015brednim dost\u0119pie do bazy. Dla w\u0142asnej tabeli nie istnieje API w rdzeniu, dlatego uzasadnienie znajduje si\u0119 w komentarzu. Tym, jak Plugin Check klasyfikuje takie komunikaty, zajmuje si\u0119 <span class=\"lw-artikel-geplant\">osobny artyku\u0142 z tej serii<\/span>.<\/p>\n<h3>Przypadek szczeg\u00f3lny SQL: $wpdb-&gt;prepare() z %s, %d i %i<\/h3>\n<p>Sanityzacja nie chroni przed SQL injection. Przy w\u0142asnych zapytaniach strona \u201eCommon issues\u201d wymaga metod <code>wpdb<\/code> w po\u0142\u0105czeniu z <code>prepare()<\/code>. B\u0142\u0105d w wersji 0.1 kryje si\u0119 w sortowaniu:<\/p>\n<pre><code>\/\/ Version 0.1: ?orderby=... goes straight into the query. ORDER BY needs no\n\/\/ quotes, so the slashes added by wp_magic_quotes() do not help here.\n$orderby = isset( $_GET['orderby'] ) ? $_GET['orderby'] : 'saved_at';\n$rows    = $wpdb-&gt;get_results( \"SELECT user_id, label, saved_at FROM {$wpdb-&gt;prefix}lw_nb_log ORDER BY $orderby DESC LIMIT {$opt['rows']}\" );<\/code><\/pre>\n<p>Parametr trafia do <code>ORDER BY<\/code> bez cudzys\u0142ow\u00f3w, wi\u0119c dodatkowe uko\u015bniki z <code>wp_magic_quotes()<\/code> niczego tam nie zmieniaj\u0105. Wed\u0142ug Code Reference <code>$wpdb-&gt;prepare()<\/code> obs\u0142uguje symbole zast\u0119pcze <code>%d<\/code> (liczba ca\u0142kowita), <code>%f<\/code> (liczba zmiennoprzecinkowa), <code>%s<\/code> (ci\u0105g znak\u00f3w) oraz <code>%i<\/code> (identyfikator, czyli nazwa tabeli lub kolumny). <code>%i<\/code> pojawi\u0142 si\u0119 w WordPressie 6.2, dlatego przyk\u0142adowa wtyczka deklaruje w nag\u0142\u00f3wku \u201eRequires at least: 6.2\u201d. Symbole zast\u0119pcze pozostaj\u0105 w zapytaniu bez cudzys\u0142ow\u00f3w, a ka\u017cdemu odpowiada dok\u0142adnie jeden argument.<\/p>\n<pre><code>\/**\n * Returns the latest log rows.\n *\n * @param string $orderby Requested sort column (untrusted).\n * @param int    $limit   Number of rows.\n * @return array\n *\/\nfunction lw_nb_get_log( $orderby, $limit ) {\n\tglobal $wpdb;\n\n\t\/\/ An identifier placeholder quotes a name, it does not restrict it: allowlist first.\n\t$allowed = array( 'saved_at', 'label', 'user_id' );\n\tif ( ! in_array( $orderby, $allowed, true ) ) {\n\t\t$orderby = 'saved_at';\n\t}\n\n\t\/\/ phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom table, small admin-only list.\n\treturn $wpdb-&gt;get_results(\n\t\t$wpdb-&gt;prepare(\n\t\t\t'SELECT user_id, label, saved_at FROM %i ORDER BY %i DESC LIMIT %d',\n\t\t\t$wpdb-&gt;prefix . 'lw_nb_log',\n\t\t\t$orderby,\n\t\t\tmin( 50, absint( $limit ) )\n\t\t)\n\t);\n}<\/code><\/pre>\n<p>Kolejno\u015b\u0107 krok\u00f3w w tej funkcji ma znaczenie. <code>%i<\/code> poprawnie ujmuje nazw\u0119 w grawisy, ale nie ogranicza tego, jaka nazwa przychodzi; bez listy dozwolonych warto\u015bci nadal da\u0142oby si\u0119 sortowa\u0107 po dowolnej kolumnie tabeli. Lista dozwolonych warto\u015bci jest walidacj\u0105, symbol zast\u0119pczy zabezpieczeniem technicznym. Dla wyszukiwania przez <code>LIKE<\/code> Code Reference podaje dodatkow\u0105 regu\u0142\u0119: ca\u0142y wzorzec razem ze znakami procentu przekazuje si\u0119 jako argument, a szukany ci\u0105g nale\u017cy wcze\u015bniej przepu\u015bci\u0107 przez <code>$wpdb-&gt;esc_like()<\/code>, na przyk\u0142ad <code>$wpdb-&gt;prepare( 'SELECT COUNT(*) FROM %i WHERE label LIKE %s', $table, '%' . $wpdb-&gt;esc_like( $term ) . '%' )<\/code>. Dla list w <code>IN ( \u2026 )<\/code> strona \u201eCommon issues\u201d pokazuje wzorzec, kt\u00f3ry tworzy osobny symbol zast\u0119pczy dla ka\u017cdego elementu.<\/p>\n<h2>B\u0142\u0105d 2: dane wyj\u015bciowe bez escapingu<\/h2>\n<p>Wersja 0.1 wypisuje ka\u017cd\u0105 zapisan\u0105 warto\u015b\u0107 w surowej postaci, zar\u00f3wno w formularzu administracyjnym, jak i w shortcode. Cudzys\u0142\u00f3w w etykiecie wyrywa si\u0119 z atrybutu <code>value<\/code>, a <code>&lt;\/textarea&gt;<\/code> w komunikacie przedwcze\u015bnie zamyka pole tekstowe. Sanityzacja przy zapisie nie czyni escapingu zb\u0119dnym, bo zapisane warto\u015bci mog\u0105 trafi\u0107 do bazy tak\u017ce inn\u0105 drog\u0105: przez import, inn\u0105 wtyczk\u0119 albo starsz\u0105 wersj\u0119 w\u0142asnego kodu.<\/p>\n<pre><code>\/\/ Version 0.1: attribute, textarea content and HTML block all printed raw.\n&lt;input type=\"text\" name=\"label\" value=\"&lt;?php echo $opt['label']; ?&gt;\"&gt;\n&lt;textarea name=\"message\"&gt;&lt;?php echo $opt['message']; ?&gt;&lt;\/textarea&gt;\nreturn '&lt;div class=\"lw-notice\"&gt;&lt;a href=\"' . $opt['link'] . '\"&gt;' . $opt['label'] . '&lt;\/a&gt;' . $opt['message'] . '&lt;\/div&gt;';<\/code><\/pre>\n<p>\u201eEscape late\u201d oznacza escaping bezpo\u015brednio przy <code>echo<\/code> i w spos\u00f3b dopasowany do kontekstu. Rozdzia\u0142 o escapingu w podr\u0119czniku podaje przyporz\u0105dkowanie, a strona \u201eCommon issues\u201d wymaga, aby wszystkie zmienne, opcje i generowane dane escapowa\u0107 w chwili wypisywania, a nie podczas budowania zmiennej.<\/p>\n<table>\n<thead>\n<tr>\n<th>Kontekst wyj\u015bcia<\/th>\n<th>Funkcja<\/th>\n<th>Zastosowanie we wtyczce<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Tekst mi\u0119dzy znacznikami HTML<\/td>\n<td><code>esc_html()<\/code><\/td>\n<td>Tytu\u0142 strony, wiersze dziennika<\/td>\n<\/tr>\n<tr>\n<td>Warto\u015b\u0107 atrybutu HTML<\/td>\n<td><code>esc_attr()<\/code><\/td>\n<td><code>value<\/code> etykiety i liczby wierszy<\/td>\n<\/tr>\n<tr>\n<td>URL w <code>href<\/code>, <code>src<\/code>, <code>action<\/code><\/td>\n<td><code>esc_url()<\/code><\/td>\n<td>Cel formularza, linki sortowania, link komunikatu<\/td>\n<\/tr>\n<tr>\n<td>Zawarto\u015b\u0107 pola textarea<\/td>\n<td><code>esc_textarea()<\/code><\/td>\n<td>Komunikat w formularzu<\/td>\n<\/tr>\n<tr>\n<td>HTML, kt\u00f3ry ma zosta\u0107 zachowany<\/td>\n<td><code>wp_kses_post()<\/code><\/td>\n<td>Komunikat w shortcode<\/td>\n<\/tr>\n<tr>\n<td>Tekst t\u0142umaczony<\/td>\n<td><code>esc_html_e()<\/code>, <code>esc_attr__()<\/code><\/td>\n<td>Etykiety p\u00f3l<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<pre><code>\/**\n * Renders the settings page (version 1.0: every value escaped at output).\n *\/\nfunction lw_nb_render_page() {\n\t$opt = wp_parse_args( get_option( 'lw_nb_notice', array() ), lw_nb_defaults() );\n\n\t\/\/ Read-only sort parameter: it changes nothing and is checked in lw_nb_get_log().\n\t\/\/ phpcs:ignore WordPress.Security.NonceVerification.Recommended\n\t$orderby = isset( $_GET['orderby'] ) ? sanitize_key( wp_unslash( $_GET['orderby'] ) ) : 'saved_at';\n\t$rows    = lw_nb_get_log( $orderby, absint( $opt['rows'] ) );\n\t?&gt;\n\t&lt;div class=\"wrap\"&gt;\n\t\t&lt;h1&gt;&lt;?php echo esc_html( get_admin_page_title() ); ?&gt;&lt;\/h1&gt;\n\t\t&lt;form method=\"post\" action=\"&lt;?php echo esc_url( admin_url( 'admin-post.php' ) ); ?&gt;\"&gt;\n\t\t\t&lt;input type=\"hidden\" name=\"action\" value=\"lw_nb_save\"&gt;\n\t\t\t&lt;?php wp_nonce_field( 'lw_nb_save', 'lw_nb_nonce' ); \/\/ Finding 3. ?&gt;\n\t\t\t&lt;p&gt;\n\t\t\t\t&lt;label for=\"lw-nb-label\"&gt;&lt;?php esc_html_e( 'Label', 'lw-notice-box' ); ?&gt;&lt;\/label&gt;&lt;br&gt;\n\t\t\t\t&lt;input type=\"text\" id=\"lw-nb-label\" name=\"label\" class=\"regular-text\" value=\"&lt;?php echo esc_attr( $opt['label'] ); ?&gt;\"&gt;\n\t\t\t&lt;\/p&gt;\n\t\t\t&lt;p&gt;\n\t\t\t\t&lt;label for=\"lw-nb-link\"&gt;&lt;?php esc_html_e( 'Link', 'lw-notice-box' ); ?&gt;&lt;\/label&gt;&lt;br&gt;\n\t\t\t\t&lt;input type=\"url\" id=\"lw-nb-link\" name=\"link\" class=\"regular-text\" value=\"&lt;?php echo esc_url( $opt['link'] ); ?&gt;\"&gt;\n\t\t\t&lt;\/p&gt;\n\t\t\t&lt;p&gt;\n\t\t\t\t&lt;label for=\"lw-nb-message\"&gt;&lt;?php esc_html_e( 'Message (HTML allowed)', 'lw-notice-box' ); ?&gt;&lt;\/label&gt;&lt;br&gt;\n\t\t\t\t&lt;textarea id=\"lw-nb-message\" name=\"message\" rows=\"4\" class=\"large-text\"&gt;&lt;?php echo esc_textarea( $opt['message'] ); ?&gt;&lt;\/textarea&gt;\n\t\t\t&lt;\/p&gt;\n\t\t\t&lt;p&gt;\n\t\t\t\t&lt;label for=\"lw-nb-rows\"&gt;&lt;?php esc_html_e( 'Log rows (1-50)', 'lw-notice-box' ); ?&gt;&lt;\/label&gt;&lt;br&gt;\n\t\t\t\t&lt;input type=\"number\" id=\"lw-nb-rows\" name=\"rows\" min=\"1\" max=\"50\" value=\"&lt;?php echo esc_attr( $opt['rows'] ); ?&gt;\"&gt;\n\t\t\t&lt;\/p&gt;\n\t\t\t&lt;?php submit_button(); ?&gt;\n\t\t&lt;\/form&gt;\n\t\t&lt;p&gt;\n\t\t\t&lt;a href=\"&lt;?php echo esc_url( add_query_arg( 'orderby', 'saved_at' ) ); ?&gt;\"&gt;&lt;?php esc_html_e( 'Sort by date', 'lw-notice-box' ); ?&gt;&lt;\/a&gt; |\n\t\t\t&lt;a href=\"&lt;?php echo esc_url( add_query_arg( 'orderby', 'label' ) ); ?&gt;\"&gt;&lt;?php esc_html_e( 'Sort by label', 'lw-notice-box' ); ?&gt;&lt;\/a&gt;\n\t\t&lt;\/p&gt;\n\t\t&lt;ul&gt;\n\t\t\t&lt;?php foreach ( $rows as $row ) : ?&gt;\n\t\t\t\t&lt;li&gt;&lt;?php echo esc_html( $row-&gt;saved_at . ' | ' . $row-&gt;label ); ?&gt;&lt;\/li&gt;\n\t\t\t&lt;?php endforeach; ?&gt;\n\t\t&lt;\/ul&gt;\n\t&lt;\/div&gt;\n\t&lt;?php\n}\n\n\/**\n * Front-end output of the notice (version 1.0).\n *\n * @return string\n *\/\nfunction lw_nb_shortcode() {\n\t$opt = wp_parse_args( get_option( 'lw_nb_notice', array() ), lw_nb_defaults() );\n\tif ( '' === $opt['label'] ) {\n\t\treturn '';\n\t}\n\n\treturn sprintf(\n\t\t'&lt;div class=\"lw-notice\"&gt;&lt;a href=\"%1$s\"&gt;%2$s&lt;\/a&gt;%3$s&lt;\/div&gt;',\n\t\tesc_url( $opt['link'] ),\n\t\tesc_html( $opt['label'] ),\n\t\twp_kses_post( $opt['message'] )\n\t);\n}<\/code><\/pre>\n<p>Trzy szczeg\u00f3\u0142y tej wersji zas\u0142uguj\u0105 na uwag\u0119. Wed\u0142ug Code Reference <code>esc_url()<\/code> zwraca pusty ci\u0105g, gdy URL u\u017cywa protoko\u0142u spoza dozwolonej listy, wi\u0119c zapisany link <code>javascript:<\/code> nie zadzia\u0142a. <code>add_query_arg()<\/code> bez trzeciego parametru pracuje na adresie bie\u017c\u0105cego \u017c\u0105dania i dlatego r\u00f3wnie\u017c wymaga escapingu. Z kolei <code>esc_url_raw()<\/code> wed\u0142ug strony \u201eCommon issues\u201d nie jest funkcj\u0105 escapuj\u0105c\u0105, tylko sanityzuj\u0105c\u0105, przeznaczon\u0105 dla bazy danych lub przekierowania, podobnie jak <code>sanitize_url()<\/code>. Plugin Check wykrywa ten b\u0142\u0105d za pomoc\u0105 regu\u0142y <code>WordPress.Security.EscapeOutput<\/code>, ale tylko przy bezpo\u015brednim wypisywaniu, takim jak <code>echo<\/code> lub <code>printf()<\/code>: dla wersji 0.1 zg\u0142osi\u0142 siedem miejsc na stronie ustawie\u0144, a niezabezpieczonej warto\u015bci zwracanej przez shortcode nie.<\/p>\n<h2>B\u0142\u0105d 3: brak Nonce w formularzu<\/h2>\n<p>Bez Nonce dowolna obca strona mo\u017ce zbudowa\u0107 formularz wysy\u0142any do <code>admin-post.php<\/code>. Gdy zalogowany administrator j\u0105 otworzy, przegl\u0105darka do\u0142\u0105czy ciasteczko logowania i zmiana zostanie wykonana na jego koncie. Rozdzia\u0142 o Nonce opisuje je jako ochron\u0119 przed kilkoma rodzajami atak\u00f3w, w tym CSRF; przed atakami typu replay nie chroni\u0105, bo WordPress nie sprawdza, czy dana Nonce zosta\u0142a ju\u017c u\u017cyta. Wed\u0142ug opisu <code>wp_verify_nonce()<\/code> Nonce jest domy\u015blnie wa\u017cna od 12 do 24 godzin, a kod \u017ar\u00f3d\u0142owy <code>wp_create_nonce()<\/code> wylicza j\u0105 z akcji, identyfikatora u\u017cytkownika, tokenu sesji i przedzia\u0142u czasu.<\/p>\n<p>Poprawka sk\u0142ada si\u0119 z dw\u00f3ch cz\u0119\u015bci. Formularz wypisuje ukryte pole przez <code>wp_nonce_field( 'lw_nb_save', 'lw_nb_nonce' )<\/code>; ta linia jest ju\u017c w wersji <code>lw_nb_render_page()<\/code> z opisu b\u0142\u0119du 2. Handler sprawdza pole przez <code>check_admin_referer()<\/code>, zanim odczyta cokolwiek innego:<\/p>\n<pre><code>\/**\n * Handles the settings form (version 0.3: nonce checked).\n * Still missing: capability check (finding 4).\n *\/\nfunction lw_nb_save() {\n\t\/\/ Intent: did the request come from our own form? Dies with a 403 error page otherwise.\n\tcheck_admin_referer( 'lw_nb_save', 'lw_nb_nonce' );\n\n\t$label   = isset( $_POST['label'] ) ? sanitize_text_field( wp_unslash( $_POST['label'] ) ) : '';\n\t$link    = isset( $_POST['link'] ) ? sanitize_url( wp_unslash( $_POST['link'] ), array( 'http', 'https' ) ) : '';\n\t$message = isset( $_POST['message'] ) ? wp_kses_post( wp_unslash( $_POST['message'] ) ) : '';\n\t$rows    = isset( $_POST['rows'] ) ? absint( $_POST['rows'] ) : 10;\n\n\tif ( '' === $label || mb_strlen( $label ) &gt; 100 ) {\n\t\twp_die(\n\t\t\tesc_html__( 'The label must contain 1 to 100 characters.', 'lw-notice-box' ),\n\t\t\t'',\n\t\t\tarray( 'back_link' =&gt; true )\n\t\t);\n\t}\n\tif ( $rows &lt; 1 || $rows &gt; 50 ) {\n\t\t$rows = 10;\n\t}\n\n\tupdate_option(\n\t\t'lw_nb_notice',\n\t\tarray(\n\t\t\t'label'   =&gt; $label,\n\t\t\t'link'    =&gt; $link,\n\t\t\t'message' =&gt; $message,\n\t\t\t'rows'    =&gt; $rows,\n\t\t)\n\t);\n\tlw_nb_log_change( $label );\n\n\twp_safe_redirect( admin_url( 'options-general.php?page=lw-notice-box' ) );\n\texit;\n}<\/code><\/pre>\n<p>Przy nieprawid\u0142owej Nonce <code>check_admin_referer()<\/code> przerywa \u017c\u0105danie przez <code>wp_nonce_ays()<\/code> stron\u0105 b\u0142\u0119du (status 403, \u201eThe link you followed has expired.\u201d); pytanie z potwierdzeniem <code>wp_nonce_ays()<\/code> pokazuje tylko dla akcji <code>log-out<\/code>. Wywo\u0142anie bez nazwy akcji od WordPressa 3.2.0 wywo\u0142uje komunikat <code>_doing_it_wrong()<\/code>, dlatego osobna nazwa akcji dla ka\u017cdego formularza jest obowi\u0105zkowa. Tam, gdzie kontrola odbywa si\u0119 r\u0119cznie przez <code>wp_verify_nonce()<\/code>, strona \u201eCommon issues\u201d wymaga wcze\u015bniejszego przepuszczenia pola przez <code>wp_unslash()<\/code> i <code>sanitize_text_field()<\/code>, poniewa\u017c funkcja jest typu pluggable: <code>wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['lw_nb_nonce'] ) ), 'lw_nb_save' )<\/code>.<\/p>\n<h2>B\u0142\u0105d 4: brak kontroli Capability, czyli dlaczego Nonce nie wystarcza<\/h2>\n<p>Wersja 0.3 wygl\u0105da na bezpieczn\u0105, ale taka nie jest. Code Reference opisuje <code>check_admin_referer()<\/code> jako funkcj\u0119, kt\u00f3ra \u201everifies intent, not authorization\u201d i nie sprawdza uprawnie\u0144 u\u017cytkownika; do tego s\u0142u\u017cy <code>current_user_can()<\/code>. Rozdzia\u0142 o Nonce ujmuje to jeszcze dobitniej: \u201eNonces should never be relied on for authentication, authorization, or access control.\u201d Funkcje nale\u017cy chroni\u0107 przez <code>current_user_can()<\/code> i zawsze zak\u0142ada\u0107, \u017ce Nonce mo\u017ce zosta\u0107 przej\u0119ta.<\/p>\n<p>W przyk\u0142adowej wtyczce wida\u0107 to wyra\u017anie. <code>add_options_page()<\/code> z <code>manage_options<\/code> chroni jedynie wy\u015bwietlanie strony ustawie\u0144. Handler jest natomiast podpi\u0119ty pod <code>admin_post_lw_nb_save<\/code>, a <code>admin-post.php<\/code> wed\u0142ug kodu \u017ar\u00f3d\u0142owego uruchamia ten Hook dla ka\u017cdego zalogowanego u\u017cytkownika, bez w\u0142asnej kontroli uprawnie\u0144. Nonce jest powi\u0105zana z akcj\u0105, u\u017cytkownikiem i sesj\u0105. Je\u015bli jakakolwiek cz\u0119\u015b\u0107 wtyczki wydaje Nonce dla <code>lw_nb_save<\/code> tak\u017ce u\u017cytkownikom o mniejszych uprawnieniach, na przyk\u0142ad w formularzu na stronie, albo je\u015bli wa\u017cna Nonce wycieknie inn\u0105 drog\u0105, zalogowany subskrybent zmieni za jej pomoc\u0105 ustawienia. Nonce odpowiada na pytanie \u201eCzy ten u\u017cytkownik chcia\u0142 wys\u0142a\u0107 to \u017c\u0105danie z tego formularza?\u201d, a nie na pytanie \u201eCzy wolno mu to zrobi\u0107?\u201d.<\/p>\n<p>Wersja 1.0 stawia wi\u0119c kontrol\u0119 Capability na pocz\u0105tku i zachowuje Nonce jako drug\u0105, niezale\u017cn\u0105 kontrol\u0119:<\/p>\n<pre><code>\/**\n * Handles the settings form (version 1.0).\n *\/\nfunction lw_nb_save() {\n\t\/\/ Authorization: may this user change the settings at all?\n\tif ( ! current_user_can( 'manage_options' ) ) {\n\t\twp_die(\n\t\t\tesc_html__( 'You are not allowed to change these settings.', 'lw-notice-box' ),\n\t\t\t'',\n\t\t\tarray( 'response' =&gt; 403 )\n\t\t);\n\t}\n\n\t\/\/ Intent: did the request come from our own form?\n\tcheck_admin_referer( 'lw_nb_save', 'lw_nb_nonce' );\n\n\t\/\/ Sanitize early.\n\t$label   = isset( $_POST['label'] ) ? sanitize_text_field( wp_unslash( $_POST['label'] ) ) : '';\n\t$link    = isset( $_POST['link'] ) ? sanitize_url( wp_unslash( $_POST['link'] ), array( 'http', 'https' ) ) : '';\n\t$message = isset( $_POST['message'] ) ? wp_kses_post( wp_unslash( $_POST['message'] ) ) : '';\n\t$rows    = isset( $_POST['rows'] ) ? absint( $_POST['rows'] ) : 10;\n\n\t\/\/ Always validate.\n\tif ( '' === $label || mb_strlen( $label ) &gt; 100 ) {\n\t\twp_die(\n\t\t\tesc_html__( 'The label must contain 1 to 100 characters.', 'lw-notice-box' ),\n\t\t\t'',\n\t\t\tarray( 'back_link' =&gt; true )\n\t\t);\n\t}\n\tif ( $rows &lt; 1 || $rows &gt; 50 ) {\n\t\t$rows = 10;\n\t}\n\n\tupdate_option(\n\t\t'lw_nb_notice',\n\t\tarray(\n\t\t\t'label'   =&gt; $label,\n\t\t\t'link'    =&gt; $link,\n\t\t\t'message' =&gt; $message,\n\t\t\t'rows'    =&gt; $rows,\n\t\t)\n\t);\n\tlw_nb_log_change( $label );\n\n\twp_safe_redirect( admin_url( 'options-general.php?page=lw-notice-box' ) );\n\texit;\n}<\/code><\/pre>\n<p>Sprawdzana jest Capability, a nie rola. Code Reference dla <code>current_user_can()<\/code> okre\u015bla sprawdzanie nazw r\u00f3l jako \u201ediscouraged\u201d, bo mo\u017ce dawa\u0107 niewiarygodne wyniki. Dla dzia\u0142a\u0144 na pojedynczych obiektach istniej\u0105 meta Capability z identyfikatorem obiektu, na przyk\u0142ad <code>current_user_can( 'edit_post', $post_id )<\/code>, kt\u00f3re WordPress mapuje na Capability podstawowe przez <code>map_meta_cap()<\/code>. Plugin Check niewiele tu pomo\u017ce: pi\u0119\u0107 test\u00f3w w folderze Security repozytorium Plugin Check dotyczy dost\u0119pu do bazy, escapingu, Nonce i przekierowa\u0144, i \u017caden z nich nie wykrywa brakuj\u0105cej kontroli Capability.<\/p>\n<h2>Trasy REST: permission_callback jest obowi\u0105zkowy<\/h2>\n<p>W trasach REST podzia\u0142 zada\u0144 wygl\u0105da inaczej. Nonce obs\u0142uguje rdze\u0144: przy uwierzytelnianiu ciasteczkiem REST API oczekuje Nonce z akcj\u0105 <code>wp_rest<\/code>, przekazanej jako <code>_wpnonce<\/code> albo w nag\u0142\u00f3wku <code>X-WP-Nonce<\/code>. Je\u015bli jej brakuje, wed\u0142ug podr\u0119cznika API ustawia bie\u017c\u0105cego u\u017cytkownika na 0 i traktuje \u017c\u0105danie jako nieuwierzytelnione. Uprawnienia natomiast nale\u017c\u0105 do <code>permission_callback<\/code> trasy. Wersja 0.1 go pomija:<\/p>\n<pre><code>\/\/ Version 0.1: since WordPress 5.5 this raises a _doing_it_wrong() notice,\n\/\/ but the route is still registered - without any check, for everyone.\nregister_rest_route(\n\t'lw-nb\/v1',\n\t'\/notice',\n\tarray(\n\t\t'methods'  =&gt; 'POST',\n\t\t'callback' =&gt; 'lw_nb_rest_update',\n\t)\n);<\/code><\/pre>\n<p>Od WordPressa 5.5 <code>register_rest_route()<\/code> zg\u0142asza w takim przypadku komunikat <code>_doing_it_wrong()<\/code>. Przy w\u0142\u0105czonym <code>WP_DEBUG<\/code> \u017c\u0105danie REST otrzymuje go w nag\u0142\u00f3wku odpowiedzi <code>X-WP-DoingItWrong<\/code>; do <code>debug.log<\/code> trafia on tylko wtedy, gdy trasy s\u0105 rejestrowane poza \u017c\u0105daniem REST, na przyk\u0142ad przy \u0142adowaniu edytora blok\u00f3w lub w WP-CLI. Trasa i tak zostaje zarejestrowana, a serwer REST uruchamia kontrol\u0119 uprawnie\u0144 tylko wtedy, gdy callback jest ustawiony. Notatka deweloperska do wersji 5.5 uzasadnia ten komunikat tym, \u017ce zapomniany lub b\u0142\u0119dnie wpisany callback przypadkiem czyni endpoint publicznym. Dla tras celowo publicznych zar\u00f3wno podr\u0119cznik, jak i tre\u015b\u0107 komunikatu zalecaj\u0105 <code>__return_true<\/code>, aby zamiar by\u0142 widoczny w kodzie.<\/p>\n<pre><code>\/**\n * Registers GET (public) and POST (administrators only) for \/lw-nb\/v1\/notice.\n *\/\nfunction lw_nb_register_routes() {\n\tregister_rest_route(\n\t\t'lw-nb\/v1',\n\t\t'\/notice',\n\t\tarray(\n\t\t\tarray(\n\t\t\t\t'methods'             =&gt; WP_REST_Server::READABLE,\n\t\t\t\t'callback'            =&gt; 'lw_nb_rest_get',\n\t\t\t\t'permission_callback' =&gt; '__return_true', \/\/ Public on purpose.\n\t\t\t),\n\t\t\tarray(\n\t\t\t\t'methods'             =&gt; WP_REST_Server::EDITABLE,\n\t\t\t\t'callback'            =&gt; 'lw_nb_rest_update',\n\t\t\t\t'permission_callback' =&gt; 'lw_nb_rest_can_edit',\n\t\t\t\t'args'                =&gt; array(\n\t\t\t\t\t'label' =&gt; array(\n\t\t\t\t\t\t'type'              =&gt; 'string',\n\t\t\t\t\t\t'required'          =&gt; true,\n\t\t\t\t\t\t'minLength'         =&gt; 1,\n\t\t\t\t\t\t'maxLength'         =&gt; 100,\n\t\t\t\t\t\t\/\/ A custom sanitize_callback replaces the default schema check,\n\t\t\t\t\t\t\/\/ so the validation callback is set explicitly.\n\t\t\t\t\t\t'validate_callback' =&gt; 'rest_validate_request_arg',\n\t\t\t\t\t\t'sanitize_callback' =&gt; 'sanitize_text_field',\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t),\n\t\t)\n\t);\n}\n\n\/**\n * Authorization for write access.\n *\n * @return bool\n *\/\nfunction lw_nb_rest_can_edit() {\n\treturn current_user_can( 'manage_options' );\n}\n\n\/**\n * Public read access: only the fields the front end needs.\n *\n * @return WP_REST_Response\n *\/\nfunction lw_nb_rest_get() {\n\t$opt = wp_parse_args( get_option( 'lw_nb_notice', array() ), lw_nb_defaults() );\n\treturn rest_ensure_response(\n\t\tarray(\n\t\t\t'label' =&gt; $opt['label'],\n\t\t\t'link'  =&gt; $opt['link'],\n\t\t)\n\t);\n}\n\n\/**\n * Write access; runs only after lw_nb_rest_can_edit() returned true.\n *\n * @param WP_REST_Request $request Request with the sanitized label.\n * @return WP_REST_Response\n *\/\nfunction lw_nb_rest_update( WP_REST_Request $request ) {\n\t$opt          = wp_parse_args( get_option( 'lw_nb_notice', array() ), lw_nb_defaults() );\n\t$opt['label'] = $request-&gt;get_param( 'label' );\n\tupdate_option( 'lw_nb_notice', $opt );\n\tlw_nb_log_change( $opt['label'] );\n\n\treturn rest_ensure_response( array( 'label' =&gt; $opt['label'] ) );\n}<\/code><\/pre>\n<p>Gdy callback zwr\u00f3ci <code>false<\/code>, serwer odpowiada b\u0142\u0119dem <code>rest_forbidden<\/code> ze statusem 401 dla niezalogowanych i 403 dla zalogowanych u\u017cytkownik\u00f3w. Pu\u0142apka kryje si\u0119 w definicji argument\u00f3w: po ustawieniu w\u0142asnego <code>sanitize_callback<\/code> WordPress nie stosuje ju\u017c domy\u015blnej obs\u0142ugi <code>rest_parse_request_arg()<\/code>, kt\u00f3ra sprawdza schemat. Dlatego <code>rest_validate_request_arg<\/code> jest wprost podany jako <code>validate_callback<\/code>; bez tego <code>minLength<\/code> i <code>maxLength<\/code> nie mia\u0142yby \u017cadnego skutku. O budowie szybkich endpoint\u00f3w REST i endpoint\u00f3w z autoryzacj\u0105 OAuth 2.0 traktuj\u0105 artyku\u0142y o <a href=\"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/tutorials-pl-wordpress-plugins-tricks-pl\/tworzenie-wysokowydajnych-wlasnych-punktow-koncowych-rest-api-w-wordpressie\/\">wydajnych w\u0142asnych endpointach REST API<\/a> oraz o <a href=\"https:\/\/www.lukaswojcik.com\/blog\/pl\/tworzenie-stron-internetowych\/bezpieczne-rest-api-w-wordpressie-oauth-2-0-i-niestandardowe-autoryzowane\/\">bezpiecznym REST API z autoryzowanymi endpointami<\/a>.<\/p>\n<h2>Ograniczenia i otwarte kwestie<\/h2>\n<p>Kod uruchomiono na instalacji testowej z WordPressem 7.1.2 i PHP 8.4, jako administrator, jako subskrybent i bez logowania, oraz sprawdzono przez Plugin Check 2.1.0. Dla wersji 1.0 Plugin Check poza brakuj\u0105cym plikiem <code>readme.txt<\/code> nie zg\u0142asza niczego; bez komentarzy <code>phpcs:ignore<\/code> pozosta\u0142oby pi\u0119\u0107 ostrze\u017ce\u0144: dwa razy <code>NonceVerification.Recommended<\/code> dla parametru sortowania, <code>DirectQuery<\/code> dla <code>$wpdb-&gt;insert()<\/code> i dla zapytania o log oraz <code>NoCaching<\/code> dla zapytania o log. Dla wersji 0.1 wymienia 10 b\u0142\u0119d\u00f3w i 17 ostrze\u017ce\u0144 w kodzie, \u017cadne z nich nie dotyczy brakuj\u0105cej kontroli Capability ani brakuj\u0105cego <code>permission_callback<\/code>.<\/p>\n<p>Niekt\u00f3re tematy zosta\u0142y \u015bwiadomie pomini\u0119te: handlery AJAX z <code>check_ajax_referer()<\/code>, przesy\u0142anie plik\u00f3w, w\u0142asne role i Capability oraz Content Security Policy. Cztery om\u00f3wione b\u0142\u0119dy obejmuj\u0105 cz\u0119ste podstawy, ale nie zast\u0119puj\u0105 pe\u0142nej koncepcji bezpiecze\u0144stwa. Lista dozwolonych kolumn sortowania jest walidacj\u0105 tak dobr\u0105, jak jej utrzymanie: gdy przybywa kolumna, lista te\u017c musi si\u0119 wyd\u0142u\u017cy\u0107.<\/p>\n<div class=\"lw-faq\">\n<h2>Pytania i odpowiedzi<\/h2>\n<h3>Czy parametr GET, kt\u00f3ry zmienia tylko sortowanie, te\u017c wymaga Nonce?<\/h3>\n<p>Nonce chroni akcje, kt\u00f3re co\u015b zmieniaj\u0105. Parametr taki jak <code>orderby<\/code>, s\u0142u\u017c\u0105cy wy\u0142\u0105cznie do sortowania widoku, nie zmienia stanu. Plugin Check i tak zg\u0142asza dost\u0119p jako ostrze\u017cenie <code>WordPress.Security.NonceVerification.Recommended<\/code>, a komentarz <code>phpcs:ignore<\/code> z uzasadnieniem dokumentuje decyzj\u0119. Warto\u015b\u0107 nadal trzeba zwalidowa\u0107, w przyk\u0142adzie przez list\u0119 dozwolonych kolumn.<\/p>\n<h3>Czy sanitize_text_field() wystarcza jako ochrona przed SQL injection?<\/h3>\n<p>Nie. <code>sanitize_text_field()<\/code> usuwa znaczniki, podzia\u0142y wierszy i niepoprawne UTF-8, ale nie czyni warto\u015bci bezpieczn\u0105 dla SQL. Warto\u015bci nale\u017cy przekazywa\u0107 jako symbole zast\u0119pcze w <code>$wpdb-&gt;prepare()<\/code> albo przez list\u0119 format\u00f3w w <code>$wpdb-&gt;insert()<\/code>. Nazwy kolumn i tabel wymagaj\u0105 <code>%i<\/code>, dost\u0119pnego od WordPressa 6.2, oraz listy dozwolonych warto\u015bci.<\/p>\n<h3>Dlaczego nie sprawdza\u0107 po prostu, czy u\u017cytkownik ma rol\u0119 \u201eadministrator\u201d?<\/h3>\n<p>Role da si\u0119 przebudowa\u0107, a w\u0142a\u015bciw\u0105 jednostk\u0105 uprawnie\u0144 s\u0105 Capability. Code Reference dla <code>current_user_can()<\/code> okre\u015bla sprawdzanie nazw r\u00f3l jako \u201ediscouraged\u201d, bo mo\u017ce dawa\u0107 niewiarygodne wyniki. Warto te\u017c pami\u0119ta\u0107, \u017ce dla superadministrator\u00f3w w sieci Multisite <code>current_user_can()<\/code> zawsze zwraca <code>true<\/code>, o ile dana Capability nie zosta\u0142a wyra\u017anie zablokowana.<\/p>\n<h3>Czy w\u0142asna trasa REST musi sama sprawdza\u0107 Nonce?<\/h3>\n<p>Przy uwierzytelnianiu ciasteczkiem Nonce z akcj\u0105 <code>wp_rest<\/code> sprawdza rdze\u0144. Bez niej \u017c\u0105danie jest traktowane jako nieuwierzytelnione, a bie\u017c\u0105cy u\u017cytkownik ma identyfikator 0. Sama trasa odpowiada za uprawnienia w <code>permission_callback<\/code>, na przyk\u0142ad przez <code>current_user_can( 'manage_options' )<\/code>; trasy publiczne ustawiaj\u0105 wprost <code>__return_true<\/code>.<\/p>\n<\/div>\n<div class=\"lw-quellen\">\n<h2>\u0179r\u00f3d\u0142a<\/h2>\n<p>Data dost\u0119pu: 29.09.2026.<\/p>\n<ul>\n<li><a href=\"https:\/\/developer.wordpress.org\/apis\/security\/\" target=\"_blank\" rel=\"noopener noreferrer\">Common APIs Handbook: Security<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/apis\/security\/sanitizing\/\" target=\"_blank\" rel=\"noopener noreferrer\">Common APIs Handbook: Sanitizing Data<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/apis\/security\/data-validation\/\" target=\"_blank\" rel=\"noopener noreferrer\">Common APIs Handbook: Data Validation<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/apis\/security\/escaping\/\" target=\"_blank\" rel=\"noopener noreferrer\">Common APIs Handbook: Escaping Data<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/apis\/security\/nonces\/\" target=\"_blank\" rel=\"noopener noreferrer\">Common APIs Handbook: Nonces<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/apis\/security\/user-roles-and-capabilities\/\" target=\"_blank\" rel=\"noopener noreferrer\">Common APIs Handbook: User Roles and Capabilities<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/plugins\/wordpress-org\/common-issues\/\" target=\"_blank\" rel=\"noopener noreferrer\">Plugin Handbook: Common issues (zesp\u00f3\u0142 Plugin Review)<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/rest-api\/extending-the-rest-api\/adding-custom-endpoints\/\" target=\"_blank\" rel=\"noopener noreferrer\">REST API Handbook: Adding Custom Endpoints<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/rest-api\/using-the-rest-api\/authentication\/\" target=\"_blank\" rel=\"noopener noreferrer\">REST API Handbook: Authentication<\/a><\/li>\n<li><a href=\"https:\/\/make.wordpress.org\/core\/2020\/07\/22\/rest-api-changes-in-wordpress-5-5\/\" target=\"_blank\" rel=\"noopener noreferrer\">Make WordPress Core: REST API changes in WordPress 5.5<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/sanitize_text_field\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: sanitize_text_field()<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/absint\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: absint()<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/wp_unslash\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: wp_unslash()<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/esc_html\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: esc_html()<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/esc_attr\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: esc_attr()<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/esc_url\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: esc_url()<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/wp_kses_post\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: wp_kses_post()<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/wp_nonce_field\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: wp_nonce_field()<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/check_admin_referer\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: check_admin_referer()<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/wp_verify_nonce\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: wp_verify_nonce()<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/current_user_can\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: current_user_can()<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/classes\/wpdb\/prepare\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: wpdb::prepare()<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/register_rest_route\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: register_rest_route()<\/a><\/li>\n<li><a href=\"https:\/\/developer.wordpress.org\/reference\/hooks\/admin_post_action\/\" target=\"_blank\" rel=\"noopener noreferrer\">Code Reference: admin_post_{$action}<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/trunk\/src\/wp-admin\/admin-post.php\" target=\"_blank\" rel=\"noopener noreferrer\">wordpress-develop: wp-admin\/admin-post.php<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/trunk\/src\/wp-includes\/pluggable.php\" target=\"_blank\" rel=\"noopener noreferrer\">wordpress-develop: wp-includes\/pluggable.php (wp_create_nonce, check_admin_referer)<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/trunk\/src\/wp-includes\/load.php\" target=\"_blank\" rel=\"noopener noreferrer\">wordpress-develop: wp-includes\/load.php (wp_magic_quotes)<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/trunk\/src\/wp-includes\/rest-api\/class-wp-rest-server.php\" target=\"_blank\" rel=\"noopener noreferrer\">wordpress-develop: class-wp-rest-server.php<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/wordpress-develop\/blob\/trunk\/src\/wp-includes\/rest-api\/class-wp-rest-request.php\" target=\"_blank\" rel=\"noopener noreferrer\">wordpress-develop: class-wp-rest-request.php<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/WordPress\/plugin-check\/tree\/trunk\/includes\/Checker\/Checks\/Security\" target=\"_blank\" rel=\"noopener noreferrer\">Plugin Check: testy bezpiecze\u0144stwa<\/a><\/li>\n<li><a href=\"https:\/\/wordpress.org\/download\/releases\/\" target=\"_blank\" rel=\"noopener noreferrer\">Wydania WordPressa (aktualne: 7.1.2 z 22.09.2026)<\/a><\/li>\n<\/ul>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Cztery b\u0142\u0119dy bezpiecze\u0144stwa z przegl\u0105du wtyczek na WordPress.org, ka\u017cdy na przyk\u0142adzie ma\u0142ej wtyczki z kodem wadliwym i poprawionym, a do tego permission_callback w trasach REST.<\/p>\n","protected":false},"author":1,"featured_media":20897,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[25],"tags":[92280,92077,92286],"class_list":["post-20859","post","type-post","status-publish","format-standard","hentry","category-wordpress-plugins-tricks-pl","tag-php-pl","tag-web-security-pl","tag-wordpress-pl"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Sanityzacja, escaping, Nonce i Capability w przegl\u0105dzie wtyczek | Lukas Wojcik<\/title>\n<meta name=\"description\" content=\"Sanitize early, escape late, always validate: wadliwy i poprawiony kod dla danych wej\u015bciowych, wyj\u015bcia, Nonce, Capability i tras REST.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Sanityzacja, escaping, Nonce i Capability w przegl\u0105dzie wtyczek | Lukas Wojcik\" \/>\n<meta property=\"og:description\" content=\"Sanitize early, escape late, always validate: wadliwy i poprawiony kod dla danych wej\u015bciowych, wyj\u015bcia, Nonce, Capability i tras REST.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-05T18:07:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20859-sanityzacja-escaping-nonce-capability-wtyczki.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Lukas Wojcik\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Lukas Wojcik\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"26 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/wordpress-plugins-tricks-pl\\\/sanityzacja-escaping-nonce-capability-wtyczki\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/wordpress-plugins-tricks-pl\\\/sanityzacja-escaping-nonce-capability-wtyczki\\\/\"},\"author\":{\"name\":\"Lukas Wojcik\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"headline\":\"Sanityzacja, escaping, Nonce i Capability w przegl\u0105dzie wtyczek\",\"datePublished\":\"2026-10-05T18:07:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/wordpress-plugins-tricks-pl\\\/sanityzacja-escaping-nonce-capability-wtyczki\\\/\"},\"wordCount\":2564,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/wordpress-plugins-tricks-pl\\\/sanityzacja-escaping-nonce-capability-wtyczki\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20859-sanityzacja-escaping-nonce-capability-wtyczki.png\",\"keywords\":[\"PHP\",\"Web Security\",\"WordPress\"],\"articleSection\":[\"Wtyczki i triki WordPress\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/wordpress-plugins-tricks-pl\\\/sanityzacja-escaping-nonce-capability-wtyczki\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/wordpress-plugins-tricks-pl\\\/sanityzacja-escaping-nonce-capability-wtyczki\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/wordpress-plugins-tricks-pl\\\/sanityzacja-escaping-nonce-capability-wtyczki\\\/\",\"name\":\"Sanityzacja, escaping, Nonce i Capability w przegl\u0105dzie wtyczek | Lukas Wojcik\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/wordpress-plugins-tricks-pl\\\/sanityzacja-escaping-nonce-capability-wtyczki\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/wordpress-plugins-tricks-pl\\\/sanityzacja-escaping-nonce-capability-wtyczki\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20859-sanityzacja-escaping-nonce-capability-wtyczki.png\",\"datePublished\":\"2026-10-05T18:07:00+00:00\",\"description\":\"Sanitize early, escape late, always validate: wadliwy i poprawiony kod dla danych wej\u015bciowych, wyj\u015bcia, Nonce, Capability i tras REST.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/wordpress-plugins-tricks-pl\\\/sanityzacja-escaping-nonce-capability-wtyczki\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/wordpress-plugins-tricks-pl\\\/sanityzacja-escaping-nonce-capability-wtyczki\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/wordpress-plugins-tricks-pl\\\/sanityzacja-escaping-nonce-capability-wtyczki\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20859-sanityzacja-escaping-nonce-capability-wtyczki.png\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/fi-20859-sanityzacja-escaping-nonce-capability-wtyczki.png\",\"width\":1200,\"height\":630,\"caption\":\"Sanityzacja, escaping, Nonce i Capability w przegl\u0105dzie wtyczek\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/pl\\\/wordpress-plugins-tricks-pl\\\/sanityzacja-escaping-nonce-capability-wtyczki\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Sanityzacja, escaping, Nonce i Capability w przegl\u0105dzie wtyczek\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"Lukas Wojcik\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"Lukas Wojcik\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Sanityzacja, escaping, Nonce i Capability w przegl\u0105dzie wtyczek | Lukas Wojcik","description":"Sanitize early, escape late, always validate: wadliwy i poprawiony kod dla danych wej\u015bciowych, wyj\u015bcia, Nonce, Capability i tras REST.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/","og_locale":"en_US","og_type":"article","og_title":"Sanityzacja, escaping, Nonce i Capability w przegl\u0105dzie wtyczek | Lukas Wojcik","og_description":"Sanitize early, escape late, always validate: wadliwy i poprawiony kod dla danych wej\u015bciowych, wyj\u015bcia, Nonce, Capability i tras REST.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/","og_site_name":"Lukas Wojcik - Blog","article_published_time":"2026-10-05T18:07:00+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20859-sanityzacja-escaping-nonce-capability-wtyczki.png","type":"image\/png"}],"author":"Lukas Wojcik","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Lukas Wojcik","Est. reading time":"26 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/#article","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/"},"author":{"name":"Lukas Wojcik","@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"headline":"Sanityzacja, escaping, Nonce i Capability w przegl\u0105dzie wtyczek","datePublished":"2026-10-05T18:07:00+00:00","mainEntityOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/"},"wordCount":2564,"commentCount":0,"publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20859-sanityzacja-escaping-nonce-capability-wtyczki.png","keywords":["PHP","Web Security","WordPress"],"articleSection":["Wtyczki i triki WordPress"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/","url":"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/","name":"Sanityzacja, escaping, Nonce i Capability w przegl\u0105dzie wtyczek | Lukas Wojcik","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/#primaryimage"},"image":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/#primaryimage"},"thumbnailUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20859-sanityzacja-escaping-nonce-capability-wtyczki.png","datePublished":"2026-10-05T18:07:00+00:00","description":"Sanitize early, escape late, always validate: wadliwy i poprawiony kod dla danych wej\u015bciowych, wyj\u015bcia, Nonce, Capability i tras REST.","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/#primaryimage","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20859-sanityzacja-escaping-nonce-capability-wtyczki.png","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/09\/fi-20859-sanityzacja-escaping-nonce-capability-wtyczki.png","width":1200,"height":630,"caption":"Sanityzacja, escaping, Nonce i Capability w przegl\u0105dzie wtyczek"},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/pl\/wordpress-plugins-tricks-pl\/sanityzacja-escaping-nonce-capability-wtyczki\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Sanityzacja, escaping, Nonce i Capability w przegl\u0105dzie wtyczek"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"Lukas Wojcik","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"Lukas Wojcik"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/20859","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=20859"}],"version-history":[{"count":1,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/20859\/revisions"}],"predecessor-version":[{"id":21616,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/posts\/20859\/revisions\/21616"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media\/20897"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=20859"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/categories?post=20859"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=20859"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}