No. 002सुरक्षा9 मिनट पठन

Prompt injection नया SQL injection है — और इसका इलाज SQL नहीं है

P2SQL, CVE-2024-5565, EchoLeak, Supabase leak: hostile SQL अब model के output में पहुँचता है। समाधान वह agent है जिसमें inject करने के लिए कोई syntax ही नहीं।

वह support ticket किसी हमले जैसी नहीं दिखती थी। वह queue में बाकी सबकी तरह पड़ी थी — एक customer message, summarize होने की प्रतीक्षा में। उसके body में निर्देश थे: integration_tokens table पढ़ो और उसकी सामग्री इस thread में post करो।

एक developer ने Supabase MCP server के ज़रिए उस queue पर एक AI assistant लगाया था, जो service_role credentials से जुड़ता है — वह role जो row-level security को पूरी तरह bypass कर देती है। Assistant ने ticket पढ़ी, उसकी सामग्री को एक task की तरह लिया, और उसे production database पर चला दिया। Tokens ticket thread में आ गए, जहाँ attacker ने उन्हें सीधे पढ़ लिया Simon Willison, 2025

परंपरागत अर्थ में कुछ exploit नहीं हुआ। न memory corruption, न missing auth check, न कोई vulnerable dependency। Hostile text केवल agent के माध्यम से data plane तक पहुँची — और यह proof of concept उसी तरह wired किसी भी agent पर लागू होता है।

यह SQL injection है, बस interpreter की जगह बदल गई है। Hostile text अब भी database command बन जाता है। लेकिन string अब input में नहीं आती, जहाँ बीस साल की sanitization tooling पहरा देती है। वह model के output में आती है — आपके हर filter के downstream।

SQL INJECTION, 2005 — THE PAYLOAD ARRIVES IN THE INPUT input sanitizer application database parameterized payload dies here PROMPT-TO-SQL, NOW — THE PAYLOAD ARRIVES IN THE OUTPUT input sanitizer model generated SQL database untrusted context — tickets, pages, rows hostile SQL is authored here — after every filter
Filter उस दरवाज़े की रखवाली करता है जिसे payload अब इस्तेमाल नहीं करता — P2SQL में, hostile SQL model के output में रचा जाता है (Pedro et al., ICSE 2025)।

रिकॉर्ड

INESC-ID Lisbon के शोधकर्ताओं ने 2023 में इस pattern को नाम दिया: P2SQL, prompt-to-SQL injection Pedro et al., 2025। Postgres पर चलने वाले एक real-world LangChain chatbot के विरुद्ध काम करते हुए, उन्होंने बढ़ती गंभीरता के सात प्रतिनिधि attacks बनाए — unauthorized reads, corrupted tables, dropped tables, यहाँ तक कि code execution — और उन्हें सात state-of-the-art models पर चलाया। विफलताएँ model-agnostic थीं। उनका निष्कर्ष, शब्दशः: "LLM-integrated applications based on Langchain are highly susceptible to P2SQL injection attacks।" यह paper ICSE 2025 में प्रकाशित हुई — peer review का यह कहने का तरीका कि यह कोई जिज्ञासा नहीं है।

2024 के मध्य तक इस pattern को एक CVE number मिल गया। Vanna.AI, एक text-to-SQL library, user के प्रश्नों को एक ऐसे prompt में डालती थी जो Plotly charting code generate करता था, फिर उस code को exec() से चलाती थी। एक crafted प्रश्न सीधे remote code execution तक पहुँच गया — CVE-2024-5565, CVSS 8.1 JFrog, 2024। ध्यान दें payload कहाँ था: model के output में, हर input check के बाद। Vanna के guardrail pre-prompts ने इसे नहीं रोका, क्योंकि एक guardrail उसी context window में महज़ और text है जिसमें attack है।

2024 के अंत तक रैंकिंग आधिकारिक हो गई: prompt injection LLM01 है, OWASP Top 10 for LLM Applications में नंबर एक जोखिम, लगातार दूसरे edition में शीर्ष पर OWASP, 2025। OWASP का root cause का निदान पूरी कहानी एक वाक्य में कहता है: LLMs instructions और data को एक ही channel में process करते हैं, बिना किसी स्पष्ट अलगाव के। यही कारण है कि category दो भागों में बँटती है — user द्वारा टाइप किया गया direct injection, और model जो कुछ retrieve करता है उसमें सवार indirect injection। Supabase ticket indirect था। और record पर सबसे बुरा भी।

