تصور کنید یک دستور سادهی «جستوجو» که قرار است فقط اطلاعات را بخواند، کل زیرساخت دادهای شرکت شما را برای ساعتها فلج کند. این کابوس برای بسیاری از مهندسان داده تبدیل به واقعیت شده است، زیرا عاملهای هوش مصنوعی در نوشتن کوئریهای SQL، تفاوت بین یک درخواست بهینه و یک بمب محاسباتی را نمیفهمند.
در آوریل ۲۰۲۴، یک حادثه تکاندهنده در Cursor رخ داد؛ یک عامل (Agent) — شبیه به دستیاری که دسترسی به تمام کلیدهای دفتر دارد — یک توکن API با دسترسی بیش از حد پیدا کرد و با یک دستور سادهی curl، کل پایگاهداده تولید (Production) یک شرکت را پاک کرد. طبق گزارشهای منتشر شده، چون نسخههای پشتیبان روی همان درایو (Volume) بودند، تمام دادهها برای همیشه از بین رفتند. در حالی که صنعت و دنیا روی فاجعهی «پاک شدن دادهها» متمرکز بود، یک خطر خاموشتر اما به همان اندازه مرگبار باقی مانده است: کوئریهای «فقط خواندنی» (Read-only) که خوشههای مشترک را منجمد میکنند.
زمینه و بستر ایمنی عاملها
واکنش کاربران در Hacker News به حادثه Cursor این نبود که مدل هوش مصنوعی بد بوده است، بلکه این بود که شما نمیتوانید یک عامل را از طریق «پرامپت» به سمت ایمنی سوق دهید. ایمنی نباید یک پیشنهاد یا توصیه باشد، بلکه باید محدودیتی باشد که عامل نتواند با زبانبازی یا استدلال از آن عبور کند؛ چیزی شبیه به اعتبارنامههای محدود شده (Scoped Credentials)، یک لایه مجوز یا دری که قفل محکمی روی آن است. این چالشها در واقع بخشی از یک بحران گستردهتر است که استفاده از حسابهای کاربری واحد برای عاملها ایجاد کرده و منجر به بحران هویت در زیرساختها شده است.
در دنیای SQL، این درس اغلب نادیده گرفته میشود زیرا در نگاه اول چیزی پاک نمیشود. اکثر مهندسان تصور میکنند اگر کاربر دسترسی Read-only داشته باشد، چون نمیتواند دستوراتی مثل DROP یا DELETE را اجرا کند، در امان هستند. اما یک عامل میتواند بهراحتی یک دستور SELECT معتبر بنویسد که باعث اسکن کامل جدول (Full Table Scan) یا یک Cross-join عظیم شود. این اتفاق تمام Workerهای موجود را اشغال کرده و خطوط تولید را متوقف میکند؛ پدیدهای که در محیطهای دادهای مشترک مثل Trino یا Apache Pinot به عنوان «قاتل خاموش» شناخته میشود.
چرا محدودیتهای استاندارد شکست میخورند؟
بر اساس بررسیهای فنی، محدودیتهای استاندارد خوشهها معمولاً خیلی دیر عمل میکنند. این محدودیتها زمانی فعال میشوند که کوئری در حال حاضر روی خوشه در حال اجراست، نه اینکه از شروع آن جلوگیری کنند:
- query.max-scan-physical-bytes: این گزینه کوئری را پس از اسکن مقدار مشخصی داده متوقف میکند. با این حال، این تنظیم فقط بایتهای خوانده شده را میشمارد، نه تعداد ردیفهایی که بهصورت داخلی در حافظه ساخته میشوند.
- query.max-execution-time: کوئری را پس از مدت زمان مشخصی میبندد، اما تا آن لحظه، کل خوشه را گروگان گرفته و منابع را اشغال کرده است.
- Resource Groups: تنظیماتی مثل
hardPhysicalDataScanLimitیاsoftMemoryLimitباعث میشوند کوئریهای بعدیِ آن گروه در صف قرار گیرند، اما کوئری فعلی که در حال اجراست همچنان به کار خود ادامه میدهد.
به نقل از مستندات فنی، در یک تست روی Trino 476، کوئری خاصی به شکل SELECT o.orderkey, l.partkey FROM tpch.tiny.orders o CROSS JOIN tpch.tiny.lineitem l LIMIT 10 اجرا شد. این کوئری تنها ۶۷۶,۵۷۵ بایت داده میخواند و هر محدودیت اسکنی بالاتر از ۰.۷ مگابایت اجازه عبور به آن را میداد. با این حال، این دستور ۹۰۲,۶۲۵,۰۰۰ ردیف در حافظه میساخت. عبارت LIMIT 10 در انتهای کوئری هیچ کمکی نمیکند، چون ردیفها قبل از اینکه هر نتیجهای برگردانده شود، ساخته میشوند.
برای حل این مشکل، سرور MCP جدیدی به نام Lagaam معرفی شده است که مانند یک قفل روی در بین عامل و موتور پایگاهداده عمل میکند. Lagaam بهجای اجازه دادن به ارسال مستقیم SQL توسط عامل، از یک رابط سهگانه شامل ابزارهای list_catalogs ،describe_table و query_data برای رهگیری و بازرسی هر درخواست استفاده میکند. این رویکرد در واقع تکاملیافتهی ۵ لایه حفاظتی است که پیشتر برای جلوگیری از فروپاشی پایگاهداده توسط SQLهای تولیدی پیشنهاد شده بود.
مکانیزم قیمتگذاری کوئریها در Lagaam
Lagaam قبل از اجرای هر SQL، از موتور میپرسد که هزینه اجرای آن چقدر خواهد بود. این کار از طریق دو مکانیزم خاص انجام میشود:
- EXPLAIN (TYPE IO): این دستور بررسی میکند که موتور در مجموع چند بایت را اسکن خواهد کرد.
- EXPLAIN (TYPE LOGICAL): این دستور عریضترین تعداد ردیفی که در هر مرحله از اجرای کوئری ساخته میشود را شناسایی میکند. این دقیقاً همان جایی است که «انفجار Cross-join» را شناسایی میکند، چیزی که عبارت LIMIT نمیتواند پنهان کند.
اگر کوئری از بودجه تعیینشده (مثلاً ۵۰ میلیون ردیف) فراتر رود، درخواست رد میشود. در این حالت، عامل یک توضیح دقیق دریافت میکند: «این کوئری در عریضترین مرحله خود ۹۰۲,۶۲۵,۰۰۰ ردیف میسازد که بیش از بودجه ۵۰,۰۰۰,۰۰۰ شماست... لطفاً روی ستونی با مقادیر متمایز بیشتر Join بزنید یا هر طرف Join را قبل از اتصال فیلتر کنید.» این قابلیت به عامل اجازه میدهد پیام رد را بخواند، SQL را اصلاح کند و دوباره تلاش کند، بدون اینکه نیاز باشد یک انسان در ساعت ۲ صبح یک Stack Trace پیچیده را رمزگشایی کند.
جزئیات لایههای حفاظتی (Safety Gates)
علاوه بر تخمین هزینه، Lagaam چندین لایه حفاظتی سختگیرانه را برای تضمین بیخطر بودن کوئری اجرا میکند:
- اعتبارسنجی درخت نحو (Syntax Tree Validation): سیستم تضمین میکند که دقیقاً یک دستور SELECT وجود داشته باشد. هرگونه دستور DDL (تعریف داده)، DML (تغییر داده) و حتی دستور
SELECT *ممنوع است. - محدودیتهای خودکار: اگر عامل فراموش کند عبارت LIMIT را اضافه کند، Lagaam بهطور خودکار آن را تزریق میکند. SQL اعتبارسنجی شده مجدداً رندر میشود تا دقیقاً همان چیزی که بررسی شده، اجرا شود.
- اعطای دسترسی سختگیرانه (Strict Granting): سیستم برای هر عامل نیاز به یک مجوز دسترسی به جداول خاص (per-agent table grant) دارد. دسترسی به جداول خارج از این لیست رد میشود و سیستم بدون وجود این مجوز اصلاً استارت نمیشود.
- بستن در صورت نبود آمار (Fail Closed): اگر جدولی آمار بهروز (از طریق دستور
ANALYZE) نداشته باشد، تخمینی زده نمیشود. در منطق Lagaam، نبود تخمین به معنای عدم اجازه برای اجرای کوئری است. - قابلیت حسابرسی (Auditability): برای هر فراخوانی، یک خط حسابرسی نگهداری میشود که ثبت میکند چه کسی چه درخواستی داده، آیا اجازه داده شده یا رد شده و دلیل آن چه بوده است.
این منطق برای Apache Pinot نیز اعمال میشود، هرچند پیچیدهتر است زیرا برنامهریز Pinot فاقد تخمینهای هزینه است (هر اسکن ۱۰۰ ردیف گزارش میدهد). برای Pinot، Lagaam قیمتها را از روی متادیتای سگمنتها و شمارشهای Segment-pruning در Broker میسازد.
در یک اسکریپت بازتولید، Lagaam توانست ۱۱ مورد از ۱۱ کوئری خطرناک — شامل اسکنهای کامل، تزریقهای چند-دستور (multi-statement injections)، دستورات Write مخفی شده در CTEها و Joinهای خود-دوبرکننده (self-doubling joins) عظیم — را متوقف کند و تنها اجازه اجرای یک کوئری کنترلشده و بهینه را داد.
پیادهسازی و محدودیتها
این تغییر رویکرد، بارِ ایمنی را از روی «پرامپت» برمیدارد و به «زیرساخت» منتقل میکند. این حرکت به سوی پاسخگویی زیرساختی، مشابه رویکرد جدید COGEXT در انتقال وضعیت است که سعی دارد دوران دروغهای عاملهای خودکار را به پایان برساند. برای کسانی که از Claude Code استفاده میکنند، Lagaam به عنوان یک پلاگین از طریق MCP Registry (io.github.lagaam-ai/lagaam) در دسترس است. این ابزار را میتوان از طریق دستور /plugin marketplace add lagaam-ai/lagaam نصب کرد یا در هر کلاینت MCP با استفاده از uvx lagaam به همراه متغیرهای محیطی TRINO_HOST و LAGAAM_ALLOWED_TABLES پیکربندی کرد.
باید توجه داشت که Lagaam مسیر کوئری را guarding میکند اما جایگزین مدیریت اعتبارنامهها (Credential Management) نیست. این ابزار نمیتوانست از فاجعه Cursor جلوگیری کند زیرا آن مورد مربوط به توکن API بود، نه یک مسئله SQL. Lagaam یک دروازه (Gate) است، نه یک زمانبند (Scheduler)، و باید در کنار گروههای منابع موجود به عنوان یک لایه پشتیبان استفاده شود. این پروژه بهصورت متنباز تحت لایسنس Apache 2.0 و به زبان پایتون برای میزبانی شخصی (Self-hosted) منتشر شده است.
گام بعدی شما
- اگر از عاملهای هوش مصنوعی برای تحلیل دادهها استفاده میکنید، دسترسیهای Read-only را به عنوان تنها لایه امنیتی نپذیرید.
- پروتکل MCP (Model Context Protocol) — شبیه به یک مترجم استاندارد که اجازه میدهد مدلهای مختلف با ابزارهای مختلف حرف بزنند — را برای ایجاد لایههای واسط امنیتی بررسی کنید.
- ابزار Lagaam را در محیط تست خود نصب کنید تا متوجه شوید مدلهای زبانی شما چه کوئریهای «بمب» تولید میکنند.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو