
एक WordPress प्लगइन जो REST API के माध्यम से एक MCP सर्वर को उजागर करता है, जिसमें सुरक्षा मॉडल ही मुख्य बिंदु है -- CVE-2026-15015 OAuth-बायपास स्वरूप को इस तरह बंद करता है कि वह सतह कभी होती ही नहीं।
सरल शब्दों में: यह एक AI एजेंट को एक वास्तविक WordPress Application Password के माध्यम से एक WordPress साइट को पढ़ने और हल्के से लिखने की अनुमति देता है — वही क्रेडेंशियल प्रकार जो wp-admin पहले से जारी करता है — और कुछ नहीं। यहाँ कोई साइन-अप फ़ॉर्म नहीं है, कोई OAuth क्लाइंट पंजीकरण नहीं है, किसी भी प्रकार का कोई टोकन एंडपॉइंट नहीं है। इसी क्षेत्र में एक प्लगइन ने ठीक यही शिप किया था, और इसी तरह एक अनप्रमाणित हमलावर पूर्ण एडमिन एक्सेस लेकर चला गया।
एक WordPress प्लगइन जो REST API पर एक MCP सर्वर को उजागर करता है, जिसमें सुरक्षा मॉडल ही मुख्य बिंदु है, कोई फ़ीचर बुलेट नहीं।
CVE-2026-15015, CVSS 9.8: WordPress के लिए MountDev AI MCP Connector ने एक सार्वजनिक रूप से सुलभ OAuth 2.1 Dynamic Client Registration एंडपॉइंट के साथ-साथ एक authorization एंडपॉइंट भी उजागर किया जो यह भी नहीं जाँचता था कि कौन पूछ रहा है। एक हमलावर अपना खुद का OAuth क्लाइंट पंजीकृत करता है — किसी क्रेडेंशियल की आवश्यकता नहीं, एंडपॉइंट डिज़ाइन से ही अनप्रमाणित है, "dynamic client registration" का यही अर्थ है — इसे बिना किसी की निगरानी के authorization एंडपॉइंट से गुज़ारता है, और एक administrator खाते से बंधा bearer token प्राप्त करता है। पूर्ण साइट नियंत्रण: सामग्री, उपयोगकर्ता, सेटिंग्स। कुछ भी गलत कॉन्फ़िगर नहीं था; प्रवाह ठीक वैसे ही काम कर रहा था जैसे OAuth क्लाइंट पंजीकरण को करना चाहिए। बस इसे कभी भी उस समय सुलभ नहीं होना चाहिए था जब इसकी स्वीकृति के लिए पहले से कोई एडमिन मौजूद न हो।
सामान्यीकृत करने वाला समाधान "OAuth को और सावधानी से लागू करना" नहीं है। यह वह सतह न होना है। यह प्लगइन किसी भी प्रकार का कोई client-registration एंडपॉइंट, कोई authorization एंडपॉइंट, और कोई token-issuance एंडपॉइंट पंजीकृत नहीं करता। हमलावर के पास पंजीकरण करने के लिए कुछ नहीं है, क्योंकि यहाँ ऐसा कुछ भी नहीं है जो क्रेडेंशियल बाँटता हो। प्रमाणीकरण एक WordPress Application Password है जिसे एक administrator ने पहले ही wp-admin से एक विशिष्ट उपयोगकर्ता के लिए बनाया है — एक क्रेडेंशियल जो केवल इसलिए मौजूद है क्योंकि manage_options वाले एक मानव ने इसे बनाने का चुनाव किया, पहले से, इस प्लगइन से पूरी तरह से बाहर।
इनमें से कोई भी अकेले नई नहीं है; दोनों को, जानबूझकर, एक-दूसरे से स्वतंत्र रूप से चलाना, वही है जो किसी एक में हुई गलती को बंद कर देता है:
1. केवल Application Password — कभी cookie session नहीं। WordPress का अपना is_user_logged_in() एक वैध cookie और nonce रखने वाले ब्राउज़र टैब के लिए भी true होता है। Auth::permission_callback() उस पथ को जानबूझकर अस्वीकार करता है: यह उस application_password_did_authenticate action को सुनता है जिसे WordPress core विशेष रूप से तब फ़ायर करता है जब Basic-auth Application Password प्रमाणीकरण सफल होता है, और उस फ़्लैग की आवश्यकता रखता है, न कि केवल "कोई उपयोगकर्ता लॉग इन है।" एक MCP क्लाइंट nonce वाला ब्राउज़र टैब नहीं है; यहाँ cookie auth स्वीकार करना एक ऐसी टूल सतह में CSRF-आकार का पथ खोल देगा जिसे अंग्रेज़ी में सामग्री संशोधित करने के लिए कहा जा सकता है, और इससे इस प्लगइन को जिस एक दरवाज़े की वास्तव में आवश्यकता है उस पर कोई लाभ नहीं मिलेगा।
2. प्रति-टूल capability, साथ ही एक write gate जिसे अकेला capability खोल नहीं सकता। ToolRegistry::call() हर कॉल पर user_can($user_id, $capability) को नए सिरे से जाँचता है — पोस्ट के लिए edit_posts, ऑर्डर के लिए manage_woocommerce। अलग से, एकमात्र write टूल, create_draft_post, को अतिरिक्त रूप से आवश्यकता होती है कि एक administrator ने Settings → WP Secure MCP से opt in किया हो, जो डिफ़ॉल्ट रूप से बंद है। एक Application Password जो संयोग से edit_posts रखता है, तब तक कुछ भी नहीं लिख सकता जब तक किसी मानव ने वह स्विच फ़्लिप न किया हो — एक capability जाँच और एक साइट-व्यापी gate दो अलग ताले हैं, और परीक्षण सिद्ध करते हैं कि किसी भी दिशा में एक भी दूसरे का विकल्प नहीं है।
limit, 1–20) सीमित करते हैं कि एक कॉल कितना लौटा सकती है; एक वैध रूप से महँगी WP_Query या wc_get_orders कॉल की लागत वही रहती है जो वह है।हर टूल की tools/list प्रविष्टि में readOnlyHint / destructiveHint एनोटेशन होते हैं, ताकि एक क्लाइंट यह तय कर सके कि किसी को कॉल करने से पहले किसी मानव से पूछना है या नहीं, बिना यह पहले से जाने कि वह क्या करता है।
composer install --no-dev
प्लगइन निर्देशिका को wp-content/plugins/ में कॉपी करें (या symlink करें), इसे wp-admin से सक्रिय करें, फिर उस खाते के लिए एक Application Password बनाएँ जिसके रूप में एजेंट को कार्य करना चाहिए: Users → your profile → Application Passwords।
Writes डिफ़ॉल्ट रूप से बंद हैं। create_draft_post की अनुमति देने के लिए: Settings → WP Secure MCP → Write tools। इसके ऊपर प्रति-टूल capability जाँच अभी भी लागू होती है — साइट-व्यापी स्विच फ़्लिप करने से किसी को वह capability नहीं मिल जाती जो उसके पास पहले से नहीं थी।
55 परीक्षण, सभी शुद्ध — कोई WordPress इंस्टॉल नहीं, कोई डेटाबेस नहीं, कोई Docker नहीं। tests/bootstrap.php उन WordPress फ़ंक्शनों को stub करता है जिन्हें src/ कॉल करता है (current_user_can, get_post, wp_insert_post, ...), ठीक उसी तरह जैसे एक WordPress प्लगइन को पारंपरिक रूप से WordPress को लोड किए बिना unit-test किया जाता है।
composer install
vendor/bin/phpunit --testdox
सबसे भारी कवरेज वहीं बैठता है जहाँ यह सबसे अधिक मायने रखता है:
ProtocolTest — एक नकली registry के विरुद्ध 17 परीक्षण, कुछ भी WordPress-विशिष्ट नहीं। JSON-RPC envelope validation, हर error code, और JSON-RPC 2.0 notification नियम कि बिना id फ़ील्ड वाले अनुरोध को कोई प्रतिक्रिया नहीं मिलती, किसी भी method के लिए, भले ही वह विकृत हो — error के जाने के लिए कहीं नहीं है।ToolRegistryTest — सिद्ध करता है कि capability जाँच और write gate दोनों दिशाओं में स्वतंत्र हैं: writes सक्षम होने पर भी एक अनुपस्थित capability कॉल को अवरुद्ध करती है, और capability मौजूद होने पर भी एक अक्षम write gate कॉल को अवरुद्ध करता है।AuthTest — यहाँ सबसे अधिक मायने रखने वाला परीक्षण है test_logged_in_user_without_application_password_authentication_is_refused: एक वास्तविक, हल किया गया WordPress उपयोगकर्ता अभी भी अस्वीकार किया जाता है, क्योंकि cookie session कभी माँगा ही नहीं गया था।CI हर push पर PHP 8.1–8.3 में PHPUnit और WordPress Coding Standards ruleset के विरुद्ध PHP_CodeSniffer चलाता है।
CI अभी हर push पर जो नहीं चलाता: @wordpress/env के माध्यम से एक लाइव WordPress + WooCommerce integration परीक्षण — एक वास्तविक Application Password के साथ एक वास्तविक REST API और एक वास्तविक डेटाबेस के विरुद्ध प्रमाणीकरण करें, जो pg-readonly-mcp की real-Postgres CI job का लाइव-इंस्टेंस समकक्ष है। यह लिखा और तर्क-सहित तैयार है, ci.yml में एक workflow_dispatch job के रूप में जुड़ा है, न कि एक आवश्यक gate के रूप में, क्योंकि इस रिपॉज़िटरी के अपने विकास वातावरण में एक स्थानीय Docker Desktop विफलता आई जिसने इस workflow के पहले वास्तविक रन से पहले wp-env का पूर्वाभ्यास असंभव बना दिया। यह यहाँ कहा गया है बजाय इसके कि किसी और के लिए इसे खोजने के लिए छोड़ दिया जाए — वही नियम जो pg-readonly-mcp अपने ही untested parser-differential अंतराल पर लागू करता है।
wp-secure-mcp.php plugin bootstrap: loads the autoloader, wires one REST route
src/
Protocol.php JSON-RPC 2.0 dispatch. Zero WordPress calls. Pure.
ToolRegistryInterface.php the seam Protocol talks to instead of WordPress directly
ToolRegistry.php the two locks: per-tool capability, server-wide write gate
Auth.php Application-Password-only permission_callback
Settings.php the write-gate admin toggle
RestController.php thin bootstrap: one route, delegates immediately
Tools/ the four v1 tools
tests/
bootstrap.php WordPress function stubs
ProtocolTest.php, ToolRegistryTest.php, AuthTest.php, ...
bin/smoke-test.sh live wp-env integration script (see Tests, above)
यदि कोई अनुरोध कभी किसी ऐसे कारण से स्वीकार या अस्वीकार किया जाता है जो Auth, ToolRegistry, या Protocol के बजाय wp-secure-mcp.php या RestController.php में रहता है, तो वह एक बग है — निर्णय एक परत नीचे होना चाहिए, जहाँ इसे एक सादे array और कुछ और नहीं के साथ परीक्षण किया जा सके।
MIT.
| Tool | Capability | Read-only | Notes |
|---|
search_posts | edit_posts | Yes | Keyword search. Hardcoded to post_status=publish — never returns drafts or private posts, even to a caller who could see them in wp-admin. |
get_post | edit_posts | Yes | Fetch by ID. Refuses a real post at a real ID if its status isn't publish — an ID is not a bypass around the same rule search_posts enforces. |
list_recent_orders | manage_woocommerce | Yes | Status, total, currency, date only. Never customer name, email, or address — order visibility doesn't require handing an agent a customer's PII, capability check or not. |
create_draft_post | edit_posts | No | post_status is a literal 'draft' in the source, not an argument. This tool cannot publish under any input, even with writes enabled. Off site-wide until an administrator opts in. |