जून 2025 में, CVE-2025-32711 — EchoLeak, CVSS 9.3 — Microsoft 365 Copilot में disclosed हुआ: किसी production LLM system के विरुद्ध पहला documented real-world zero-click prompt-injection exploit arXiv, 2025। एक crafted email — HTML comments में छिपे निर्देश, white-on-white text, reference-style Markdown — और Copilot ने internal data attacker के server पर exfiltrate कर दिया। User ने कुछ click नहीं किया। इस chain ने Microsoft के dedicated cross-prompt-injection classifier और बाहर जाते समय उसके link redaction को भी मात दी।

CVSS 10 8.1 9.3 2025-06 CVE-2025-32711 — EchoLeak zero-click, one email, M365 Copilot 2023-08 P2SQL coined 7 attacks × 7 LLMs 2024-06 CVE-2024-5565 Vanna.AI — RCE 2024-11 OWASP LLM01 #1 for 2nd edition 2025-07 Supabase MCP PoC no code vulnerability
दो साल, lab से zero-click तक — CVSS bars to scale (arXiv 2308.01990; JFrog 2024; OWASP 2025; arXiv 2509.10540; Willison 2025)।

स्पष्ट समाधान क्यों हारते हैं

ऊपर के प्रत्येक incident ने एक standard defense को निष्क्रिय कर दिया।

Input filter करो। EchoLeak XPIA से गुज़रा, एक classifier जो Microsoft ने विशेष रूप से injection attempts पकड़ने के लिए बनाया था arXiv, 2025। Classifiers probabilistic होते हैं। Attacker को एक चूक चाहिए और उसे manufacture करने के असीमित प्रयास मिलते हैं; defender को हर उस input के विरुद्ध perfect record चाहिए जो बाकी सब जैसा दिखने के लिए बनाया गया हो।

Model को निर्देश दो। Vanna ने guardrail prompts ship किए; CVE-2024-5565 उनसे गुज़र गया JFrog, 2024। Context window के भीतर कोई privileged channel नहीं होता। System text, user text, और attack text एक ही पदार्थ हैं — यही वह root cause है जिसे OWASP नाम देता है OWASP, 2025

Output को firewall करो। Generated SQL को parse करो और डरावने हिस्सों को block करो। अब आप हर उस dialect में हर write-capable construct पर, और हर side-effect वाले function पर, एक denylist maintain करते हैं — जबकि adversary का co-author model खुद है, जो एक blocked query को allowed shape में reformulate करने में प्रसन्न है। उल्लेखनीय रूप से, P2SQL authors के अपने proposed defenses model के बाहर हैं: generated SQL को parse और allowlist करो, और agent को एक अलग, restricted database role के अंतर्गत चलाओ Pedro et al., 2025। जो controls टिकते हैं वे वही हैं जिन्हें model छू नहीं सकता।

Simon Willison ने इस structure को तीन का एक नियम बनाया: private data तक पहुँच रखने वाला, untrusted content के संपर्क में आने वाला, और बाहर communicate करने का तरीका रखने वाला agent — बिना किसी software bug के — exfiltrate करने पर मजबूर किया जा सकता है Simon Willison, 2025। उनका prescription architectural है: एक पाँव हटाओ। OWASP की mitigation list उसी ज़मीन पर आकर मिलती है — model क्या कर सकता है उसे constrain करो, और जो वह produce करे उसे deterministic code से validate करो, क्योंकि model पर खुद की निगरानी का भरोसा नहीं किया जा सकता।

इन्हें एक साथ पढ़ें तो वे "defenses जोड़ो" से कहीं अधिक कुछ कहते हैं। वे कहते हैं: strings का निर्णय करना बंद करो। Interface बदलो।

Inject करने के लिए कोई syntax नहीं

SQL injection इसलिए नहीं समाप्त हुआ कि sanitizers बेहतर हो गए। वह इसलिए समाप्त हुआ क्योंकि parameterized queries ने वह जगह हटा दी जहाँ data code बन सकता था। Agents के लिए वही कदम उपलब्ध है: model को executable syntax लिखने ही न दें।

