تصور کنید یک برنامهنویس برای ارسال پیام جدید کلید Enter را میزند، اما بهجای ارسال متن، ناگهان دستوری برای پاک کردن کل دیتابیس اجرا میشود. این کابوس فنی، نتیجهای از «باگ رضایت» (Consent Bug) است که در ۹ سپتامبر ۲۰۲۶ توسط یکی از توسعهدهندگان در پلتفرم dev.to افشا شد. نویسنده در گزارش خود اشاره میکند که یک «چیپ تأیید» که شبیه به یک اعلان موفقیت (Success Toast) به نظر میرسد، میتواند بهطور تصادفی یک دستور مخرب شل (Shell Command) را اجرا کند، اگر کاربر صرفاً برای ارسال پیام جدید کلید Enter را فشار دهد.
بسیاری از عاملهای چت، تمرکز (Focus) کاربر را در کادر پیامرسان نگه میدارند. وقتی یک عامل (Agent) — شبیه به دستیاری که برای هر کار باید اجازه بگیرد — ابزاری مثل run_command را پیشنهاد میدهد، بسیاری از رابطهای کاربری یک «چیپ» (Chip) کوچک و غیرقابلتمرکز در کنار استریم متن نمایش میدهند. چون تمرکز کیبورد هرگز از کادر متن خارج نمیشود، کاربر هنگام تلاش برای ارسال یک پیام تکمیلی، ممکن است بهطور ناخواسته اکشن «اجازه بده» (Allow) را فعال کند.
همانطور که در تحلیلهای پیشین ما دربارهی امنیت رابطهای کاربری هوش مصنوعی اشاره کردیم، این مشکل یک نقص بصری ساده یا موضوع مربوط به صیقل دادن ظاهر برنامه نیست، بلکه یک خطای ساختاری در دسترسیپذیری (Accessibility) است. این چالش در راستای جلوگیری از دسترسیهای غیرمجاز عاملها به دادههای حساس قرار دارد تا از اجرای دستورات تخریبی در محیطهای عملیاتی جلوگیری شود. در مورد گزارششده، پیشنهاد ابزار شامل یک مرحله read_file و یک مرحله run_command بود. رابط کاربری از یک div با یک هندلر کلیک و یک انیمیشن CSS استفاده کرده بود که باعث میشد این المان کاملاً از ترتیب Tab مرورگر حذف شود.
کالبدشکافی شکست
به نقل از گزارش dev.to، این پیادهسازی معیوب چهار خطای بحرانی داشت:
- حفظ تمرکز نادرست: تمرکز روی المان
#composerباقی ماند، به این معنی که کاربر هرگز متوجه نشد که یک تصمیم لازم است و هشدار لازم به او داده نشد. - حذف از ترتیب Tab: با استفاده از
divبهجای دکمه یا دیالوگ، گزینههای «تأیید» و «رد» برای کاربرانی که فقط از کیبورد استفاده میکنند یا از صفحهخوانها (Screen Readers) بهره میبرند، نامرئی بود. برای مثال، نرمافزار NVDA هرگز افعال مربوط به این دکمهها را نام نبرد. - رضایت ضمنی: سیستم یک تایمر ۸ ثانیهای برای تأیید خودکار (
setTimeout(..., 8000)) پیاده کرده بود؛ یعنی سکوت کاربر به عنوان یک «بله» برای عملیاتهای بالقوه مخرب تلقی میشد. این رویکرد دقیقاً نقطه مقابل پیادهسازی پاسخهای منفی ماشینخوان است که برای جلوگیری از تصمیمات خودسرانه عاملها ضروری است. - تداخل وضعیت: ارسال یک پیام جدید توسط کاربر، پیشنهاد ابزار باز را بازنشانی (Retire) نمیکرد. این امر اجازه میداد فشار بعدی کلید Enter، همان برنامه قبلی را اجرا کند.
علاوه بر این، استفاده از aria-live="polite" روی متن گفتگو باعث میشد صفحهخوانها رشتههای ناقص JSON — مانند {"cmd":"rm — را بخوانند، در حالی که درخواست اصلی «آیا اجازه میدهید؟» در صف انتظار میماند و ساکت بود. ناحیه Live مشغول خواندن دادههای بیمعنی بود و تصمیم واقعی هرگز اولویت صف را به دست نیاورد.
عیبیابی شکاف رضایت
نویسنده برای یافتن ریشه مشکل، بهجای حدس زدن با ویژگیهای ARIA، وضعیت ماشین و تمرکز را ثبت کرد. او از یک کد کمکی محلی برای ردیابی تمرکز در هر تغییر وضعیت استفاده کرد:
const focusLog = [];
function traceFocus(reason) {
const id = document.activeElement && document.activeElement.id;
focusLog.push({ reason, id, at: Date.now() });
console.table(focusLog.slice(-8));
}
نتایج تکاندهنده بود: هر توکن برنامهریزی تابع traceFocus("chunk") را فراخوانی میکرد، اما شناسه تمرکز (ID) همچنان روی composer باقی مانده بود. چیپ هرگز در لاگ ظاهر نشد، که ثابت کرد مسیر کیبورد نمیتواند به دکمههای «رد» یا «تأیید» برسد. این موضوع تأیید کرد که چیپ در واقع یک Toast بود که سعی داشت وظیفه یک Dialog را انجام دهد.
رویکرد جدول وضعیت
برای اصلاح منطق، نویسنده وضعیتهای قانونی را ترسیم کرد تا رضایت ضمنی حذف شود. او معتقد است اگر یک انتقال (Transition) را نتوان نامگذاری کرد، رابط کاربری بهطور ناخودآگاه یک «بله» ضمنی اختراع میکند:
- بیکار (Idle): کادر پیام فعال؛ بدون اعلان.
- برنامهریزی (Planning): کادر پیام غیرفعال؛ متن وضعیت: «در حال برنامهریزی ابزارها».
- در انتظار تأیید (Awaiting_confirm): کادر پیام غیرفعال (Inert)؛ دیالوگ مودال باز؛ تمرکز روی دکمه «رد»؛ اعلان: «دو ابزار پیشنهاد شد. رد یا تأیید کنید».
- در حال اجرا (Executing): کادر پیام غیرفعال؛ دیالوگ بسته؛ برای هر مرحله یک فعل اعلام میشود.
- شکست (Failed): کادر پیام فعال؛ دیالوگ بسته؛ اعلان: «کدام مرحله شکست خورد».
- لغو شده (Cancelled): کادر پیام فعال؛ دیالوگ بسته؛ اعلان: «اجرای ابزار لغو شد».
بهطور قابل توجهی، این جدول مواردی مثل implicit_allow (اجازه ضمنی)، timeout_yes (بله بر اساس تایمر) یا «Enter در کادر متن یعنی موافقت» را حذف میکند. بدون اینها، سیستم بهجای امیدواری به اینکه کسی متوجه نشود، شروع به درخواست رضایت واقعی میکند.
راهکار پیشنهادی: مودالهای بومی
نویسنده توصیه میکند چیپها با DialogElement.showModal() بومی HTML جایگزین شوند. این رویکرد یک «تله تمرکز» (Focus Trap) ایجاد میکند، یک هندلر برای کلید Escape فراهم میکند و یک پسزمینه غیرفعال (Inert Backdrop) ایجاد میکند، بدون اینکه نیاز به کدهای سفارشی و پیچیده برای لایههای رویی باشد.
در این ماشین وضعیت، سیستم از idle به planning و سپس به awaiting_confirm میرود. در این وضعیت، دکمه «رد» اولین کنترل در ترتیب DOM است و تمرکز اولیه را دریافت میکند. این تضمین میکند که یک فشار تصادفی روی Enter، بهجای یک «بله» خطرناک، به صورت پیشفرض منجر به یک «نه» ایمن شود.
جزئیات پیادهسازی فنی
- مدیریت تمرکز: استفاده از
dialog.showModal()تضمین میکند کاربر نتواند با کادر پیام تعامل کند تا زمانی که به پیشنهاد پاسخ دهد. فراخوانیdenyBtn.focus()بهصورت صریح انجام میشود. - بازنشانی پیشنهاد: یک تابع
retireProposalبرای پاکسازی پیشنهادها و تایمرها در صورتی که کاربر حین باز بودن اعلان، پیام جدیدی بفرستد، استفاده میشود. این کار از انباشت چندین چیپ جلوگیری میکند. منطق برنامه تأکید دارد که یک ارسال جدید باید پیشنهاد قبلی را بازنشانی کند، نه اینکه چیپ دومی را روی آن انباشته کند. - نشانهگذاری معنایی: بهجای استفاده از یک چیپ شمارشی (مثلاً «تأیید ۲ ابزار»)، از یک لیست
<ol>استفاده شده است. این کار افعال واقعی را فراهم میکند، مانندread_file on package.jsonو یک «شانه بالا انداختن» را به یک تصمیم آگاهانه تبدیل میکند. - وضعیت کادر پیام: هنگامی که برنامهریزی شروع میشود، کادر پیام با
aria-disabled="true"علامتگذاری شده و یک المان<p id="composer-state">همراه آن، عبارت «در حال برنامهریزی ابزارها. لغو در دسترس است» را اعلام میکند.
مکانیسمهای بازیابی و منطق پیشرفته
برای تضمین اینکه سیستم واقعاً ایمن است، نویسنده منطقهای خاصی را برای مدیریت انتقال از پیشنهاد به وضعیت بیکار پیاده کرد:
- الگوی اولویت رد (Deny-First): با قرار دادن دکمه رد در ابتدای DOM و متمرکز کردن فوری آن، سیستم تضمین میکند که رایجترین اقدام تصادفی کیبورد (Enter) منجر به لغو عملیات شود.
- محرک بازنشانی: منطق
form.addEventListener("submit", ...)بررسی میکند که آیا وضعیتawaiting_confirmاست یا خیر. اگر چنین باشد، تابعretireProposal("Proposal retired because you sent a new message.")را فراخوانی کرده و سریعاً خارج میشود تا از اجرای ابزار جلوگیری کند. - مسیر خروج: هندلر
dialog.addEventListener("cancel", ...)تضمین میکند که کلید Escape همان مسیر دکمه رد را طی کرده و به وضعیتcancelledمنجر شود.
چکلیست پیادهسازی برای استفاده ایمن از ابزار
توسعهدهندگان تشویق میشوند تا از یک ماتریس QA سختگیرانه برای جلوگیری از «سرقت رضایت» پیروی کنند. این اقدامات در کنار رعایت اصول بنیادین امنیت در عاملهای هوش مصنوعی میتواند ریسک نشت دادهها و اجرای دستورات مخرب را به حداقل برساند:
۱. تأیید تمرکز: ثبت document.activeElement.id در هر تغییر وضعیت برای اطمینان از انتقال تمرکز به دیالوگ.
۲. تست بدون موس: جدا کردن موس و استفاده از Tab از کادر پیام برای شمارش توقفهای واقعی.
۳. تأیید بازنشانی: اطمینان از اینکه ارسال پیام دوم در حالی که یک پیشنهاد باز است، فوراً پیشنهاد قبلی را میکشد.
۴. حذف تایمرها: حذف تمام تایمرهای «تأیید خودکار»؛ ابزارها هرگز نباید بدون یک اقدام صریح اجرا شوند.
۵. بررسی جریان صفحهخوان: اطمینان از اینکه عنوان دیالوگ، تصمیم را نام میبرد (نه نام محصول را) و role="status" تغییرات وضعیت را یکبار اعلام میکند، نه برای هر توکن.
۶. بررسی ترتیب DOM: تأیید اینکه لیست ابزارها یک <ol> است و هر آیتم شامل هر دو مورد «فعل» و «هدف» است.
تست با نوسانات شبکه (Jitter)
نویسنده از دسترسی رایگان MonkeyCode به مدلها و گزینه سرور رایگان برای تست این اصلاحات استفاده کرد. استریمهای داده در دنیای واقعی اغلب متوقف میشوند، نام یک ابزار را در چندین تکه (Chunk) تقسیم میکنند یا سه پیشنهاد را بهطور همزمان میفرستند. این نوسانات زمانی معمولاً در محیطهای شبیهسازی شده (Local Mocks) پنهان میمانند.
استفاده از یک استریم ریموت ثابت کرد که دیالوگ باید حتی زمانی که آرگومانها دیر میرسند، پایدار بماند. این تستها تأیید کرد که اگر انسان به صحبت ادامه دهد، پیشنهاد باید بازنشانی شود، فارغ از سرعت استریم. همچنین یک «دروازه همرده» (Sibling Gate) برای اولین فراخوانی ریموت توصیه میشود تا رضایت حریم خصوصی را مشابه رضایت ابزار، در مسیر مرورگر مسدود کند.
ماتریس QA محیطی
برای تضمین استحکام، نویسنده تست روی این جابجاییهای خاص را پیشنهاد میکند:
- Firefox/Windows/NVDA: برنامهریزی $\rightarrow$ باز شدن دیالوگ؛ NVDA عنوان و سپس دکمه «رد» را میخواند. «رد» اولین عبارت بعد از «در حال برنامهریزی ابزارها» است.
- Chrome/Windows/NVDA: خروج با Escape از وضعیت دیالوگ $\rightarrow$ لغو شده؛ تمرکز به کادر پیام باز میگردد.
- Safari/macOS/VoiceOver: اجرای VO+Space روی «رد» $\rightarrow$ ابزارها هرگز اجرا نمیشوند.
- Chrome/macOS (فقط کیبورد): چرخه Tab داخل دیالوگ $\rightarrow$ عدم دسترسی به کادر پیام یا لینکهای استریم.
- هر مرورگر (استریم کند): رسیدن آرگومانها بعد از باز شدن دیالوگ $\rightarrow$ بهروزرسانی لیست بدون جابجایی تمرکز.
- هر مرورگر (پیام جدید): ارسال پیام دوم حین درخواست $\rightarrow$ بازنشانی پیشنهاد، عدم اجرا.
محدودیتها و محدوده
این الگو بهطور خاص برای UX تعاملی مرورگر طراحی شده است، جایی که ابزارها میتوانند فایلها را تغییر دهند یا دستورات را اجرا کنند. این یک جایگزین برای احراز هویت سمت سرور یا سیاستهای پیشامضا شده CI/CD نیست. همچنین نباید برای عاملهای بدون نظارت (Unattended Agents) یا توضیحات صرفاً خواندنی استفاده شود.
اگر در حال جایگزینی اعلانهای مجوز سیستمعامل در یک شل بومی هستید یا قصد داشتید تأیید خودکار را نگه دارید و «فقط آن را بلندتر اعلام کنید»، از این رویکرد استفاده نکنید. برای کسانی که ابزارهای حساس مانند «حذف» یا «استقرار» (Deploy) را پیاده میکنند، نویسنده پیشنهاد میکند سطحی برای بازبینی ایجاد شود که با رفرش صفحه از بین نرود، زیرا یک دیالوگ سمت کلاینت به تنهایی یک راهکار امنیتی کامل نیست.
مسیر بازیابی عمداً ساده و خستهکننده نگه داشته شده است: بعد از «رد» یا شکست، تمرکز به کادر پیام برمیگردد و هیچ چیز بهطور خودکار تکرار نمیشود. اضافه کردن یک دکمه Stop دوم یا بازگرداندن چیپها، فقط همان حفره رضایتی را میپوشاند که بهسختی بسته شده است.
آنچه این موضوع برای این حوزه به همراه دارد، تغییر در نحوه نگاه ما به «AI UX» است. ما باید از برخورد با رضایت ابزار به عنوان یک اعلان (Toast) دست برداریم و با آن به عنوان یک دروازه دسترسی (Modal) برخورد کنیم. اگر انتقالی در یک جدول وضعیت نامگذاری نشود، رابط کاربری ناگزیر یک «بله» ضمنی اختراع میکند که کاربر هرگز قصد آن را نداشته است.
گام بعدی شما
- اگر از استفاده از ابزار (Tool Use) در اپلیکیشن خود استفاده میکنید، تمام
setTimeoutهای تأیید خودکار را حذف کنید. - تمرکز کیبورد را در لحظه نمایش پیشنهاد ابزار، بهصورت صریح به دکمه «رد» منتقل کنید.
- از
dialog.showModal()بهجای المانهای سفارشی برای ایجاد تله تمرکز استفاده کنید.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو