مدل امنیتی
‏aipager از طرف شما Claude Code را اجرا میکند و از تلگرام کنترل میشود. دو سؤال پیش میآید:
- چه کسی میتواند ربات را کنترل کند؟
- ربات وقتی کنترل شد چه کارهایی میتواند بکند؟
مرز اعتماد#
هر گردانندهای که ربات دارد (پیام، فایل، ویس، دکمه) پشت فیلتر چت python-telegram-bot است که از چتهای تنظیمشده در ~/.config/aipager/aipager.yaml ساخته میشود، یا (برای فرمانها) پشت همان بررسی در مجوزدهی خود aipager. تنها استثنا فقط فرستندهها را در گروههایی که تنظیم نشدهاند یادداشت میکند (پایین). فقط چتهای تنظیمشده میتوانند با ربات کار کنند. هیچ چیزی از هیچ چت دیگری اجرا یا به یک سشن فرستاده نمیشود. یک چت خصوصی که تنظیم نشده یک بار جوابِ «🚫 این ربات برای گفتگو با شما تنظیم نشده.» را میگیرد. در گروهی که تنظیم نشده ربات هیچوقت جواب نمیدهد، ولی یادداشت میکند چه کسی آنجا به آن پیام داده (آیدی تلگرام، یوزرنیم و اسمش، و آیدی، عنوان و نوع گروه) در ~/.claude/aipager-pending-users.json، تا aipager config بتواند وقتی aipager اجرا میشود یک گروه جدید و آدمهایش را پیدا کند. هر کسی میتواند ربات را به یک گروه اضافه کند، پس هر کسی میتواند یک خط به آن فایل اضافه کند؛ هیچ دسترسیای نمیدهد. این فایل فقط برای شما قابل خواندن است (حالت دسترسی 0600)، در هر نوشتن کامل عوض میشود (هیچوقت نیمهنوشته نمیماند) و حداکثر ۲۰۰ نفر آخر را نگه میدارد و هر کسی را که ۳۰ روز دیده نشده کنار میگذارد. در استفادهٔ تیمی، یک فهرست مجاز برای هر کاربر با نقشها رویش اضافه میشود، و در گروه فقط پیامی که خطابش ربات است (یک فرمان، جواب به ربات، منشن، یا زدن کیبوردش) اجرا میشود، چه ربات ادمین گروه باشد چه نه (استفادهٔ تیمی و گروه را ببینید).
یعنی چیزی که در معرض «دنیای بیرون» است:
- توکن ربات (یک راز).
- آیدی چت (یک عدد بلند).
اگر هر دو لو بروند، مهاجم میتواند aipager شما را کنترل کند. اگر فقط توکن لو برود، مهاجم میتواند پیامهایی را که از چت شما به ربات فرستاده میشود بخواند، ولی نمیتواند فرمانی بفرستد که aipager اجرایش کند. اگر فقط آیدی چت لو برود، به هیچ دردی نمیخورد: ربات بدون توکن اصلاً نمیتواند با تلگرام حرف بزند.
رازها#
| راز | کجا | حالت دسترسی |
|---|---|---|
| توکن ربات | ~/.config/aipager/aipager.yaml | بهطور پیشفرض 600 |
| آیدیهای چت | همان فایل | بهطور پیشفرض 600 |
توکن به لاگ aipager نمیرسد: خطهای درخواست HTTP که توکن در آنهاست لاگ نمیشوند، و پیامهای خطا، aipager doctor و ویزارد هر چیزی را که شکل توکن ربات دارد پنهان میکنند. آیدیهای چت در لاگ aipager دیده میشوند (مثلاً در خطهای محدودیت سرعت)، پس خروجی aipager logs را خصوصی بدانید. هیچکدام در ریپو commit نمیشود: تنظیمات در ~/.config خود کاربر است، نه در ریپو. روند انتشار PyPI با Trusted Publisher هم هیچوقت به رازها دست نمیزند؛ احراز هویتش با OIDC است.
اگر فکر میکنید توکن لو رفته، آن را از @BotFather باطل کنید (/revoke)، یک توکن جدید بسازید و دوباره aipager config را اجرا کنید.
اطلاعات ورود Claude: چه چیزی واقعاً از آن محافظت میکند#
aipager یک ایجنت خودمختار با دسترسی Bash را با کاربر یونیکس شما اجرا میکند. پس هر رازی که aipager بتواند بخواند، ایجنتی که اجرا میکند هم میتواند بخواند. ارزش دارد این را صاف بگوییم، چون راحت میشود سراغ نوع اشتباهی از کنترل رفت.
~/.config/aipager/ در فهرست ممنوع safety.py است و جلوی خواندن فایلهای زیر آن را در سشنی که از تلگرام کنترل میشود میگیرد. این یک کنترل مسیر است و اینجا هیچ کاری نمیکند: اطلاعات ورود Claude (CLAUDE_CODE_OAUTH_TOKEN یا ANTHROPIC_API_KEY) از راه مسیر به دست نمیآید؛ در محیط پروسهٔ هر سشنی که شروع میشود به ارث میرسد. داخل یک سشن، echo $CLAUDE_CODE_OAUTH_TOKEN آن را چاپ میکند، فهرست ممنوع باشد یا نباشد. یک قانون مسیر فایل، شکل اشتباهی از کنترل برای یک متغیر محیطی است.
اطلاعات ورود کجاست#
| نوع اجرا | منبع | حالت دسترسی |
|---|---|---|
| سرویس systemd کاربری | LoadCredential=claude_oauth:%h/.config/aipager/daemon.env، خواندهشده از $CREDENTIALS_DIRECTORY/claude_oauth | 600 (daemon.env)؛ systemd اطلاعات ورود را از محیط خود سرویس بیرون نگه میدارد |
| macOS / Docker / بدون systemd | ~/.config/aipager/daemon.env، مستقیم خوانده میشود | 600 |
هر دو مسیر با یک کد (aipager/daemon_secrets.py) خوانده میشوند و از طریق جدول env= زیرپروسه به سشن داده میشوند، هیچوقت داخل رشتهٔ دستور bash -c گذاشته نمیشوند. /proc/PID/cmdline را همه میتوانند بخوانند (0444)؛ /proc/PID/environ فقط مال صاحبش است (0400).
آنچه LoadCredential= بهعلاوهٔ 0600 واقعاً میدهد: کاربرهای یونیکس دیگر روی همان کامپیوتر نمیتوانند اطلاعات ورود را بخوانند، و هیچوقت در systemctl show، در نسخهٔ پشتیبان یک فایل سرویس، یا در اسکرینشاتی از خروجی journalctl که یک نفر در یک گزارش باگ میگذارد دیده نمیشود. این بین aipager و ایجنتی که aipager اجرا میکند مرزی نمیسازد؛ چنین مرزی وجود ندارد و هیچ مقدار مهندسی دسترسی فایل نمیتواند بسازدش، چون کار اصلی ایجنت این است که در همان محیط به جای صاحبش عمل کند.
تنها کنترلی که واقعاً کار میکند#
خود اطلاعات ورود را محدود کنید. aipager را با یک حساب جدای Anthropic اجرا کنید، یا با یک کلید API که سقف هزینه دارد؛ نه با همان توکنی که برای لاگین تعاملی claude خودتان استفاده میکنید. باطل کردن اطلاعات ورود aipager نباید هیچوقت خود شما را بیرون بیندازد؛ اگر بیندازد، این دو یک اطلاعات ورودند و هیچکدام محدود نشده. aipager doctor --fix میتواند یک توکن موجود را برای پر کردن daemon.env پیدا کند، ولی اینکه کدام توکن را به آن بدهید تصمیم خود شماست، و این تنها تصمیمی در این صفحه است که از دسترسیهای فایلهای اطرافش مهمتر است.
عیبیابی لاگین بدون حدس زدن#
aipager doctor (و اعلان یکخطی که در هر شروع aipager یک بار فرستاده میشود) وضعیت لاگین را از claude auth status خود Claude Code میگیرند، هیچوقت از یک بررسی فایل دستساز. سه حالت همهجا با متن جدا از هم نگه داشته میشوند:
-
auth: <method> (<source>): لاگین بودن تأیید شده. -
auth: none (not logged in): لاگین نبودن تأیید شده. -
auth: unknown (...): خود بررسی شکست خورد (تمام شدن مهلت، نبودن فایل اجرایی، خروجی نامفهوم) یا فایل اجرایی قدیمیتر از نسخهای است که خروجی JSON را اضافه کرد. این هیچوقت «لاگین نیست» گزارش نمیشود: aipager به خاطر بررسی لاگینی که از نتیجهاش مطمئن نیست، جلوی شروع یک سشن را نمیگیرد. لاگین از طریق Keychain در macOS، اطلاعات ورودی که فقط refresh token دارد، و لاگینهای غیرفایلی پلن Max از دید هر بررسی فایلی پنهاناند و وگرنه درست مثل «لاگاوتشده» به نظر میرسیدند.
چه کسی تصمیم میگیرد یک ابزار اجرا شود#
دو بررسی جدا تصمیم میگیرند یک ابزار اجرا شود یا نه، و به این ترتیب اجرا میشوند:
- بررسی ایمنی aipager. Claude Code قبل از هر اجرای ابزار، هوک
PreToolUseaipager را اجرا میکند. برای کاری که از تلگرام شروع شده، اجرا را به قانونهای نقش فرستنده و به کف ایمنی (مسیرهای محافظتشده، الگوهای دستور) محدود میکند و هر چه را آنها رد کنند رد میکند: آنوقت Claude Code بدون پرسیدن از کسی ابزار را رد میکند، هر چه قانونهای خودش بگویند. پیامی که در ترمینال تایپ شده با این بررسی محدود نمیشود. اینکه چه چیزهایی را پوشش میدهد پایینتر، زیر اجرای قانونها در استفادهٔ تیمی، آمده. - قانونهای خود Claude Code (
~/.claude/settings.json، مود اجازهٔ سشن). اگر اینها بگویند بپرس، Claude Code رویدادPermissionRequestرا میفرستد و aipager درخواست را در تلگرام نشان میدهد (هوکها را ببینید). دکمهای که میزنید بهشکل تصمیم هوک به Claude Code برمیگردد، یا اگر انتظار ۲۰ ثانیهای تمام شده باشد، در پنجرهٔ سؤال خود Claude Code تایپ میشود. اگر قانونهای Claude Code اجرا را رد کنند، هیچوقت به تلگرام نمیرسد.
پس aipager میتواند اجرایی را که Claude Code اجازه میداد رد کند، و وقتی Claude Code میپرسد، aipager هیچوقت خودش بله نمیگوید: اجازه همیشه زدن دکمه توسط یک نفر است. سه چیز اجازه میدهند ابزارها بدون پرسیدن از کسی اجرا شوند:
- مود دسترسی کامل، برای ادمینها (و هر کسی در چت خصوصی خودش). سشنی که در مود دسترسی کامل است با
--dangerously-skip-permissionsشروع میشود: Claude Code چیزی نمیپرسد، پس هر اجرایی که بررسی ایمنی aipager ردش نکند بدون زدن دکمه اجرا میشود. فقط یک ادمین (نقشهایownerوadmin، یا نقشی باcan_manage) میتواند سشنی را در مود دسترسی کامل شروع کند، سشنی را به آن ببرد (/mode full، بعد از تأیید) یا سشنی را در آن ادامه دهد، با یک استثنا: در چت خصوصی خودش با ربات، کسی که نقشش اجازهٔ فرستادن پیام و جواب دادن به Claude را میدهد (can_promptوcan_approve، پسuserبله ولیread_onlyنه) میتواند همین کارها را برای سشنهای خودش بکند (/new !x1، یا تنظیم «سشنهای جدید» روی دسترسی کامل در همان چت، /mode full، /resume)، در تلگرام و در Mini App. هیچوقت در گروه یا در چت خصوصی کس دیگر. بقیه به جایش مود خودکار میگیرند و جواب همین را میگوید. مود دسترسی کامل فقط از سؤالهای خود Claude Code رد میشود: هوک PreToolUse aipager همچنان قانونهای نقش آن شخص (کف ایمنی، محدود بودن به پوشه، قانونهای رد، فایلهای اطلاعات ورود) را روی هر اجرا اعمال میکند، درست مثل مود پرسش و مود خودکار؛ مودی که Claude Code گزارش میدهد هیچوقت آنها را شل نمیکند. بررسی ایمنی aipager در مود دسترسی کامل هم روی هر اجرا انجام میشود، ولی در کارهای خود مالک یا پیامی که در ترمینال تایپ شده چیزی را رد نمیکند. - مود خودکار، که پیشفرض است. سشنی که در مود خودکار است با
--permission-mode autoشروع میشود: بررسی ایمنی خود Claude Code هر اجرا را نگاه میکند، بیخطرها را اجرا میکند و پرریسکها را نگه میدارد، که بعد مثل مود پرسش بهشکل درخواست به تلگرام میرسند. هر سشن در مود خودکار شروع میشود، مگر اینکه چت زیر /settings← سشنهای جدید چیز دیگری انتخاب کرده باشد. هر کسی که میتواند روی یک سشن کار کند میتواند آن را بین مود پرسش و مود خودکار عوض کند (/mode)؛ aipager این کار را بدون ریاستارت و با زدن Shift+Tab در سشن انجام میدهد، هیچوقت با فلگی که دسترسی کامل را وارد چرخهٔ Shift+Tab در Claude Code کند. اگر Claude Code روزی برای سشنی که aipager در دسترسی کامل شروعش نکرده دسترسی کامل گزارش دهد، aipager همان چیزی را که گزارش شده نشان میدهد، یک هشدار در لاگ مینویسد و به خاطر آن هیچوقت سشن را در دسترسی کامل شروع یا ریاستارت نمیکند. محدودیت شناختهشده: مودی که Claude Code گزارش میدهد از سوکت هوک میآید که هر پروسهٔ محلیای میتواند رویش بنویسد (ارتباطات شبکه را ببینید). یک گزارش جعلی میتواند مودی را که سشن در آن نشان داده میشود عوض کند، و وقتی یک تعویض بین پرسش و خودکار منتظر تأیید است، میتواند باعث شود aipager یک بار دیگر Shift+Tab بزند، که ممکن است سشن واقعی را وقتی پرسش خواسته شده در خودکار بگذارد (هوک واقعی بعدی مود واقعی را دوباره نشان میدهد). هیچوقت فلگی را که ریاستارت یا ادامهٔ سشن میدهد عوض نمیکند و هیچوقت به دسترسی کامل نمیرسد: فقط فلگ شروع آن را تنظیم میکند. aipager در سشنی که میداند ممکن است با چرخه به دسترسی کامل برسد Shift+Tab نمیزند: سشنی که در دسترسی کامل شروع شده (حتی بعد از اینکه در ترمینال به پرسش یا خودکار برده شده)، یا سشنی که از وقتی شروع شده به aipager در حال اجرا دسترسی کامل گزارش داده. آنجا هر تعویض مودی یک ریاستارت است که فلگ مود جدید را میدهد و فلگ دسترسی کامل را کنار میگذارد، و یک گزارش جعلی دسترسی کامل فقط میتواند یک تعویض را به ریاستارت تبدیل کند. aipager این را برای سشنی که در ترمینال با فلگی شروع شده که دسترسی کامل را به چرخهٔ Shift+Tab اضافه میکند (--allow- جلوی فلگ دسترسی کامل) نمیداند، یا با --dangerously-skip-permissionsوقتی سشن قبل از اینکه aipager از آن خبردار شود از دسترسی کامل بیرون آمده (aipager آن موقع اجرا نمیشد، یا از آن موقع ریاستارت شده). آنجا رفتن از پرسش به خودکار ممکن است در دسترسی کامل بایستد، و آنوقت کارت میگوید سشن در دسترسی کامل است. برای سشنهای ترمینالی که آدمهایی که ادمین نیستند میتوانند از تلگرام عوضشان کنند، از این فلگها استفاده نکنید. برای اینکه مود دسترسی کامل از تلگرام کار کند، aipager در هر شروعskipDangerousModePermissionPromptرا در ~/.claude/settings.jsonتنظیم میکند (وگرنه هشدار Claude Code قبل از اجرا در این مود جلوی سشن را میگرفت)؛ این کار هشدار را برای سشنهایی که خودتان شروع میکنید هم خاموش میکند. همینطور پوشهای را که هر سشن در آن شروع میشود در ~/.claude.jsonمورد اعتماد علامت میزند (مگر اینکه آن پوشه یا پوشهای بالاتر از آن از قبل مورد اعتماد باشد)، تا سؤال «Do you trust this folder?» در Claude Code جلوی سشنی را که از تلگرام شروع میشود نگیرد: اولین پیام با «No, exit» به آن جواب میداد. تنظیمات .claude/، هوکهای .git/ و فایل .mcp.jsonخود یک پوشهٔ مورد اعتماد میتوانند دستور اجرا کنند؛ برای همین عضوهای بدونcan_manageهیچوقت نمیتوانند این فایلها را بنویسند (مگر اینکهpolicy.yamlدوباره به نقششان shell بدهد). Claude Code اعتماد یک پوشه را به پوشههای زیرش هم میدهد، به شرطی که داخل یک ریپوی git نباشند؛ یک ریپو باز هم سؤال خودش را میپرسد. پوشهٔ خانه وقتی علامت میخورد که سشنی آنجا شروع شود، چون Claude Code «بله»ای را که در پوشهٔ خانه داده شود فقط برای همان یک سشن نگه میدارد. از آن به بعد، Claude Code در هیچ پوشهای زیر پوشهٔ خانهٔ شما که داخل یک ریپوی git نیست چیزی نمیپرسد، در سشنهایی که خودتان شروع میکنید هم./و پوشههای بالاتر از پوشهٔ خانه هیچوقت علامت نمیخورند: این کار به همهٔ چنین پوشههایی روی کامپیوتر میرسید، پس سشنی که در یکی از آنها شروع شود پشت همان سؤال میماند. aipager موقع شروع چیزی را علامت نمیزند، فقط پوشهای را که سشنی دارد در آن شروع میشود. سشن جدید در پوشهای شروع میشود که aipager در آن اجرا میشود (پوشهٔ خانه برایaipager service installروی Linux و macOS و در کانتینر)، یا درAIPAGER_WORK_DIRاگر تنظیم شده باشد؛ لاگ aipager موقع شروع هشدار میدهد اگر آن پوشه/یا پوشهای بالاتر از پوشهٔ خانه باشد و مورد اعتماد نباشد. نسخههای قبل از این، پوشهٔ aipager را در هر شروع علامت میزدند، از جمله/برای سرویس macOS (که در/اجرا میشد)؛ aipager نمیتواند چنین ردیفی را از ردیفی که خودتان ساختهاید تشخیص دهد، پس دست به آن نمیزند. aipager به ~/.claude.jsonو ~/.claude/settings.jsonدست نمیزند اگر نتواند کامل بخواندشان، یا اگر یک Claude در حال اجرا همان وقتی که aipager داشت آنها را آپدیت میکرد تغییرشان داده باشد. - همیشه اجازه بده، که قانون «دیگر نپرس» را که Claude Code با درخواست پیشنهاد کرده بود، درست مثل همان گزینه در پنجرهٔ خودش، به Claude Code برمیگرداند.
برای بستن یک ابزار برای همه، آن را در ~/.claude/settings.json رد کنید؛ برای بستنش برای بعضیها، از قانونهای نقششان در policy.yaml استفاده کنید (استفادهٔ تیمی را ببینید).
آنچه درخواست نشان میدهد همان چیزی است که اجرا میشود. درخواست اجازه دستور یا مسیر واقعی را زیر توضیح Claude نشان میدهد. نویسههای کنترل جهت یونیکد (U+202A تا U+202E، U+2066 تا U+2069، U+200E، U+200F، U+061C) ترتیبی را که تلگرام، یک مرورگر یا یک گوشی متن بعدشان را میکشد عوض میکنند، در حالی که shell نویسهها را به ترتیب اجرا میکند، پس میتوانند کاری کنند که یک دستور شبیه دستور دیگری خوانده شود (حقهٔ «Trojan Source»، CVE-2021-42574). در هر درخواست، سؤال، پیشنمایش diff و رکورد ممیزی، اینها بهشکل علامتهای دیدنی (⟨U+202E⟩) نشان داده میشوند و درخواست هشدار میدهد که ممکن است دستور چیزی غیر از آنچه به نظر میرسد اجرا کند. بقیهٔ نویسههای نامرئی همانطور که هستند میمانند.
اجرای قانونها در استفادهٔ تیمی#
در استفادهٔ تیمی، aipager لایهٔ خودش را زیر دکمهها اضافه میکند: هر پیامی که از تلگرام میآید یک یادداشت اجازه همراه دارد (چه کسی فرستاده و نقشش چه اجازهای میدهد) که در /tmp/claude-notes-<session>/ نوشته میشود، یک فایل برای هر پیام. وقتی Claude یک پیام را برمیدارد، ابزار کمکی aipager-hook آن را با این یادداشتها جور میکند و قانونهای فرستنده را جایی میگذارد که بررسی PreToolUse میخواند. این ویژگیها پایهایاند:
- سختترین قانون برنده است. اگر Claude پیامهای بیش از یک نفر را در یک کار بردارد (aipager پیامها را نگه میدارد تا این اتفاق نیفتد، ولی اگر باز هم بیفتد امن میماند)، آن کار با محدودکنندهترین ترکیب اجرا میشود؛ پیام یک مالک هیچوقت نمیتواند اختیاراتش را به پیام کس دیگری قرض بدهد.
- یک کار در جریان هیچوقت بازتر نمیشود. پیامی از یک نفر دیگر تا تمام شدن کار در جریان صبر میکند و بعد بهعنوان کار خودش با قانونهای فرستندهاش اجرا میشود. اگر باز هم یکی به کار در جریان اضافه شود، کار سختترین قانونهای همهٔ کسانی را که در آن هستند نگه میدارد؛ پیام تلگرامی که به کاری اضافه شود که شما در ترمینال تایپ کردهاید، بقیهٔ آن کار را به قانونهای فرستندهاش محدود میکند (پیام یک مالک چیزی را عوض نمیکند).
- نقش شما همان نقشتان در چتی است که در آن کار میکنید. کسی که در چند چت هست (مثلاً
userدر یک گروه وownerدر چت خصوصی خودش) برای هر چیزی که در گروه میفرستد یا میزند نقش گروه را دارد و در چت خصوصی نقش آنجا را، به هر ترتیبی که چتها درaipager.yamlآمده باشند. - ناشناخته یعنی محدود. پیامی که نمیشود به کسی نسبتش داد با کف داخلی اجرا میشود (بدون معافیت، با همهٔ قانونهای رد فعال)، هیچوقت مثل یک پیام بیمحدودیت ترمینال. پیامهایی که مستقیم در ترمینال تایپ میکنید بیمحدودیت میمانند؛ هر چیزی که روی هر خطی علامت تلگرام را داشته باشد محدود میشود. هر پیام تلگرام این علامت را دارد، با برچسب کسی که همان پیام را فرستاده (هیچوقت کسی که آخرین بار سشن را گردانده)؛ فرستندهای که aipager نمیشناسد، مثلاً کسی که وقتی پیامش منتظر بود از
aipager.yamlحذف شده، یک [via Telegram] خالی و کف را میگیرد. - فرمانهای اسلش ادمین لازم دارند. فرمان اسلش خام و بدون علامت در Claude تایپ میشود، و فرمان یا مهارتی مثل
/deliverیک کار کامل را از متن خودش اجرا میکند. پس عضوی که نقشش از قانونهای نقش معاف نیست (userوread_onlyداخلی، نقشهایی که بدونbypass_role_deniesتعریف کردهاید، و هر کسی که aipager نمیشناسد) فقط میتواند فرمانهای کیبورد «فرمانها»ی aipager (بهعلاوهٔ /compact) و تعویض مدل (/model <name>) را بفرستد؛ هر فرمان اسلش دیگری، چه تایپ شود، چه با /<name> به یک سشن فرستاده شود، چه در کپشن یک فایل، چه بهعنوان اولین پیام /newیا بهشکل یک قالب، با «این فرمان فقط برای ادمین است.» و یک 🤷 رد میشود. مالکها و ادمینها هر فرمانی را میفرستند. فرمان اسلشی که از تلگرام به Claude برسد باز هم محدود میشود: هوک میبیند که کار یک پیام تلگرام را برداشته که این فرمان را فرستاده و آن را به قانونهای همان فرستنده محدود میکند. فرمان اسلشی که در ترمینال تایپ میکنید بیمحدودیت میماند، مگر اینکه همان فرمان (با هر آرگومانی) قبلاً در همان سشن از تلگرام فرستاده شده باشد: aipager نمیتواند نسخهٔ شما را از تحویل دوبارهٔ نسخهٔ تلگرام توسط Claude Code تشخیص دهد، پس مال شما با قانونهای آخرین کار تلگرام اجرا میشود. این با بسته شدن سشن تمام میشود. - پیامی که فرستندهٔ تازهای ندارد قانونهای کار را نگه میدارد. Claude Code پیامهایی را هم که هیچ پیام تلگرامی توضیحشان نمیدهد، وقتی یک کار تلگرام در جریان است، گزارش میدهد: پیامی که در ترمینال تایپ شده و پشت کار در صف مانده (همان لحظه که به صف اضافه میشود گزارش میشود)، پیامی از یک سشن Claude محلی دیگر، یا همان پیام تلگرام که بعد از یک کامپکت دوباره تحویل داده میشود. چنین پیامی هیچ فرستندهٔ تلگرامی اضافه نمیکند، پس کار قانونهایی را که داشت نگه میدارد، با هر پیامی که هنوز منتظر برداشته شدن است سختتر و هیچوقت شلتر نمیشود. پیامی که متن تلگرامی دارد که aipager نمیتواند به کسی نسبتش دهد باز هم به کف داخلی میافتد، و پیامی از یک فرستنده با اختیارات کمتر باز هم کار را به قانونهای آن فرستنده پایین میآورد.
چه چیزی واقعاً یک کاربر محدود را مهار میکند. سشن Claude با همان کاربر سیستمعاملی اجرا میشود که صاحب تنظیمات aipager است (توکن ربات در ~/.config/aipager/aipager.yaml، اطلاعات ورود Claude در ~/.config/aipager/daemon.env) و صاحب فایل قانون هر کارش (/tmp/claude-policy-<session>.json). یک shell که با آن کاربر اجرا شود میتواند با شکلهایی که هیچ الگویی پیشبینی نکرده به هر دو برسد (sed -i … /tmp/*-policy-*.json، یک حلقهٔ for روی یک glob، یک یکخطی Python) و قانونهای خودش را به قانونهای مالک تغییر دهد. پس الگوهای دستور یک فیلترند، نه یک مرز. برای کاری که با نقشهای محدود داخلی (user، read_only) اجرا میشود، و برای کاری که aipager نمیتواند به کسی نسبتش دهد، aipager این قانونها را اجرا میکند که هیچکدام به الگوهای دستور وابسته نیستند (برای چیزهایی که پوشش نمیدهند، محدودیتهای شناختهشدهٔ پایین را ببینید):
- بدون shell. نمیتوانند از
Bashیا هر ابزار دیگری که Claude Code با آن کد اجرا میکند استفاده کنند (PowerShell،Monitor،REPL،Workflow، زمانبندهای پیامCronCreateوRemoteTrigger، …؛ فهرستشsafety.CODE_EXECUTION_TOOLSاست). یک پیام زمانبندیشده بدون علامت تلگرام میرسید، یعنی بیمحدودیت، پس اینها هم حساباند. به همین دلیل نمیتوانند ازSendMessageاستفاده کنند (میتواند پیامی را به یک سشن Claude محلی دیگر بدهد که آن را مثل یک پیام ترمینال اجرا کند)، و نه ازEnterWorktree/ExitWorktree(پوشهٔ کاری سشن را جابهجا میکنند و قانون بعدی به آن تکیه دارد). - نوشتن داخل پروژه میماند.
Write،Edit،MultiEditوNotebookEditفقط داخل پوشهٔ سشن (پوشهٔ کاریای که Claude Code در محتوای هوک گزارش میدهد، هیچوقت مسیری از خود فراخوانی ابزار) و scratchpad سشن در Claude Code (/tmp/claude-<uid>/<project>/<session id>/، یا وقتی این پیدا نشود /tmp/claude-<uid>/) مینویسند. هر جای دیگری رد میشود: فایلهای شروع shell شما، ~/.ssh، پروژههای دیگر، فایلهای /tmpخود aipager. داخل پوشه هم .claude/، .git/ و .mcp.jsonرد میشوند، چون هر کدام باعث میشود Claude Code یا git یک دستور اجرا کند. symlinkها قبل از تصمیم دنبال میشوند. سشنی که پوشهاش پوشهٔ خانهٔ شما،/یا پوشهای بالاتر از پوشهٔ خانه است پوشهٔ پروژهای ندارد: آنجا یک کار محدود فقط در scratchpad خودش مینویسد، و نوشتن در هر جای دیگر با «این سشن در پوشهٔ خانه (یا بالاتر از آن) اجرا میشود و این نقش فقط داخل پوشهٔ یک پروژه میتواند بنویسد» رد میشود. برای اینکه اعضای محدود آنجا گیر نکنند، /newو Mini App اجازه نمیدهند در آن پوشهها سشن شروع کنند (پوشهٔ خود aipager، که پیشفرض یک سشن جدید است، زیر سرویس پسزمینه همان پوشهٔ خانهٔ شماست)؛ میتوانند یک پوشهٔ جدید داخل پوشهٔ خانه بسازند و در آن شروع کنند (یک پوشهٔ جدید: نه پوشهای که از قبل آنجا دارید، و نهbinیاnode_modulesکه برنامههای دیگر از آنها کد لود میکنند). - اعضا در پوشهٔ root خودشان میمانند. نقشی که
can_manageندارد (userوread_onlyداخلی) داخل پوشهٔ root چت (پوشههای root) کار میکند که آن را درaipager configتنظیم میکنید (پیشفرض ~/aipager-users/<label>؛ ~/aipager-usersو خود پوشه با حالت دسترسی 0700 ساخته میشوند). /new، ادامه، جایگزینی، ریاستارت، تعویض مود و Mini App سشن چنین عضوی را فقط داخل آن شروع یا دوباره اجرا میکنند. هوک ایمنی این را از هر راهی که پیام برسد اجرا میکند (تلگرام، Mini App، پیام یک سشن دیگر): پوشهٔ root در عکس فوری قانونهای کار، در رسید پیامهای بین سشنها و در اختیارات ثابتشده و زمانبندیشده همراه است، و وقتی اعضایی با پوشههای root مختلف در یک کار شریکاند، باریکترین برنده است (دو پوشه که داخل هم نیستند فقط پوشهٔ خود سشن را باقی میگذارند). نوشتن فقط در پوشهٔ سشن انجام میشود، وقتی آن پوشه داخل پوشهٔ root است (سشنی که بیرون آن است، مثلاً سشنی که قبل از وجود پوشههای root شروع شده، به کارش ادامه میدهد ولی کارهایش فقط در scratchpad مینویسند: «این سشن بیرون از پوشهٔ خود این نقش اجرا میشود»)، وRead،LSP،GrepوGlobبه هیچ جایی در پوشهٔ خانهٔ شما بیرون از پوشهٔ root نمیرسند («این نقش در پوشهٔ خانه فقط داخل پوشهٔ خودش میتواند بخواند»)، بهجز scratchpad سشن و فایل جواب خودش. پوشهٔ root هر چت دیگر، هر جا باشد، برای آن عضو یک مسیر محافظتشده است، و پوشهٔ آپلود هر چت دیگر زیر /tmp/aipager-files/ هم همینطور (Read،Grep،Globو ابزارهای نوشتن؛ برای نقشی که Bash گرفته، دستوری که اسم یکی از این پوشهها را بیاورد رد میشود، در حد تلاش)، همراه با فایلهایی که همهٔ چتها پیش از جدا شدن پوشهٔ هر چت با هم آنجا داشتند. آپلودهایی که در چت خود عضو انجام شده خواندنی میمانند. پوشههای بیرون از پوشهٔ خانه (/usr، /etc، /srv) خواندنی میمانند. نقش سفارشیای کهbypass_role_deniesیاbypass_safetyدارد ولیcan_manageندارد، هر جای داخل پوشهٔ root خودش مینویسد و جستوجو میکند، و خواندنش در پوشهٔ خانهٔ شما هم داخل آن میماند: فقط مالکها و ادمینها بیمحدودیتاند. باbypass_safety، در سشنی که پوشهاش بیرون از پوشهٔ root است، مثل یکuserفقط در scratchpad مینویسد و جستوجو میکند، و وقتی یکی از ابزارهایش رد شد بقیهٔ کار متوقف میشود. - جستوجو داخل پروژه میماند. یک
GrepیاGlobفقط وقتی اجازه دارد که همهٔ اینها برقرار باشد: پوشهای که میگردد (pathآن، یا وقتی ندارد پوشهٔ سشن، با دنبال کردن symlinkها) داخل پوشهٔ سشن (نه وقتی آن پوشهٔ خانه،/یا بالاتر از پوشهٔ خانه است، مثل نوشتن) یا scratchpad باشد؛ آن پوشه یک مسیر محافظتشده نباشد و هیچ مسیر محافظتشدهای را در خود نداشته باشد (سشنی که در پوشهٔ خانه شروع شده ~/.config/aipagerرا در خود دارد، پس نمیتواند از بالایش جستوجو کند)؛ هر glob یک الگوی نسبی ساده باشد: بدون/،~،$یا حرف درایو در اول، بدون..، بدون#(برای ripgrep یعنی توضیح) یا!(یعنی نفی)، بدون escape با\؛ و هیچ قانون مسیر محافظتشدهای بدون/یا~در اول نداشته باشید (**/.envمیتواند هر جای پروژه باشد، پس با بودنش هر جستوجوی محدودی رد میشود). هر چیز دیگری رد میشود. این عمداً یک فهرست مجاز است: سه دور بازبینی هر کدام یک شکل جدید glob پیدا کردند که ripgrep را از پوشهای که مدلی از تجزیهاش انتظار داشت بیرون برد، و یکی از آنها توکن ربات را خواند. - ابزارهای فایل مسیرهای محافظتشده را رعایت میکنند.
Read،LSPو ابزارهای نوشتن چک میکنند یک مسیر واقعاً به کجا میرسد، symlinkها هم. تنها فایل کنترلی که یک کار میتواند بخواند فایل /tmp/claude-reply-<session>.txtسشن خودش است که aipager وقتی به یک پیام قدیمیتر جواب میدهید اسمش را در پیام میآورد؛ فایلهای جواب سشنهای دیگر و هر نوشتنی روی آن رد میمانند. روی macOS، /tmpهمان /private/tmpاست و دیسک به بزرگی و کوچکی حروف اهمیت نمیدهد، پس هر دو شکل و هر ترکیبی از حروف بزرگ و کوچک چک میشوند. - بررسیای که شکست بخورد رد میکند. اگر تصمیم دربارهٔ یک اجرای ابزار با خطا روبهرو شود (یک بایت NUL در مسیر، یک باگ)، یا حافظهٔ هوک وسط تصمیم تمام شود، اجرا رد میشود، مگر اینکه قانونهای سشن خوانده شوند و معافیت مالک را بدهند. قبلاً اجازه داده میشد.
مسیرهای محافظتشدهٔ خود شما. بخش safety: در policy.yaml (deny_paths_no_access، deny_paths_no_write، deny_bash_patterns) به مسیرهای محافظتشده و الگوهای دستور داخلی اضافه میکند. بخشی از کفی است که هر کار هر نقشی جز owner میگیرد، ادمینها هم، و درست مثل فهرست داخلی روی ابزارهای فایل، جستوجوها و دستورهای Bash اعمال میشود. aipager فهرست ترکیبی را در /tmp/claude-policy-.floor-<uid>.json مینویسد (که خودش یک مسیر محافظتشده است) برای حالتهای جایگزین هوک ایمنی؛ اگر آن فایل نباشد، خوانده نشود یا مال کاربر دیگری از سیستمعامل باشد، هوک از فهرست داخلی استفاده میکند، هیچوقت کمتر. تغییری که با خواندن دوبارهٔ تنظیمات انجام شود روی پیامهایی که بعد از آن فرستاده میشوند اعمال میشود، نه روی کاری که از قبل در جریان است یا پیامی که از قبل منتظر است. تا نسخهٔ 0.7.20 این بخش خوانده میشد و aipager doctor --safety-check نشانش میداد، ولی اجرا نمیشد.
کارهایی که aipager نمیتواند به کسی نسبت دهد. کار تلگرامی که فرستندهای ندارد تا به قانونهایش محدود شود با کف اجرا میشود که با نقش user داخلی جور است (نه با محدودیتهای اضافهای که در policy.yaml به user دادهاید) بهعلاوهٔ بخش safety: شما: بدون ابزارهای اجرای کد، نوشتن و جستوجوی محدود، مسیرهای محافظتشده. این برای پیامی اتفاق میافتد که یادداشت اجازهاش منقضی شده (بعد از ۲۴ ساعت) یا نوشته نشده، و برای پیامی که متن تلگرامی دارد که هیچ یادداشتی با آن جور نمیشود. دکمهٔ «دوباره امتحان کن» با نقش کسی اجرا میشود که آن را زده، وقتی همان شخص پیامی را که دوباره فرستاده میشود هم فرستاده بوده، پس «دوباره امتحان کن» یک مالک روی پیام خودش Bash را نگه میدارد؛ «دوباره امتحان کن» روی پیام کس دیگر، یا پیامی که aipager فرستندهاش را نمیشناسد، با کف اجرا میشود، تا متن هیچکس اختیارات کسی را که دکمه را زده قرض نگیرد.
خواندن بیرون از مسیرهای محافظتشده برای نقشی که به پوشهٔ root محدود نیست (یک نقش سفارشی با can_manage ولی بدون معافیت) مجاز است: میتواند پروژههای دیگر همان کاربر سیستمعامل را بخواند. user و read_only داخلی هیچ چیزی در پوشهٔ خانهٔ شما بیرون از پوشهٔ root خودشان نمیخوانند (بالا). فایلهای اطلاعات ورود شما برای هر نقشی که bypass_role_denies ندارد (user و read_only داخلی، نقشهایی که تعریف میکنید، و کف) مسیر محافظتشدهاند: ~/.ssh، ~/.gnupg، ~/.aws، ~/.config/gh، ~/.git-credentials، ~/.netrc، ~/.docker/config.json، ~/.kube، ~/.config/gcloud، ~/.azure، ~/.password-store، ~/.pgpass، ~/.npmrc و ~/.pypirc را نمیشود خواند، نوشت یا گشت. اینها پیشفرض نقشاند، پس deny_paths_no_access خود یک نقش در policy.yaml برای آن نقش جایشان را میگیرد، جز وقتی که یک پیام دیگر یا یک پیام تایپشده در ترمینال به یک کار در جریان اضافه شود: از آن لحظه تا تمام شدن کار، دوباره به آنها محدود میشود (سختترین قانون برنده است). یک فایل اطلاعات ورود در جای دیگر (یک .env در یک پروژه، یک توکن در پوشهٔ دیگر) پوشش داده نمیشود: آن را به deny_paths_no_access نقش اضافه کنید. مالکها به هیچکدام از اینها محدود نیستند. نوشتن ادمینها محدود نیست. Grep/Glob آنها میتواند هر پوشهای را که به یک مسیر محافظتشده نمیرسد بگردد: جستوجو وقتی رد میشود که پوشهای که میگردد (path آن، یا وقتی ندارد پوشهٔ سشن؛ برای یک Glob، پوشهای هم که الگویش نام میبرد، مثل /home/you/.config/aipager/*، یا پوشهٔ بالای الگویی بدون wildcard که Claude Code آن اسم را در هر عمقی در آن میگردد)، با باز شدن symlinkها، یک پوشهٔ محافظتشده باشد، داخل یکی باشد یا یکی را در خود داشته باشد. پس Grep روی ~، /، پوشهٔ بالای پوشهٔ خانه یا /tmp رد میشود (~/.config/aipager، ~/.claude یا فایلهای /tmp/claude-* خود aipager را در خود دارند)، ولی Grep روی یک پروژه یا /tmp/myproj نه. یک قانون مسیر محافظتشده بدون / یا ~ در اول (مثل **/.env) میتواند در هر پوشهای جور شود، پس با بودنش در قانونهای یک ادمین (یا در بخش safety:، که هر نقشی جز مالک میگیرد) هر جستوجوی ادمین رد میشود، مثل نقشهای محدود؛ aipager doctor دربارهٔ چنین قانونی در بخش safety: هشدار میدهد. ادمینها Bash دارند، پس قانونهایشان در حد تلاش میماند (پایین).
محدودیتهای شناختهشده. اینها پوشش داده نمیشوند:
- اگر خود هوک قبل از شروع تصمیمگیری شکست بخورد (مثلاً نتواند فایل وضعیت خودش را بخواند، یا موقع شروع حافظهاش تمام شود)، Claude Code این را «اجازه» حساب میکند. یک کاربر محدود نمیتواند باعث این شود؛ هر خطایی وسط تصمیمگیری رد میکند.
- ابزارهایی که ابزار فایل نیستند ولی میتوانند داده بیرون بفرستند، از نظر مسیر چک نمیشوند:
WebFetch(هر چیزی که کار بتواند بخواند میتواند به یک آدرس بفرستد) و بقیهٔ ابزارهای ارسال Claude Code. اگر برایتان مهم است، آنها را برای یک نقش درpolicy.yamlرد کنید.SendUserFile، که فایل به چت میفرستد، چک میشود (فایلهایی که Claude میفرستد را ببینید). - یک ایجنت پسزمینه که یک کار محدود شروعش کرده بعد از تمام شدن آن کار به کارش ادامه میدهد، و اجرای ابزارهای بعدیاش با جدیدترین پیام سشن قضاوت میشود: یک پیام بعدی از مالک یا ترمینال محدودیتهایش را برمیدارد.
- نقشی که با
can_manageولی بدون هیچکدام از معافیتها تعریف کنید به پوشهٔ root محدود نیست: باز هم میتواند در هر پوشهٔ پروژهای که چتش از قبل در آن کار میکند (پوشهای که یک سشن قبلی استفاده کرده) شروع کند، از جمله یکی از پوشههای شما، و پروژههای دیگرتان را بخواند. - کاری که هیچ عضوی نمیشود به آن محدودش کرد (فرستندهٔ ناشناخته، عضو حذفشده، پیامی از یک سشن دیگر که فرستندهاش تأیید نمیشود) با کف محدود اجرا میشود که پوشهٔ root ندارد. در خواندن، این کف از یک
userداخلی ضعیفتر است: چنین کاری میتواند هر فایلی را در پوشهٔ خانهٔ شما که مسیر محافظتشده نیست بخواند (مثلاً پروژههای دیگرتان)، در حالی که کار یکuserفقط داخل پوشهٔ root خودش میخواند. چنین کاری آپلودهای همهٔ چتها را هم در /tmp/aipager-files/ میتواند بخواند، در حالی که کار یکuserفقط آپلودهای چت خودش را میخواند. نوشتنش به پوشهٔ سشن و scratchpad محدود میماند. پیام تأییدشده از سشن یک عضو پوشهٔ root آن عضو را همراه دارد. - پوشهٔ root فقط ابزارهای فایل را محدود میکند. نقش سفارشیای که به آن
Bashبدهید میتواند از طریق shell بیرون از آن را بخواند (userوread_onlyداخلیBashندارند). نقش سفارشیای کهbypass_safetyدارد ولیcan_manageندارد، معافیتش را برای همهچیز جز مسیرهای ابزارهای فایل نگه میدارد: هیچ ابزار یا الگوی دستوری برایش رد نمیشود. - دلیل ردی که پوشهٔ root عضو دیگری را نام ببرد، مسیر آن پوشه را به Claude و در چت نشان میدهد.
- کار عضوی که وقتی پوشههای root اضافه میشوند (یک آپدیت) از قبل در جریان است، تا پیام بعدی آن عضو پوشهٔ root ندارد.
- داخل پروژه، یک کاربر محدود میتواند چیزی را که شما بعداً خودتان اجرا میکنید یا میخوانید عوض کند (سورس،
Makefile، اسکریپتهایpackage.json،conftest.py، یکCLAUDE.mdکه کارهای خود شما لود میکنند).
نقشی که Bash دارد (admin که از قانونهای رد نقش معاف است، یا هر نقشی که policy.yaml دوباره Bash به آن داده: allow_tools که Bash را نام ببرد، یا deny_tools عوضشده) فقط به قانونهایی در حد تلاش محدود است: الگوهای دستور پایین، که یک کاربر مصمم میتواند دورشان بزند. aipager doctor برای هر چنین نقشی که عضوی دارد یک بار هشدار میدهد. Bash را فقط به کسی بدهید که حاضرید یک shell روی کامپیوترتان به او بدهید.
قانونهای دستور داخلی. بین الگوهای Bash که هر کار تلگرامی غیرمالک با Bash به آنها محدود است، یکی کلمهٔ claude را میبندد: یک claude تودرتو (با هر مسیر، wrapper یا فلگی) و هر دسترسی به ~/.claude را با یک قانون متوقف میکند، و اسم سادهٔ فایلهای کنترلی /tmp/claude-* خود aipager را هم میگیرد، چون اسمشان همین پیشوند را دارد (یک الگو برای آن فایلها محافظت واقعی نیست: یک شکل glob از آن رد میشود). تنها استثنا پوشهٔ scratchpad است که Claude Code میسازد، /tmp/claude-<uid>/…: همان پوشهٔ دقیق، بهعنوان یک جزء کامل از مسیر، معاف است تا دستورهایی که از scratchpad یک سشن اجرا میشوند متوقف نشوند. این پوشه مال کاربر سیستمعامل است، نه یک سشن، پس کاری که Bash دارد میتواند scratchpad سشنهای دیگر و خروجی کارهایشان را آنجا بخواند، همانطور که ابزار Read میتواند. یک glob یا شکل شبیه به آن (/tmp/claude-1*، claude-1000x)، یک پوشهٔ موقت دیگر، و هر چیزی که از آن به آن برسد (…/../../home/you/.claude) باز هم بستهاند. اشارههای دیگر به این کلمه (git log --grep=claude، فایلی به اسم claude-notes.md، pip show claude-agent-sdk) هم عمداً بستهاند: همین کلمه اسم فایل اجرایی، SDKهایش و فایلهای کنترلی aipager است و یک الگو نمیتواند آنها را از هم جدا کند. قانونهایی که در policy.yaml اضافه میکنید دستور را دقیقاً همانطور که تایپ شده میبینند، scratchpad هم.
جستوجو از طریق Bash. بعضی نسخههای Claude Code ابزار Grep یا Glob ندارند (2.1.289 یکی از آنهاست) و Claude آنوقت با grep، rg یا find از طریق Bash میگردد، جایی که قانون Grep/Glob بالا اعمال نمیشود. برای هر نقشی جز مالک، الگوهای دستور جستوجو یا فهرست گرفتن بازگشتی از کل پوشهٔ خانه یا / را هم رد میکنند: grep -r (-R، --recursive، -d recurse، egrep، fgrep، rgrep)، rg (و rg --files)، ag، ack (و ack -f)، find، fd، tree، du و ls -R که پوشهشان ~، ~user، $HOME، ${HOME} یا "$HOME"، /home، /home/<user> (یا /home/$USER)، /Users، /Users/<user>، /root یا / (با یا بدون /، /. یا /* در آخر)، یا ~/.config یا ~/.local باشد که پوشههای aipager را در خود دارند. این بعد از cd x &&، در یک pipe، داخل bash -c "..."، بعد از xargs، env یا timeout و بعد از تنظیمهایی مثل LC_ALL=C هم برقرار است. جواب میگوید به جایش داخل پوشهٔ یک پروژه بگردید. جستوجو در پوشهٔ یک پروژه (grep -rn x ~/proj، rg x src/، find . -name y)، جستوجو داخل یک پروژه برای یک مسیر خانه (grep -rn "$HOME" src؛ اولین کلمه بعد از گزینهها متنی است که دنبالش میگردد، نه پوشه) و جستوجوهای غیربازگشتی (grep x ~/.bashrc، ls ~) تحت تأثیر نیستند. این، مثل هر الگوی دستوری، در حد تلاش است: یک الگو کلمهها را میبیند، نه جایی که shell اجرایشان میکند، پس نمیتواند جستوجویی را ببیند که بدون پوشه، یا با . یا ..، از پوشهٔ خانه (یا زیر آن) اجرا شود (cd ~ && grep -r x .)، پوشهٔ خانهای که از راه یک متغیر دیگر یا یک glob به آن برسند، یا پوشهٔ خانهای که کامل بیرون از /home، /Users و /root نوشته شود، و نه \grep را، و نه پوشهای را که درست بعد از کلمهای که به -e یا -f تمام میشود بیاید (آن را متنی حساب میکند که -e/-f به grep و rg میدهد، پس ag x -f ~ رد میشود). همینطور نمیداند کدام گزینهها مقدار جدا میگیرند، پس rg -t py "$HOME" . یا grep -rn -A 3 "/home/you" src را طوری میخواند که py یا 3 متن است و مسیر خانه پوشه، و رد میکند (-A3، --include=*.py بنویسید، یا گزینه را بعد از متن بگذارید)؛ مسیر خانهای که در یک خوشه به -e داده شود هم همینطور (grep -rne "/home" src؛ -r -n -e مشکلی ندارد)؛ دستوری که داخل متن دستور دیگری نقل شده (echo "a; rg x ~") هم رد میشود، و du -sh ~ هم (فقط اندازهها).
چرا یک ممانعت کل کار را متوقف میکند. بعد از اینکه جلوی یک اجرای ابزار گرفته شد، هر اجرای بعدی در همان کار هم تا پیام بعدی رد میشود. بدون این، ایجنت همان کار را با شکل دیگری دوباره امتحان میکند (یک glob، یک دستور دیگر، یک مسیر کدشده) تا یکی از الگوها رد شود. متوقف کردن کار اولین تلاش را پرهزینه میکند؛ یک الگو را مرز نمیکند، چون ممکن است اولین شکلی که امتحان میشود همانی باشد که رد میشود. هزینهٔ یک ممانعت اشتباه یک کار متوقفشده است، و برای همین قانونهای بالا باریک نگه داشته شدهاند.
برای قانونهایی که این یادداشتها با خود میبرند و شما میبینید، استفادهٔ تیمی ← قانونها چطور کار میکنند را ببینید.
فایلهایی که Claude میفرستد#
هر سشنی که aipager باز میکند ابزار SendUserFile در Claude Code را دارد (CLAUDE_CODE_ATTACHMENTS_BY_PATH=1)، که فایلها را میخواند و به چت سشن میفرستد (فرمانها). هوک PreToolUse هر فایلِ فراخوانی را قبل از اینکه Claude Code آن را بخواند چک میکند:
- کف ارسال، برای هر کار، کار owner و پیامی که در ترمینال تایپ شده هم: تنظیمات و وضعیت aipager (
~/.config/aipager/**، ~/.local/share/aipager/**، ~/.local/state/aipager/** و فایلهای کنترلش در /tmp)، همهٔ ~/.claude/** و ~/.claude.json* (اطلاعات ورود و حساب Claude)، فایلهای اطلاعات ورودی که هیچ نقش محدودی به آنها دسترسی ندارد (~/.ssh/**، ~/.gnupg/**، ~/.aws/**، ~/.git-credentials، ~/.netrcو بقیهٔ همان فهرست)، /proc/**، /sys/**، /dev/**، و اسمهایی که هر جا باشند شبیه اطلاعات محرمانهاند (.env، .env.*، *.pem، *.key،id_rsa*،id_ed25519* و بقیهٔ اسمهای کلید، *.p12، *.pfx، *.kdbx، *.jks، *.keystore، .pgpass،credentials، *credentials*.json). مسیر همانطور که نوشته شده و جایی که واقعاً به آن میرسد چک میشود، پس سیملینکی که به یکی از اینها برسد هم رد میشود. بعضی فایلهای بیخطر هم همین اسمها را دارند (فایل *.keyبرنامهٔ Keynote، یک .env.example) و آنها هم رد میشوند. - خود فایل: بعد از دنبال کردن سیملینکها یک فایل معمولی باشد، فقط یک هارد لینک داشته باشد، خالی نباشد، و حداکثر ۵۰ مگابایت باشد.
- قانونهای خود کار: چیزهایی که کار اجازهٔ خواندنشان (Read) را ندارد، و برای یک عضو (نقشی بدون
can_manageکه به یک پوشهٔ root محدود است) قانون سختگیرانهتری: هر فایل، هم آنطور که نوشته شده و هم جایی که واقعاً به آن میرسد، باید داخل پوشهٔ root باشد. برخلاف خواندنهای یک عضو، برای فایلهای بیرون از پوشهٔ خانه (/tmp، /etc) و برای scratchpad سشن استثنایی نیست.
رد کردن یک رد نرم است: Claude دلیل را میخواند («SendUserFile refused, nothing was sent: ... Tell the person which file could not be sent and why.») و کار ادامه پیدا میکند؛ سشن مثل وقتی که یک ابزار مسدود میشود متوقف نمیشود. اگر خود چک خطا بدهد، فایل رد میشود، برای owner هم. هر فایل ردشده یک رکورد FileRefused در لاگ ممیزی دارد.
aipager دربارهٔ هیچ فایلی حرف سوکت کنترل را چشمبسته قبول نمیکند: هر پردازهٔ محلی میتواند در آن بنویسد و اسم هر سشن، هر فراخوانی و هر گفتگوی ذخیرهشدهای را بیاورد، و حتی hook یک فراخوانی ابزار بهتنهایی سشن بیکار را در حال کار نشان میدهد. برای همین یک فراخوانی فقط وقتی حساب میشود که در کاری باشد که aipager شروعش را با یک پرامپت دیده (پرامپتی که خودش از Telegram تایپ کرده، یا یک UserPromptSubmit) یا در کار پسزمینهٔ همان کار، تا وقتی سشن بیکار شود و هیچ کاری در حال اجرا نباشد؛ aipager در همان شروع، ساعت و گفتگوی ذخیرهشدهای را که برای سشن دنبال میکند یادداشت میکند (یک UserPromptSubmit بعدیِ همان کار دوباره اسم گفتگو را میآورد). دستور اسلشی که aipager تایپ میکند هیچ کاری را شروع نمیکند (دستوری که یک کار را اجرا کند، UserPromptSubmit خودش را میفرستد)، و کاری که هیچوقت شروع نشود (سشن ۱۵ ثانیه بعد هنوز بیکار و بدون کار باشد) دوباره بسته میشود. Claude Code بعضی کارها را بدون UserPromptSubmit شروع میکند: پیامی که وقتی یک کار در جریان است فرستاده شده و آن را کار بعدیاش میکند، و کاری که یک hook از نوع Stop ادامهاش میدهد (/goal). چنین کاری وقتی حساب میشود که خود aipager آن پیام را تایپ کرده باشد، یا به محض اینکه گفتگوی ذخیرهشدهای که یادداشت کرده شروع آن را بعد از آخرین Stop نشان بدهد (یک خط صف dequeue، یا خط Stop hook feedback: که Claude Code مینویسد)؛ تا آن موقع هیچ چیزی از فراخوانی خوانده نمیشود، حداکثر تا ۲۰ ثانیه. فهرست فایلها را از PreToolUseای برمیدارد که خودش چک کرده، یا وقتی آن از دست رفته (بعد از ریاستارت) از PostToolUse؛ هیچوقت از نتیجهٔ Claude Code. هر فراخوانی را فقط یک بار کپی میکند: id فراخوانیای که یک بار کپی شده دیگر هیچوقت کپی یا فرستاده نمیشود (۲۵۶ تای آخر هر سشن را، بعد از ریاستارت هم، نگه میدارد). مسیر واقعی هر فایل را فقط یک بار پیدا میکند و فایل را روی همان مسیر دوباره چک میکند (کف، چکهای فایل و پوشهٔ root سشن)، دقیقاً همان مسیر را بدون دنبال کردن لینک باز میکند، مطمئن میشود فایل بازشده همانی است که چک کرده و هنوز در همان مسیر است، و آن را در ~/.local/state/aipager/outbox/<chat>/ کپی میکند (پوشه 0700، فایل 0600)، جایی که هیچ کار محدودی از قبل به آن دسترسی ندارد. قبل از ارسال، فراخوانی را در همان گفتگوی ذخیرهشدهای پیدا میکند که برای آن کار یادداشت کرده (برای فراخوانی یک سابایجنت، در گفتگوی ذخیرهشدهٔ خود آن سابایجنت که کنارش است)، و هیچوقت در گفتگویی که دیتاگرام یک فراخوانی ابزار اسمش را بیاورد: یک خط دستیار با یک tool_use از SendUserFile با همان id و دقیقاً همان فایلها، که از همان پوشه و نه زودتر از شروع کار نوشته شده، در یک فایل .jsonl زیر ~/.claude/projects (یا $CLAUDE_CONFIG_DIR/projects، همانطور که برای خود aipager تنظیم شده) که مال کاربر سیستمعامل خود aipager است. Claude Code گفتگوی ذخیرهشده را دیر مینویسد، برای همین aipager تا ۲۰ ثانیه منتظر فراخوانی میماند، و تا وقتی کار سشن هنوز در جریان است (یک ایجنت پسزمینه، یک فراخوانی ابزار) فایلها را نگه میدارد و بعداً دوباره نگاه میکند. فراخوانیای که وقتی دیگر هیچ کاری در جریان نیست هم پیدا نشود فرستاده نمیشود و کپیهایش پاک میشوند؛ فراخوانیای که وقتی چنین کاری در جریان نبوده رسیده، یا بار دوم رسیده، اصلاً کپی نمیشود. هر فایل اینها در لاگ ممیزی یک رکورد FileFailed با دلیل unverified میگیرد. چت فقط وقتی برایشان یک خط میگیرد (⚠️ report.pdf ارسال نشد: aipager نتوانست مطمئن شود که Claude آن را در همین کار فرستاده است.) که گفتگوی ذخیرهشدهٔ خود Claude همان فراخوانی را در طول کار نشان بدهد، و آن خط اسم فایلهایی را میآورد که همان خط گفتگو فهرست کرده، پس یک فراخوانی جعلی نه خطی به چت میفرستد نه اسمی؛ در غیر این صورت چیزی به چت نمیرود. فقط کپی فرستاده میشود، فقط به چت خود سشن، بعد از جواب کار، بعد از فایلهای قبلی همان سشن و بدون اینکه کار بعدی را معطل کند؛ کپی بعد از ارسال، یا بعد از یک روز، پاک میشود.
چیزهایی که این چکها جلویشان را نمیگیرند: پردازهای که با کاربر سیستمعامل خود aipager اجرا میشود، همانطور که در سوکت مینویسد میتواند گفتگوهای ذخیرهشدهٔ Claude را هم بنویسد (آن فایلها را از قبل خودش میتواند بخواند). پردازهٔ یک کاربر دیگر که به سوکت دسترسی دارد میتواند یک UserPromptSubmit هم جعل کند، که از نظر این چکها یک کار را شروع میکند و اسم گفتگوی ذخیرهشدهٔ آن را میآورد؛ بعد میتواند کاری کند که aipager فایلی را به چت آن سشن بفرستد که یک فراخوانی واقعی SendUserFile اسمش را آورده، به شرطی که آن فراخوانی بعد از آن لحظه از همان پوشه در همان گفتگو نوشته شود (مثلاً فراخوانیای که درست همان موقع یک سشن دیگر انجام میدهد)، برای هر فراخوانی یک بار. برای این کار باید id خود همان فراخوانیِ در جریان (tool_use_id) را هم بفرستد، که کاربر دیگر نمیتواند بخواندش: در گفتگوهای ذخیرهشده زیر ~/.claude کاربر aipager است، و hook آن را از ورودیاش میگیرد، نه از خط فرمانش. یک Stop جعلی از نظر این چکها کار را تمام میکند، پس فایلهایی که آن کار بعد از آن بفرستد فرستاده نمیشوند. سشنی که با $CLAUDE_CONFIG_DIRای غیر از مال aipager شروع شده، یا کاری که aipager UserPromptSubmit آن را از دست داده (در ترمینال تایپ شده وقتی aipager داشت ریاستارت میشد)، هیچ فایلی نمیفرستد. کاری هم که Claude Code به یک روش دیگر بدون hook شروع کند (روشهای شناختهشده همین دو تای بالا هستند) فایلی نمیفرستد؛ فایلی که گفتگوی ذخیرهشدهٔ Claude نشان بدهد آنجا خواسته شده، همان خط بالا را میگیرد. اگر $XDG_RUNTIME_DIR تنظیم شده باشد، سوکت آنجاست و کاربرهای دیگر به آن دسترسی ندارند؛ اگر نه، در /tmp است.
لاگ ممیزی#
هر زدن «اجازه بده» یا «رد کن»، بهعلاوهٔ هر جواب AskUserQuestion، یک خط JSON به ~/.claude/aipager-audit.jsonl اضافه میکند:
{
"ts": "2026-05-18T15:42:11+00:00",
"session": "claude-jim",
"label": "jim",
"action": "Allowed",
"tool": "Bash",
"summary": "ls -la /tmp",
"user_id": 12345,
"username": "alice",
"denied": false
}
فیلدها (برای فهرست کامل aipager/audit.py را ببینید):
-
ts: زمان به ISO 8601 و UTC، با دقت ثانیه. -
session/label: اسم داخلی و اسم آشنا. -
action: چه اتفاقی افتاد (Allowed،Denied،Allowed always، یک جوابAskUserQuestion، یک رد خودکار، …). -
tool/summary: اسم ابزار و ورودی کوتاهشده یا متن سؤال. -
user_id/username: چه کسی دکمه را زد، برای اینکه در استفادهٔ تیمی معلوم باشد چه کسی بود؛ و جایی که چند چت تنظیم شده،scope_label/scope_chat_id. -
denied: برای هر ردی true است، چه با دکمه چه با قانون.
نوشتن در حد تلاش است. اگر دیسک پر شود یا ~/.claude/ قابل نوشتن نباشد، aipager یک WARNING در لاگ مینویسد و به کارش ادامه میدهد؛ نه از دست رفتن بیصدا، نه کرش. aipager/audit.py را ببینید.
لاگ ممیزی روی دیسک فقط اضافه میشود. آن را کنار جواب ممیزی داخل چت (یک پیام تلگرام برای هر تصمیم، زیر کارت در حال کار) بگذارید تا دو سابقهٔ مستقل داشته باشید.
مرز دسترسی#
aipager هیچوقت دسترسی بالاتر نمیگیرد. نه sudo، نه setuid، نه doas. هر فایلی که مینویسد زیر پوشهٔ خانهٔ شما، زیر /tmp (فایلهای کنترلی هر سشن، دانلودها و لاگ ریاستارت) یا در پوشهٔ سوکت کنترلش ($XDG_RUNTIME_DIR) است؛ فهرست کامل در معماری است. هر زیرپروسه (claude، dtach، نصبکنندهها، claude update) با کاربر aipager اجرا میشود.
نصب قابلیت ویس از تلگرام ([📦 نصب قابلیت ویس]) از طریق نصبکنندهای که صاحب aipager است انجام میشود (uv tool install --reinstall aipager[voice]، pipx install --force aipager[voice])، و در غیر این صورت پکیجهای آن را با Python خود aipager نصب میکند (<python> -m pip install): همیشه در محیط خود aipager، هیچوقت در Python سیستم.
دکمهٔ [🔄 ریاستارت aipager]، زیر سرویس systemd، systemctl --user restart aipager.service را در یک سرویس موقت کاربری جدا زمانبندی میکند؛ روی macOS launchctl kickstart -k را اجرا میکند؛ aipagerی که خودتان اجرا کردهاید یک کپی جداشده از خودش را شروع میکند که منتظر میماند قبلی بسته شود. بعد از آپدیت، فقط سرویس systemd خودش ریاستارت میشود (فرمانها ← آپدیت را ببینید). همه با همان کاربر اجرا میشوند؛ هیچ دسترسی بالاتری گرفته نمیشود.
ارتباطات شبکه#
aipager روی هیچ پورت TCP غیر loopback گوش نمیدهد. سرور Mini App فقط روی 127.0.0.1 باز میشود (در کد ثابت است؛ عمداً هیچ گزینهای برای host نیست) و از بیرون فقط از طریق تونل مدیریتشدهای که پایین آمده میشود به آن رسید. ارتباطهای خروجی:
- long-poll با HTTPS به
api.telegram.org(گرفتن آپدیتهای ربات). - HTTPS به
telegram.orgبرای اسکریپت SDK مربوط به Mini App (telegram-web-app.js) که وقتی سرور Mini App شروع میشود یک بار دانلود و حداکثر روزی یک بار آپدیت میشود؛ زیر ~/.local/share/aipager/webapp-sdk/ نگه داشته میشود. aipager همان بایتها را از آدرس خودش به صفحه میدهد و هیچوقت اجرایشان نمیکند. aipager اسکریپت را دوباره منتشر نمیکند: چیزی در پکیج نیست، و وقتی aipager نسخهای ندارد، صفحه آن را مثل قبل مستقیم ازtelegram.orgلود میکند. - تونل Mini App به Cloudflare، تا وقتی روشن است (پیشفرض). فایل اجرایی
cloudflaredکه اجرا میکند یک بار از releaseهایgithub.com/cloudflare/cloudflaredدانلود میشود، با یک SHA-256 ثابت چک میشود و زیر ~/.local/share/aipager/cloudflared/ نگه داشته میشود؛cloudflaredی که درPATHشماست هیچوقت استفاده نمیشود. با آدرس خود شما برای Mini App هیچکدام از اینها اتفاق نمیافتد (پایین را ببینید). - چک کردن نسخهها، فقط وقتی کسی چک کردن آپدیت یا آپدیت را بزند (
/update، بخش آپدیتهای Mini App)، یا وقتی کسی که اجازهٔ آپدیت دارد تب تنظیمات Mini App را باز کند (برای هر اپ باز، حداکثر هر ده دقیقه یک بار):pypi.orgبرای aipager، وdownloads.claude.aiیاregistry.npmjs.orgبرای Claude Code، بسته به اینکه چطور نصب شده. بعد آپدیت نصبکننده را اجرا میکند (که با PyPI یا Homebrew حرف میزند) وclaude updateرا.aipager updateدر خط فرمان فقط نصبکننده را اجرا میکند. - نصب قابلیت ویس، وقتی دکمهاش را میزنید: نصبکننده پکیجها را از PyPI میگیرد و مدل تبدیل صدا به متن در اولین استفاده از Hugging Face (
huggingface.co) دانلود میشود. - HTTPS به
raw.githubusercontent.com(فایل کلید گزارش) و به سرور دریافت Sentry، فقط وقتی ارسال یک گزارش مشکل را تأیید میکنید: گزارشی که پیشنمایشش را دیدید و یک خلاصهٔ کوتاه از آن، و هیچ چیز دیگر. گزارش مشکل را ببینید.
ارتباط ورودی:
- سوکت datagram یونیکس در
$XDG_RUNTIME_DIR/aipager.sock(وقتی $XDG_RUNTIME_DIRتنظیم نشده، /tmp/aipager.sock). aipager در شروع آن را باز و دسترسیاش را تنظیم میکند (aipager/dtach/hook_receiver.py). حالت دسترسی 0o666است تا هر پروسهٔ محلی بتواند رویدادهای هوک را به آن بفرستد؛ همان اعتمادی که ~/.claude/settings.jsonدارد، که از قبل تعیین میکند چه چیزی هوکها را اجرا کند.
یعنی: aipager از راه دور قابل حمله نیست. یک مهاجم شبکه بدون جای پایی روی خود کامپیوتر نمیتواند به آن برسد.
متن Claude و دانلودهای خود تلگرام. سرورهای تلگرام بعضی از آدرسهای داخل یک پیام را خودشان دانلود میکنند: یک بلوک عکس، ویدیو یا صدا در یک پیام غنی (rich message)، و صفحهای که پشت یک پیشنمایش لینک است. جوابهای Claude، یادداشتهایش روی کارت در حال کار، توضیح ایجنتها و آرگومانهای ابزارها همانطور که Claude نوشته به تلگرام میرسند، پس یک حملهٔ تزریق پرامپت (prompt injection) میتواند Claude را وادار کند آدرسی بنویسد که یک راز را بیرون میبرد، بدون هیچ اجرای ابزار و بدون هیچ درخواست اجازهای. aipager راه اول را میبندد، و راه دوم را هر جا که تلگرام گزینهای برایش دارد:
- در هر پیام غنی، عکسی که بهشکل
 نوشته شده بهشکل یک لینک ساده همراه با متن جایگزینش (alt) فرستاده میشود، و هر شکل دیگری از نوشتن عکس، و تگهای خام <img>، <video>، <audio>، <source>، <picture>، <figure>، <iframe>، <embed>، <object>، <tg-document>، <tg-collage>، <tg-slideshow>، <tg-button> و <tg-button-row>، بهشکل متن نشان داده میشوند (aipager/bot/media_guard.py). داخل یک بلوک کد، مثلاً یک تکه HTML، این کار یک\یا < اضافه نشان میدهد. - پیشنمایش لینک در هر پیام متن ساده و HTML که aipager میفرستد یا ویرایش میکند خاموش است (نسخههای متن سادهٔ جایگزین جوابها و کارتها هم)، پیامهای خود aipager هم. لینکهای Mini App آن دکمهاند، و تلگرام چیزی از دکمهها دانلود نمیکند.
تلگرام برای پیامهای غنی هیچ گزینهٔ پیشنمایشی را مستند نکرده؛ اینکه برای چنین پیامی پیشنمایش لینک میسازد یا نه، روی سرویس واقعی چک نشده است.
تونل Mini App#
Mini App بهطور پیشفرض روشن است، پس در یک نصب معمولی aipager بدون اینکه کسی بخواهد این تونل را باز میکند. این یک انتخاب آگاهانه است (قابلیت اختیاریای که هیچکس پیدایش نکند انگار وجود ندارد)، ولی یعنی یک نصب جدید به اینترنت وصل میشود. با aipager miniapp disable خاموشش کنید، یا زیر miniapp: در aipager.yaml مقدار enabled: false را بگذارید؛ یک false صریح همیشه رعایت میشود. توجه کنید که بخش miniapp: نبوده یا خراب حالا به روشن برمیگردد، نه خاموش.
Mini App (مگر اینکه با --url آدرس دیگری داده شده باشد) یک تونل سریع Cloudflare مدیریتشده راه میاندازد که به سرور loopback خود aipager اشاره میکند، و هر آدرس عمومی https://*.trycloudflare.com که به آن داده شود را بهعنوان دکمهٔ منوی تلگرام و جواب /app منتشر میکند.
آن آدرس راز نیست و قرار هم نیست باشد: هر درخواستی که Mini App جواب میدهد با امضای initData تلگرام چک میشود (مرز اعتماد را در بالا ببینید)، همان بررسیای که هر جور آدرس به دست آمده باشد از آن محافظت میکند. دانستن اسم host بهتنهایی به مهاجم چیزی نمیدهد که با حدس زدن تصادفی یک زیردامنهٔ trycloudflare.com به دست نمیآورد؛ دروازهٔ واقعی همان بررسی امضاست. با آن مثل هر آدرس دیگری که فهرست نشده ولی راز هم نیست رفتار کنید: میشود روشن بماند، ولی بیدلیل در یک کانال عمومی پیستش نکنید.
اسم host فقط در حافظه نگه داشته میشود؛ هیچوقت در aipager.yaml یا هیچ جای زیر ~/.config/aipager/ نوشته نمیشود. یک ریاستارت برنامهریزیشده (ریاستارت بعد از آپدیت، همین حالا ریاستارت کن، ریاستارت درجا بدون سرویس) تونل را روشن نگه میدارد، پس آدرس، دکمهٔ منو و Mini App باز همچنان کار میکنند: aipager بعدی فقط وقتی تونل را تحویل میگیرد که مطمئن شود همان بوت سیستم است، همان پورت است، پروسه همان cloudflared است (از روی خط فرمانش)، هنوز همان آدرس را گزارش میکند (~/.claude/aipager-tunnel.json یک هش از آن نگه میدارد، هیچوقت خود آدرس را) و آدرس جواب میدهد. در هر حالت دیگری تونل را میبندد و یک تونل جدید راه میاندازد، و تونل نگهداشتهشدهای را که هیچ aipagerی تا ۳ دقیقه تحویل نگیرد یک پروسهٔ نگهبان کوچک میبندد. هر توقف دیگری (Ctrl+C، docker stop، systemctl stop) تونل را هم میبندد، و ریاستارت کامپیوتر یا کانتینر آدرس جدید میدهد. MINIAPP_KEEP_TUNNEL=0 نگهداشتن تونل را خاموش میکند. اگر aipager یکباره کشته شود (کمبود حافظه، kill -9)، تونل روشن میماند تا وقتی aipager دوباره با تونل مدیریتشده شروع شود (Mini App روشن، بدون MINIAPP_PUBLIC_URL) و آن را تحویل بگیرد یا ببندد، یا تا وقتی کامپیوتر ریاستارت شود؛ aipager uninstall هم آن را میبندد. اگر تونل وسط کار از بین برود، aipager با فاصلههای بیشترشونده دوباره راهش میاندازد و آدرس جدید را منتشر میکند؛ اگر بعد از چند بار برنگردد، دکمه برداشته میشود تا به یک پورت مرده اشاره نکند.
تنظیم MINIAPP_PUBLIC_URL (یا aipager miniapp enable --url https://…) تونل مدیریتشده را کاملاً خاموش میکند (هیچ فایل cloudflaredی دانلود یا اجرا نمیشود) و آدرس دادهشده همانطور که هست، با همان بررسی initData زیرش، استفاده میشود. پیدا کردن خودکار Tailscale (tailscale status --json) هم برای کسانی که از قبل Tailscale دارند و نمیخواهند اصلاً به Cloudflare وابسته باشند، بهعنوان راهی کمزحمتتر در دسترس است.
تبدیل ویس به متن#
faster-whisper داخل خود پروسه اجرا میشود. صدا بهشکل .ogg در پوشهٔ خود چت زیر /tmp/aipager-files/ (همان جایی که فایلهای آپلودشده ذخیره میشوند، 0700، و خود فایل 0600) دانلود میشود، روی همین کامپیوتر با CPU به متن تبدیل میشود، و فایل زیر کنترل شما میماند. هیچ صدایی از کامپیوتر بیرون نمیرود. هیچ API بیرونیای در کار نیست. جز توکن ربات که برای اصلاً حرف زدن با تلگرام لازم است، هیچ کلیدی لازم نیست.
اگر .ogg را بعد از تبدیل پاک کنید، تنها سابقهٔ متنی پیام همان متنی است که در سشن claude تایپ میشود (که از قواعد حریم خصوصی خود Claude Code پیروی میکند).
جدا بودن سشنها از هم#
هر سشن Claude Code در dtach خودش اجرا میشود. سوکت کنترل در /tmp/claude-dtach-<name>.sock مال کاربر aipager است؛ dtach اجازهٔ وصل شدن کاربر دیگر را نمیدهد. هر سشن در پوشهٔ خودش شروع میشود: پوشهای که با /new یا Mini App انتخاب شده، یا پوشهای که aipager session را در آن اجرا کردهاید.
aipager بین سشنها جدایی در سطح فایلسیستم نمیسازد: سشنی که به ~/projects/foo وصل است، اگر اجازههای Claude Code اجازه دهند، در اصل میتواند ~/projects/bar را بخواند. برای جدایی قویتر، از تنظیمات ~/.claude/settings.json مخصوص هر پروژه یا یک کانتینر (ایمیج Docker) استفاده کنید.
پیامهای بین سشنهای Claude#
Claude Code اجازه میدهد یک سشن به سشن دیگری پیام بفرستد (ابزار SendMessage آن). در سشن گیرنده آن پیام یک پیام مثل بقیه است، پس aipager اختیاراتش را همانطور تعیین میکند که اختیارات یک پیام تلگرام را: از روی اینکه واقعاً چه کسی فرستاده، هیچوقت از روی چیزی که در متنش آمده.
- پیام اختیارات فرستنده را دارد، هیچوقت بیشتر. وقتی aipager به یک سشن اجازهٔ فرستادن پیام میدهد، یک رسید یکبارمصرف مینویسد: اثر انگشت متن دقیق، پروسهٔ Claude فرستنده (شمارهٔ پروسهاش و، روی Linux، زمان شروعش) و اختیاراتی که آن کار داشت. Claude Code پیام دریافتی را با شمارهٔ پروسهٔ فرستنده ثبت میکند که کرنل تأییدش کرده، و هیچوقت به اسمی که فرستنده برای خودش میگوید اعتماد نمیکند. قبل از اینکه کار گیرنده هیچ ابزاری اجرا کند، aipager این دو را جور میکند و دقیقاً اختیارات کار فرستنده را به آن کار میدهد: پیام یک مالک با اختیارات مالک اجرا میشود، پیام یک عضو با اختیارات همان عضو، و پیامی که در ترمینال سشن فرستنده تایپ شده بیمحدودیت.
- بدون رسید، اختیارات محدود. پیامی که aipager نمیتواند جورش کند (فرستادهشده از سشن یا پروسهٔ Claudeی که aipager اجرایش نمیکند، رسیدی که بعد از یک ساعت منقضی شده، فرستندهای روی کامپیوتر دیگر) با سختترین قانونها بهعلاوهٔ هیچ تغییری در فایلها و هیچ دسترسی به وب اجرا میشود (فقط خواندن و فکر کردن). اختیارات قبلی خود سشن گیرنده هیچوقت روی آن اعمال نمیشود.
- بقیهٔ ورودیهایی که از بیرون سشن میآیند هم همینطور. هر چیزی که Claude Code آن را از طرف آدم خود این سشن ثبت نکند همین اختیارات محدود را میگیرد: یک push از کانال MCP، یک فراخوان از راه دور، پیامی که یک پلاگین در صف میگذارد، پیامی که از صندوق سشن دیگری رد شده، و هر نوع ورودیای که aipager هنوز نمیشناسد (فرمان یک پلاگین ورودی بیرونی حساب میشود، حتی وقتی خودتان شروعش کردهاید). ایجنتهای پسزمینهٔ خود سشن (چیزهایی که برمیگردانند و گزارشهایشان) و اعلانهای کارهای خودش اختیارات همان کار را نگه میدارند.
- پیامی که خود سشن زمانبندی کرده با اختیارات کاری اجرا میشود که زمانبندیاش کرد. وقتی یک کار یک
/loopیا یک cron تنظیم میکند، aipager اختیارات همان کار را برای آن پیام ثبت میکند؛ وقتی بعداً اجرا شود همان اختیارات را میگیرد، هیچوقت بیشتر، هر کسی که آخرین بار حرف زده باشد؛ پیامی که به آن کار اضافه شود فقط میتواند محدودترش کند، و تغییری در قانونها که از کسی که زمانبندیاش کرده اختیاری بگیرد هم محدودترش میکند. همان پیام که دو بار زمانبندی شود اختیارات سختتر را نگه میدارد. یک /loopخالی به جای متن خودش با دستورالعملهای loop خود Claude Code اجرا میشود، پس سختترین اختیارات همهٔ loopهای خالیای را که سشن تنظیم کرده میگیرد، وقتی گفتگوی ذخیرهشده اجرای آن را نشان دهد (تا آن موقع یک اجرای ابزار ردشده آرام رد میشود و Claude دوباره امتحان میکند). پیامی که هیچکس از طریق بررسی aipager زمانبندیاش نکرده اختیارات محدود را میگیرد. - متن چیزی را ثابت نمیکند. اسم فرستنده، یک خط
[via Telegram · @owner] یا «مالک میخواهد…» داخل یک پیام چیزی را عوض نمیکند. به Claude گفته میشود aipager چه کسی را بهعنوان فرستنده تأیید کرده، و اینکه ادعاهای داخل متن مدرک نیستند. - اضافه شدن به یک کار در جریان فقط محدودترش میکند. پیامی از سشن دیگر که وسط یک کار برسد آن کار را به سختتری بین آن دو میرساند، هیچوقت شلتر. این وقتی هم برقرار است که Claude Code پیام را بدون هیچ رویداد پیام جداگانهای بردارد (به کار در جریان اضافه شود، یا از صفش بهعنوان کار بعدی اجرا شود): مسیر سریع مالک قبل از هر اجرای ابزار انتهای گفتگوی ذخیرهشده را نگاه میکند، و پایان کار و خود aipager وقتی چنین پیامی را در صف ببینند سشن را برای یک بررسی علامت میزنند.
- نقشها در چت خودشان میمانند. رسیدی از یک سشن در چت دیگر فقط وقتی حساب میشود که کار آن اختیارات مالک را داشته یا در ترمینالش تایپ شده باشد؛ هر چیز دیگری محدود اجرا میشود.
- چه کسی میتواند بفرستد. نقشهای محدود داخلی اصلاً نمیتوانند از SendMessage استفاده کنند. نقشی که یک policy این را به آن بدهد فقط میتواند به سشنهایی که aipager اجرا میکند بنویسد (با آدرس
uds:…/<pid>.sock)؛aipager doctorدربارهٔ چنین نقشی هشدار میدهد. - ایجنتهای پسزمینه اختیارات خودشان را نگه میدارند. یک ایجنت اختیارات کاری را که شروعش کرد نگه میدارد (یا اختیارات سختتر یک کار بعدی را)، هیچوقت اختیارات کار بعدیای را که بیشتر دارد.
- وقتی متوقف میشود. اگر قانونهای ایمنی جلوی کاری را که سشن دیگری شروع کرده بگیرند، کارت در حال کار از قبل نشان میدهد چه کسی آن را فرستاده، اعلان توقف همین را میگوید، و کسی که کار را سپرده خبردار میشود، تا هیچوقت بیصدا گم نشود: چت سشن فرستنده وقتی هر دو سشن در یک چتاند؛ وگرنه فقط مالک، در چت خصوصی، و فقط وقتی مالک (یا ترمینال) کار فرستنده را گردانده باشد.
محدودیتهای شناختهشده:
- کاری که shell دارد (مال مالک یا یک ادمین) با همان کاربر سیستمعامل aipager اجرا میشود و میتواند یک رسید جعل کند، همانطور که از قبل میتوانست بقیهٔ فایلهای کنترلی aipager را بازنویسی کند. این همان مدل اعتماد موجود است: shell را فقط به کسانی بدهید که به آنها برای کامپیوتر اعتماد دارید.
- سشنهای Claude که aipager اجرایشان نمیکند اصلاً زیر کنترل aipager نیستند، هر چه به آنها بنویسد.
- بیرون از Linux، فرستنده فقط با شمارهٔ پروسهاش جور میشود (Claude Code آنجا زمان شروع را ثبت نمیکند). روی Linux، رکورد دریافتی بدون زمان شروع با هیچ چیزی جور نمیشود.
- Claude Code ممکن است پیام را کمی بعد از اولین اجرای ابزار کار در گفتگوی ذخیرهشده بنویسد. تا وقتی ننوشته، اجرایی که اختیارات محدود ردش میکرد آرام رد میشود (به Claude گفته میشود دوباره امتحان کند؛ کار متوقف نمیشود). بعد از ۸ اجرا بدون آن، کار با اختیارات محدود اجرا میشود.
- مسیر سریع مالک آخرین ۱ مگابایت گفتگوی ذخیرهشده را میخواند، هیچوقت بیشتر. پیامی از سشن دیگر که طولانیتر از این باشد با خطی تمام میشود که مسیر سریع آن را یک پیام از سشن دیگر حساب میکند (هر خط بیشتر از ۲۵۶ کیلوبایت که این پنجره ببُرد همینطور است). پیامی که کاملاً عقبتر باشد (بیشتر از ۱ مگابایت گفتگو بعد از آن و قبل از اجرای ابزار بعدی نوشته شده باشد) را aipager وقتی کار در جریان است یا پایان کار وقتی هنوز در صف است میگیرد، نه مسیر سریع.
- تا وقتی یک سشن یک
/loopخالی تنظیمشده دارد، اولین اجراهای ابزار هر کاری که هیچ پیام تلگرامی شروعش نکرده (از جمله پیامی که در ترمینال تایپ شده) ممکن است آرام رد شوند، تا Claude Code پیام آن کار را بنویسد و aipager بفهمد اجرای loop نیست؛ بعد Claude دوباره امتحان میکند. - aipager کار یک پیام زمانبندیشده را از چیزی میخواند که گفتگوی ذخیرهشده از شروعش به دست آورده؛ بیشتر از ۸ مگابایت، از ۴ مگابایت اول و آخرش (بخش وسط وقتی جدیدترین بخش بود چک شده).
- اختیارات ثبتشدهٔ یک پیام زمانبندیشده تا وقتی سشن بسته شود (
/kill) میماند: سشنی که تمام شده یا ادامه داده شده نگهشان میدارد، همانطور که Claude Code cronهایش و یک بیدار شدن منتظر /loopرا نگه میدارد. برای هر سشن حداکثر ۲۵۶ تا نگه داشته میشود و زمانبندی یکی بیشتر برای همه در آن سشن رد میشود، پس هر کسی که میتواند زمانبندی کند میتواند تمامشان کند (آنوقت یک سشن با اسم دیگر شروع کنید، یا این یکی را /killکنید). پیامی که بدون رکورد اجرا شود اختیارات محدود را میگیرد، یا در سشنی که یک /loopخالی دارد اختیارات همان loop را، وقتی Claude Code آن را در گفتگوی ذخیرهشده نوشت؛ تا آن موقع aipager کار را با کار قبلیاش میخواند. این برای پیامی اتفاق میافتد که قبل از این نسخه تنظیم شده؛ بعد از ریاستارت کامپیوتر (رکوردها در پوشهٔ موقت سیستماند که ریاستارت پاکش میکند، در حالی که Claude Code هنوز میتواند بیدار شدن یک سشن ادامهدادهشده را در همان ساعت برگرداند)؛ و برای یک cron ماندگار، که Claude Code وقتی سشن خودش تمام شد آن را در سشن دیگری از همان پوشه اجرا میکند. سشنی با اختیارات کمتر که همان متن را زمانبندی کند میتواند اختیارات یک پیام را پایین بیاورد (هیچوقت بالا نمیبرد). - سشنی که با اسم یک سشن قبلی از نو شروع شود (جایگزینی) رکوردهای آن سشن را، از جمله اختیارات
/loopخالیاش، تا /killنگه میدارد. - پیام زمانبندیشدهای که Claude Code درست بعد از یک کار دیگر، بدون رویداد پیام جداگانه (از صفش)، اجرا کند، سختتری بین اختیارات خودش و اختیارات کاری که پشتش آمده را میگیرد (بعد از کاری که در ترمینال تایپ شده، دقیقاً مال خودش را).
- اولین اجراهای ابزار کاری که در ترمینال تایپ شده ممکن است تا وقتی Claude Code پیامش را در گفتگوی ذخیرهشده بنویسد رد شوند (تا آن موقع aipager کار قبلی را میخواند).
- یک ایجنت پسزمینه که با اختیارات باریکتری شروع شده میتواند متنی را به یک کار بعدی همان کار برگرداند که آن را با اختیارات کار میخواند، مثل بقیهٔ اعلانهای کار در Claude Code.
- Claude Code میتواند پیامی را برای تأیید در ترمینال گیرنده نگه دارد (تنظیم
crossSessionInboundآن، یا وقتی دو سشن در دو ردهٔ اجازهٔ متفاوت اجرا میشوند، مثلاً دسترسی کامل و خودکار). aipager نمیتواند چنین پیامی را از تلگرام آزاد کند؛ رسید تا یک ساعت منتظر میماند.
خلاصهٔ تهدیدها#
| تهدید | راه مقابله |
|---|---|
| یک غریبه به ربات فرمان میفرستد | فیلتر آیدی چت ردش میکند |
| توکن ربات دزدیده شده | در @BotFather /revoke را بزنید و دوباره تنظیم کنید |
| یک اجرای ابزار آلوده در claude | بررسی ایمنی aipager هر چه را قانونهای یک کار تلگرام رد کنند رد میکند؛ در مود پرسش، قانونهای خود Claude Code در settings.json تعیین میکنند دربارهٔ چه چیزی بپرسد و تنها اجازه زدن دکمه توسط یک نفر است؛ در مود خودکار (پیشفرض هر سشن جدید) بررسی ایمنی خود Claude Code اجراهایی را که بیخطر میداند بدون پرسیدن اجرا میکند و پرریسکها را برای یک نفر نگه میدارد؛ سشن در مود دسترسی کامل (ادمینها، و هر کسی در چت خصوصی خودش) چیزی نمیپرسد، پس فقط بررسی ایمنی aipager جلویش است، و برای کارهای خود مالک و پیامهای ترمینال هیچ چیز (چه کسی تصمیم میگیرد) |
| یک کاربر محدود تلگرام به جای مالک مینشیند | بدون shell، سشنها فقط داخل پوشهٔ root خود عضو شروع میشوند، نوشتن و جستوجو به پروژهٔ داخل آن محدود است (هیچوقت پوشهٔ خانه)، هیچ خواندنی در پوشهٔ خانه بیرون از پوشهٔ root نیست، پوشههای root اعضای دیگر و آپلودهای چتهای دیگر دستنیافتنیاند، فایلهای اطلاعات ورود خواندنی نیستند، مسیرهای محافظتشده برای ابزارهای فایل، بررسیهای شکستخورده رد میکنند (اجرای قانونها در استفادهٔ تیمی)؛ برای نقشی که Bash گرفته فقط در حد تلاش، و برای موارد «محدودیتهای شناختهشده» پوشش داده نمیشود |
| پیام یک سشن ادعا میکند اختیار مالک را دارد | پیام با اختیارات اثباتشدهٔ کار فرستنده اجرا میشود، هیچوقت با اختیارات قبلی سشن گیرنده؛ بدون مدرک، فقط خواندن و فکر کردن (پیامهای بین سشنها) |
| یک عضو گروه اختیارات کس دیگری را قرض میگیرد | هر پیام با نقش فرستندهٔ خودش در همان چتی که از آن آمده اجرا میشود؛ پیام فرستندهٔ دیگری منتظر تمام شدن کار در جریان میماند؛ اگر باز هم یکی اضافه شود، سختترین قانونها برندهاند؛ فرمانهای اسلش نقشهای محدود به فرمانهای کیبورد محدودند (اجرای قانونها در استفادهٔ تیمی) |
| حرفهای گروه به یک سشن میرسد | در گروه فقط فرمانها، جواب به ربات، منشن آن و دکمههای علامتدار کیبورد اجرا میشوند (استفادهٔ تیمی) |
| یک عضو حذفشده دسترسیاش را نگه میدارد | aipager config تنظیمات aipager را بدون ریاستارت دوباره میخواند؛ پیامهای نگهداشتهشدهاش دور ریخته میشوند و کاری از او که هنوز در جریان است اختیارات قبلیاش را از دست میدهد؛ حذفش از گروه تلگرام جلوی خواندن گروه را میگیرد (استفادهٔ تیمی ← خواندن دوبارهٔ تنظیمات) |
| دست بردن در لاگ ممیزی | فقط اضافه میشود؛ جلوگیری از آن بدون یک برنامهٔ امضای جدا خارج از این طرح است |
| یک حملهٔ تزریق پرامپت Claude را وادار میکند آدرسی بنویسد که داده را بیرون میبرد | عکسها و تگهای رسانه در متن Claude بهشکل لینک یا متن نشان داده میشوند، هیچوقت بهشکل رسانه، و پیشنمایش لینک در پیامهای متن ساده و HTML خاموش است (ارتباطات شبکه) |
| مهاجم شبکه | هیچ پورت ورودیای نیست، مستقیم در دسترس نیست |
| بالا بردن دسترسی محلی | بدون sudo یا setuid؛ aipager در فضای کاربر میماند |
| صدای ویس به ابر میرود | تبدیل به متن روی همین کامپیوتر است |
| ایجنت اطلاعات ورود Claude خود aipager را میخواند | انتظار میرود، باگ نیست؛ اطلاعات ورود Claude را ببینید. خود اطلاعات ورود را محدود کنید، نه فایلی را که در آن نگه داشته میشود |
اینها را هم ببینید#
- معماری: ساختار پروسه.
- رویدادهای هوک: aipager واقعاً چه چیزی از claude میبیند.
- فرمانهای ربات: سمتی که شما کنترل میکنید.