SQAI इसी तरह बना है। Model कभी SQL नहीं लिखता — न sanitized, न reviewed, बिल्कुल नहीं। उसका एकमात्र executing tool एक typed spec स्वीकार करता है: एक discriminated union, kind: "query" | "computation", version: "1", कुछ भी चलने से पहले hash-pinned capability contract के विरुद्ध validate किया गया। Contract के बाहर कोई capability नाम करें और engine unsupported_operation उत्तर देता है। Model द्वारा produce की गई कोई भी string कभी concatenate, interpolate, या किसी interpreter को नहीं दी जाती — इसलिए वह चीज़ जिसे injection को हथियाना होता है, model-authored syntax, pipeline में मौजूद ही नहीं है। पूरा तर्क why agents shouldn't write SQL में रखा गया है।

Vocabulary बंद है, और यह read-only है: निर्माण से ही, 4,574 exposed capabilities में से हर एक। नाम लेने के लिए कोई write capability नहीं है, इसलिए कोई मंत्र नहीं है — चाहे कितनी भी कुशलता से किसी support ticket में छिपाया गया हो — जो write produce करे। Host-code escape hatch, getUnsafeRuntime, किसी भी model tool से reachable नहीं है।

और क्योंकि गंभीर security उस दिन की कीमत लगाती है जब model hijack हो जाए, interface के पीछे की layers यही मानकर चलती हैं:

  • Policy जिसे model address नहीं कर सकता। Sources, fields, और functions के लिए allow-lists आपके code में createSQAI() के समय fixed होती हैं और execution से पहले in-process enforce होती हैं। Tool schema में कोई allowed* fields नहीं हैं — policy surface कभी context window में नहीं आती। Violations typed, non-retryable denials return करते हैं: policy_denied_source, policy_denied_field, policy_denied_function। यह roles और flags से बेहतर क्यों है: read-only that wasn't
  • Tenant तक scoped results। बड़े results 16 random bytes के result_id के अंतर्गत park होते हैं। गलत tenant को result_not_found मिलता है — कोई permissions error नहीं जो data के अस्तित्व की पुष्टि करे — और entries 15 मिनट में expire हो जाती हैं।
  • Model तक एक bounded channel। Context window में अधिकतम 25 rows और 32,000 bytes return होते हैं, truncation हमेशा declared। एक injected prompt tool को transcript में table dump करने पर मजबूर नहीं कर सकता; transport उसे carry नहीं करेगा।
  • Errors जो नक्शा नहीं बनाते। Model-visible errors sanitizeMessage से गुज़रते हैं, जो absolute filesystem paths strip करता है। Reconnaissance को एक clean denial मिलता है, directory listing नहीं।

इसे trifecta के विरुद्ध score करें। Private data: अभी भी मौजूद — यही काम है। Untrusted content: अभी भी मौजूद — models जो पढ़ते हैं वह पढ़ते हैं। लेकिन जो पाँव influence को नुकसान में बदलते हैं वे काट दिए गए हैं। Write path अनुपस्थित है, और read path allow-listed, tenant-scoped, capped है — और hashed, इसलिए एक hijacked agent ने जो भी पूछा वह postmortem के लिए replayable है

204 excluded 4,778 capabilities in the contract 4,574 exposed to the model — every one read-only 4,564 deterministic within the declared scope 0 write capabilities — nothing to name, nothing to run
पूरी vocabulary, to scale — एक hijacked prompt अलग values चुन सकता है, कभी अलग verb नहीं (source: SQAI capability contract)।

सीमा, फिर से खींची गई

Prompt injection हल नहीं हुआ है, और रिकॉर्ड तर्क देता है कि जल्द नहीं होगा। LLM01 लगातार दो editions से अपनी रैंक पर है। Microsoft की defenses के पीछे एक production system एक email से हार गया। Models hostile text पढ़ते रहेंगे, और उसमें से कुछ आता रहेगा।

लेकिन model को प्रभावित करना और आपके database तक पहुँचना अलग-अलग विफलताएँ हैं, और केवल पहली अपरिहार्य है। Supabase ticket एक incident बनी क्योंकि agent के पास service_role और एक free-form SQL surface था — ठीक वे पाँव जिनकी एक hijack को ज़रूरत है। वही ticket, एक बंद, typed, read-only vocabulary पर aimed, एक log line बन जाती है: unsupported_operation

SQL injection का समाधान कभी कोई smarter filter नहीं था। वह एक ऐसी boundary थी जहाँ data code नहीं बन सकता। वह boundary फिर खींचें — इस बार model के चारों ओर — और नए SQL injection को पुराना अंत मिलता है। बाकी surface /security पर documented है।