Mô hình bảo mật
aipager chạy Claude Code thay mặt bạn và được điều khiển từ Telegram. Từ đó có hai câu hỏi:
- Ai có thể điều khiển bot?
- Khi được điều khiển, bot làm được gì?
Ranh giới tin cậy#
Mọi phần xử lý mà bot có (tin nhắn, file, tin nhắn thoại, nút bấm) đều đi qua bộ lọc chat của python-telegram-bot, được tạo từ các chat cấu hình trong ~/.config/aipager/aipager.yaml, hoặc (với lệnh) qua cùng bước kiểm tra đó trong phần cấp quyền của chính aipager. Ngoại lệ duy nhất chỉ ghi lại người gửi trong các nhóm chưa được cấu hình (bên dưới). Chỉ những chat đã cấu hình mới tương tác được với bot. Không có gì từ chat nào khác được xử lý hay chuyển tới một phiên. Một chat riêng chưa được cấu hình nhận một câu trả lời duy nhất “Bot này chưa được thiết lập để nói chuyện với bạn”. Trong một nhóm chưa được cấu hình, bot không bao giờ trả lời, nhưng có ghi lại ai đã nhắn cho nó ở đó (ID Telegram, username và tên của người đó, cùng ID, tiêu đề và loại của nhóm) vào ~/.claude/aipager-pending-users.json, để aipager config tìm được nhóm mới và những người trong đó trong lúc aipager đang chạy. Ai cũng có thể thêm bot vào một nhóm, nên ai cũng có thể thêm một dòng vào file này; điều đó không cho họ quyền gì. Chỉ bạn đọc được file này (quyền 0600), nó được thay cả file mỗi lần ghi (không bao giờ bị bỏ dở giữa chừng), và giữ tối đa 200 người mới nhất, bỏ những ai không xuất hiện trong 30 ngày. Trong chế độ làm việc nhóm, bên trên còn có một danh sách người được phép kèm vai trò, và trong một nhóm chỉ những tin nhắn gửi cho bot mới được xử lý (một lệnh, một tin trả lời tin của bot, một lần nhắc đến bot, hoặc một lần chạm trên bàn phím của bot), dù bot có là quản trị viên của nhóm hay không (xem nhóm).
Như vậy, phần mở ra với thế giới bên ngoài là:
- Token của bot (một bí mật).
- ID chat (một số nguyên dài).
Nếu cả hai bị lộ, kẻ tấn công có thể điều khiển aipager của bạn. Nếu chỉ token bị lộ, kẻ tấn công có thể đọc tin nhắn gửi cho bot từ chat của bạn nhưng không gửi được lệnh nào mà aipager sẽ làm theo. Nếu chỉ ID chat bị lộ thì chẳng có gì đáng kể: bot cần token mới nói chuyện được với Telegram.
Thông tin bí mật#
| Bí mật | Vị trí | Quyền |
|---|---|---|
| Token bot | ~/.config/aipager/aipager.yaml | mặc định 600 |
| ID chat | cùng file đó | mặc định 600 |
Token không xuất hiện trong log của aipager: các dòng yêu cầu HTTP mang token không được ghi lại, và thông báo lỗi, aipager doctor cùng trình thiết lập đều che đi mọi thứ trông giống token bot. ID chat thì có xuất hiện trong log của aipager (ví dụ trong các dòng về giới hạn tốc độ), nên hãy coi đầu ra của aipager logs là riêng tư. Cả hai đều không được commit: cấu hình nằm trong ~/.config của người dùng, không phải trong repo. Quy trình phát hành lên PyPI qua Trusted Publisher cũng không bao giờ chạm tới bí mật; việc xác thực do OIDC lo.
Nếu bạn nghi token đã bị lộ, hãy thu hồi nó từ @BotFather (/revoke), tạo token mới, và chạy lại aipager config.
Thông tin đăng nhập Claude: điều gì thực sự bảo vệ nó#
aipager chạy một agent tự hành có quyền dùng Bash dưới người dùng UNIX của bạn. Vì vậy mọi bí mật mà aipager đọc được thì agent mà nó khởi động cũng đọc được. Điều này đáng nói thẳng ra, vì rất dễ dựa vào sai loại biện pháp bảo vệ.
~/.config/aipager/ nằm trong danh sách cấm của safety.py, danh sách này ngăn một phiên điều khiển từ Telegram đọc các file bên trong. Đó là một biện pháp theo đường dẫn, và ở đây nó vô tác dụng: thông tin đăng nhập Claude (CLAUDE_CODE_OAUTH_TOKEN hoặc ANTHROPIC_API_KEY) không được lấy qua đường dẫn mà được truyền vào môi trường tiến trình riêng của mỗi phiên được khởi động. Bên trong một phiên, echo $CLAUDE_CODE_OAUTH_TOKEN sẽ in nó ra, có danh sách cấm hay không. Một quy tắc theo đường dẫn file là sai loại biện pháp cho một biến môi trường.
Thông tin đăng nhập nằm ở đâu#
| Cách triển khai | Nguồn | Quyền |
|---|---|---|
| Dịch vụ người dùng systemd | LoadCredential=claude_oauth:%h/.config/aipager/daemon.env, đọc từ $CREDENTIALS_DIRECTORY/claude_oauth | 600 (daemon.env); systemd giữ thông tin đăng nhập bên ngoài môi trường riêng của dịch vụ |
| macOS / Docker / không có systemd | ~/.config/aipager/daemon.env, đọc trực tiếp | 600 |
Cả hai đường đều được cùng một đoạn mã đọc (aipager/daemon_secrets.py) và được đưa cho phiên được khởi động qua bảng env= của tiến trình con, không bao giờ chèn vào chuỗi lệnh bash -c. /proc/PID/cmdline thì ai cũng đọc được (0444); /proc/PID/environ là 0400 (chỉ chủ sở hữu).
Những gì LoadCredential= + 0600 thực sự mang lại: những người dùng UNIX khác trên cùng máy không đọc được thông tin đăng nhập, và nó không bao giờ xuất hiện trong systemctl show, trong bản sao lưu file dịch vụ, hay trong ảnh chụp đầu ra journalctl mà người vận hành dán vào một báo cáo lỗi. Nó không tạo ra ranh giới nào giữa aipager và agent mà aipager khởi động: ranh giới đó không tồn tại, và không có cách chỉnh quyền file nào tạo ra được nó, vì toàn bộ việc của agent là hành động như người vận hành bên trong chính môi trường đó.
Biện pháp duy nhất thực sự có tác dụng#
Hãy giới hạn chính thông tin đăng nhập. Chạy aipager với một tài khoản Anthropic riêng, hoặc với một khóa API có giới hạn chi tiêu, chứ không phải cùng token mà bạn dùng cho lần đăng nhập claude tương tác của chính mình. Thu hồi thông tin đăng nhập của aipager không bao giờ được làm con người bị đăng xuất; nếu có, thì hai cái là cùng một thông tin đăng nhập và không cái nào được giới hạn. aipager doctor --fix có thể tìm một token sẵn có để điền vào daemon.env, nhưng chọn token nào để đưa cho nó là quyết định của người vận hành, và đó là quyết định duy nhất trong tài liệu này quan trọng hơn quyền của các file xung quanh nó.
Chẩn đoán đăng nhập mà không phải đoán#
aipager doctor (và thông báo một dòng gửi một lần mỗi khi aipager khởi động) báo tình trạng đăng nhập qua chính claude auth status của Claude Code, không bao giờ qua một bước kiểm tra file tự chế. Ba trạng thái luôn được viết khác nhau ở mọi nơi chúng xuất hiện:
auth: <method> (<source>): đã xác nhận là đang đăng nhập.auth: none (not logged in): đã xác nhận là chưa đăng nhập.auth: unknown (...): chính lần thử đã thất bại (hết thời gian chờ, không có chương trình, đầu ra không đọc được) hoặc chương trình cũ hơn phiên bản đã thêm đầu ra JSON. Trường hợp này không bao giờ được báo là “chưa đăng nhập”: aipager không từ chối khởi động một phiên chỉ vì một bước kiểm tra đăng nhập mà nó không chắc chắn. Đăng nhập qua Keychain của macOS, thông tin đăng nhập chỉ có refresh token, và đăng nhập gói Max không dùng file đều vô hình với mọi bước kiểm tra file, và nếu không thì sẽ trông y hệt “đã đăng xuất”.
Ai quyết định một lần gọi công cụ có được chạy hay không#
Hai bước kiểm tra riêng biệt quyết định một lần gọi công cụ có được chạy hay không, và chúng chạy theo thứ tự này:
- Bước kiểm tra an toàn của aipager. Claude Code chạy hook
PreToolUsecủa aipager trước mỗi lần gọi công cụ. Với một việc bắt đầu từ Telegram, hook bắt lần gọi tuân theo quy tắc vai trò của người gửi và mức an toàn tối thiểu (đường dẫn được bảo vệ, mẫu lệnh), và từ chối những gì chúng không cho phép: khi đó Claude Code từ chối công cụ mà không hỏi ai, bất kể quy tắc riêng của nó nói gì. Tin nhắn gõ trong terminal không bị bước kiểm tra này giới hạn. Nó bao gồm những gì được mô tả bên dưới, ở phần áp dụng quy tắc trong chế độ làm việc nhóm. - Quy tắc riêng của Claude Code (
~/.claude/settings.json, chế độ quyền của phiên). Nếu chúng nói phải hỏi, Claude Code phátPermissionRequest, và aipager hiện yêu cầu cấp quyền trong Telegram (xem hook). Lần chạm của bạn quay về Claude Code thành quyết định của hook, hoặc được gõ vào hộp thoại riêng của Claude Code nếu 20 giây chờ đã hết. Nếu quy tắc của Claude Code từ chối lần gọi, nó không bao giờ tới Telegram.
Vì vậy aipager có thể từ chối một lần gọi mà Claude Code sẽ cho phép, và khi Claude Code hỏi, aipager không bao giờ tự trả lời có: một lần cho phép luôn là lần chạm của một con người. Có ba thứ cho phép các lần gọi chạy mà không hỏi ai:
- Chế độ Toàn quyền, cho quản trị viên (và cho một người trong chat riêng của chính họ). Một phiên ở Chế độ Toàn quyền được khởi động với
--dangerously-skip-permissions: Claude Code không hỏi gì cả, nên mọi lần gọi mà bước kiểm tra an toàn của aipager không từ chối đều chạy mà không cần chạm. Chỉ quản trị viên (vai tròownervàadmin, hoặc một vai trò cócan_manage) mới bắt đầu được một phiên ở Chế độ Toàn quyền, chuyển một phiên sang đó (/mode full, sau một bước xác nhận) hoặc tiếp tục một phiên ở đó, với một ngoại lệ: trong chat riêng của chính họ với bot, một người có vai trò được gửi tin nhắn và trả lời Claude (can_promptvàcan_approve, tức làusernhưng không phảiread_only) có thể làm điều tương tự cho các phiên của chính họ (/new !x1, hoặc “Phiên mới” đặt ở Chế độ Toàn quyền trong chat đó,/mode full,/resume), trong Telegram và trong Mini App. Không bao giờ trong một nhóm hay trong chat riêng của người khác. Mọi người khác nhận Chế độ Auto thay vào đó, và câu trả lời nói rõ điều này. Chế độ Toàn quyền chỉ bỏ qua các câu hỏi của riêng Claude Code: hook PreToolUse của aipager vẫn áp quy tắc vai trò của người đó (mức an toàn tối thiểu, giới hạn thư mục, quy tắc cấm, file chứa thông tin đăng nhập) lên mọi lần gọi, y như ở Chế độ Hỏi và Chế độ Auto; chế độ mà Claude Code báo về không bao giờ nới lỏng chúng. Bước kiểm tra an toàn của aipager vẫn chạy ở mọi lần gọi trong Chế độ Toàn quyền, nhưng nó không từ chối gì trong các việc của chính chủ sở hữu hay trong tin nhắn gõ ở terminal. - Chế độ Auto, mặc định. Một phiên ở Chế độ Auto được khởi động với
--permission-mode auto: bước kiểm tra an toàn riêng của Claude Code xem xét từng lần gọi, chạy những lần an toàn và dừng những lần rủi ro, rồi những lần đó tới Telegram thành yêu cầu cấp quyền như ở Chế độ Hỏi. Mọi phiên bắt đầu ở Chế độ Auto trừ khi chat đã chọn khác trong/settings→ Phiên mới. Ai có quyền thao tác trên một phiên đều có thể chuyển nó qua lại giữa Chế độ Hỏi và Chế độ Auto (/mode); aipager làm việc đó ngay tại chỗ bằng cách nhấn Shift+Tab trong phiên, không bao giờ bằng một cờ có thể đưa Chế độ Toàn quyền vào vòng Shift+Tab của Claude Code. Nếu có lúc Claude Code báo Chế độ Toàn quyền cho một phiên mà aipager không khởi động ở chế độ đó, aipager hiện những gì được báo, ghi một cảnh báo vào log, và không bao giờ vì thế mà khởi động hay khởi động lại phiên ở Chế độ Toàn quyền. Giới hạn đã biết: chế độ mà Claude Code báo về đi vào qua socket của hook, nơi mọi tiến trình trên máy đều ghi vào được (xem kết nối mạng). Một báo cáo giả có thể đổi chế độ mà một phiên được hiện ra, và trong lúc một lần chuyển giữa Chế độ Hỏi và Chế độ Auto đang chờ xác nhận, nó có thể khiến aipager nhấn Shift+Tab thêm một lần, khiến phiên đang chạy ở lại Chế độ Auto trong khi Chế độ Hỏi mới là chế độ được yêu cầu (lần hook thật tiếp theo sẽ hiện lại chế độ thật). Nó không bao giờ đổi cờ mà một lần khởi động lại hay tiếp tục truyền vào, và không bao giờ đạt tới Chế độ Toàn quyền được: chỉ cờ lúc khởi động mới đặt chế độ đó. aipager không nhấn Shift+Tab trong một phiên mà nó biết có thể xoay vòng tới Chế độ Toàn quyền: một phiên khởi động ở Chế độ Toàn quyền (kể cả sau khi đã được chuyển sang Chế độ Hỏi hay Chế độ Auto trong terminal), hoặc một phiên đã báo Chế độ Toàn quyền cho aipager đang chạy kể từ lúc nó khởi động. Ở đó mọi lần đổi chế độ là một lần khởi động lại, truyền cờ của chế độ mới và bỏ cờ của Chế độ Toàn quyền, và một báo cáo Chế độ Toàn quyền giả chỉ có thể biến một lần đổi thành một lần khởi động lại. aipager không biết được điều này với một phiên khởi động trong terminal bằng cờ thêm Chế độ Toàn quyền vào vòng Shift+Tab (--allow-trước cờ của Chế độ Toàn quyền), hoặc bằng--dangerously-skip-permissionskhi nó đã rời Chế độ Toàn quyền trước khi aipager nghe được tin từ nó (lúc đó aipager không chạy, hoặc đã khởi động lại từ đó tới giờ). Ở những phiên đó, chuyển từ Chế độ Hỏi sang Chế độ Auto có thể dừng ở Chế độ Toàn quyền, và khi đó thẻ báo phiên đang ở Chế độ Toàn quyền. Đừng dùng những cờ đó cho các phiên terminal mà những người không phải quản trị viên có thể đổi chế độ từ Telegram. Để Chế độ Toàn quyền dùng được từ Telegram, aipager đặtskipDangerousModePermissionPrompttrong~/.claude/settings.jsonmỗi lần khởi động (nếu không, lời cảnh báo của Claude Code trước khi chạy ở chế độ này sẽ làm phiên dừng lại); điều đó cũng tắt lời cảnh báo cho các phiên bạn tự khởi động. Tương tự, nó đánh dấu thư mục nơi mỗi phiên bắt đầu là đáng tin trong~/.claude.json(trừ khi thư mục đó hoặc một thư mục phía trên đã được tin), để câu hỏi “Do you trust this folder?” của Claude Code không chặn một phiên khởi động từ Telegram: tin nhắn đầu tiên sẽ trả lời nó bằng “No, exit”. Cài đặt.claude/, hook.git/và.mcp.jsonriêng của một thư mục đáng tin có thể chạy lệnh; vì vậy thành viên không cócan_managekhông bao giờ ghi được các file đó (trừ khipolicy.yamltrả lại shell cho vai trò của họ). Claude Code áp sự tin cậy của một thư mục cho các thư mục bên dưới không nằm trong một repo git; một repo vẫn nhận câu hỏi riêng của nó. Thư mục home được đánh dấu khi một phiên bắt đầu ở đó, vì Claude Code chỉ giữ câu trả lời đồng ý trong thư mục home cho đúng phiên đó. Sau đó, Claude Code không hỏi gì trong một thư mục bên dưới thư mục home của bạn mà không nằm trong một repo git, kể cả trong các phiên bạn tự khởi động./và các thư mục phía trên thư mục home không bao giờ được đánh dấu: việc đó sẽ chạm tới mọi thư mục như vậy trên máy, nên một phiên bắt đầu ở một trong số đó sẽ dừng ở câu hỏi. Lúc khởi động aipager không đánh dấu gì, chỉ đánh dấu thư mục nơi một phiên sắp bắt đầu. Một phiên mới bắt đầu trong thư mục aipager đang chạy (thư mục home vớiaipager service installtrên Linux và macOS, và trong container) hoặc trongAIPAGER_WORK_DIRkhi biến này được đặt; log của aipager cảnh báo lúc khởi động khi thư mục đó là/hoặc một thư mục phía trên thư mục home và chưa được tin. Các phiên bản trước bản này đánh dấu thư mục của aipager mỗi lần khởi động, kể cả/với một dịch vụ macOS (vốn chạy trong/); aipager không phân biệt được mục như vậy với một mục bạn tự tạo, nên để nguyên. aipager không đụng tới~/.claude.jsonvà~/.claude/settings.jsonkhi không đọc được trọn vẹn, hoặc khi một Claude đang chạy đã thay đổi chúng trong lúc aipager đang cập nhật. - Luôn cho phép, trả lại cho Claude Code quy tắc “đừng hỏi lại” mà nó đã đưa ra kèm yêu cầu, y như lựa chọn đó trong hộp thoại riêng của nó.
Để khóa một công cụ với mọi người, hãy cấm nó trong ~/.claude/settings.json; để khóa nó với một số người, hãy dùng quy tắc vai trò của họ trong policy.yaml (xem nhóm).
Những gì yêu cầu cấp quyền hiện ra là những gì sẽ chạy. Một yêu cầu cấp quyền hiện lệnh hoặc đường dẫn thật dưới phần mô tả của Claude. Các ký tự điều khiển hướng của Unicode (U+202A-U+202E, U+2066-U+2069, U+200E, U+200F, U+061C) thay đổi thứ tự mà Telegram, trình duyệt hay điện thoại vẽ phần chữ phía sau chúng, trong khi shell chạy các ký tự theo đúng thứ tự, nên chúng có thể khiến một lệnh trông như một lệnh khác (mẹo “Trojan Source”, CVE-2021-42574). Trong mọi yêu cầu cấp quyền, câu hỏi, bản xem trước thay đổi và bản ghi kiểm tra, chúng được hiện thành các dấu nhìn thấy được (⟨U+202E⟩), và yêu cầu cấp quyền cảnh báo rằng lệnh có thể không chạy đúng như vẻ ngoài của nó. Các ký tự ẩn khác được để nguyên.
Áp dụng quy tắc trong chế độ làm việc nhóm#
Trong chế độ làm việc nhóm, aipager thêm một lớp riêng bên dưới các lần chạm: mọi tin nhắn đến từ Telegram đều mang một ghi chú quyền (ai gửi và vai trò của họ cho phép gì), được ghi vào /tmp/claude-notes-<session>/, mỗi tin nhắn một file. Khi Claude nhận một tin nhắn, chương trình phụ trợ aipager-hook so nó với các ghi chú đó và đặt quy tắc của người gửi vào nơi bước kiểm tra PreToolUse đọc. Mọi thứ khác dựa vào những tính chất sau:
- Nghiêm nhất thắng. Nếu Claude nhận tin nhắn của nhiều người trong cùng một việc (aipager giữ tin nhắn lại để tránh chuyện này, nhưng vẫn an toàn nếu nó vẫn xảy ra), việc đó chạy theo tổ hợp nghiêm nhất: tin nhắn của chủ sở hữu không bao giờ cho người khác mượn quyền của mình.
- Một việc đang chạy không bao giờ được nới rộng. Tin nhắn từ một người khác sẽ chờ việc đang chạy kết thúc, rồi chạy thành việc riêng với quy tắc của người gửi nó. Nếu dù vậy một tin nhắn vẫn nhập vào một việc đang chạy, việc đó giữ quy tắc nghiêm nhất của tất cả mọi người trong đó; một tin nhắn Telegram nhập vào một việc mà bạn gõ trong terminal sẽ bắt phần còn lại của việc đó theo quy tắc của người gửi (tin nhắn của chủ sở hữu không đổi gì).
- Vai trò của bạn là vai trò trong chat mà bạn đang thao tác. Một người có mặt trong nhiều chat (giả sử là
usertrong một nhóm vàownertrong chat riêng của chính họ) mang vai trò của nhóm cho mọi thứ họ gửi hay chạm trong nhóm, và vai trò của chat riêng trong chat riêng, bất kể thứ tự các chat trongaipager.yaml. - Không rõ nghĩa là bị giới hạn. Một tin nhắn không biết là của ai sẽ chạy theo mức tối thiểu có sẵn (không có quyền bỏ qua, mọi quy tắc cấm đều có hiệu lực), không bao giờ như một tin nhắn terminal không bị giới hạn. Tin nhắn bạn gõ thẳng vào terminal vẫn không bị giới hạn; mọi thứ mang dấu Telegram ở bất kỳ dòng nào đều bị kiểm tra. Mọi tin nhắn Telegram đều mang dấu đó, ghi tên người đã gửi chính tin nhắn ấy (không bao giờ là người điều khiển phiên gần nhất); một người gửi mà aipager không biết, ví dụ một người đã bị xóa khỏi
aipager.yamltrong lúc tin nhắn của họ đang chờ, nhận dấu[via Telegram]trơn và mức tối thiểu. - Lệnh slash cần quản trị viên. Một lệnh slash được gõ nguyên vào Claude, không có dấu nào, và một lệnh hay skill như
/deliverchạy cả một việc từ chính nội dung của nó. Vì vậy một thành viên có vai trò không bỏ qua được quy tắc vai trò (uservàread_onlycó sẵn, các vai trò bạn tự tạo mà không cóbypass_role_denies, và bất kỳ ai aipager không biết) chỉ được gửi các lệnh trên bàn phím Lệnh của aipager (cộng thêm/compact) và các lần đổi mô hình (/model <name>); mọi lệnh slash khác, dù được gõ, gửi tới một phiên bằng/<name>, làm chú thích của file, làm tin nhắn đầu tiên của/newhay làm một mẫu, đều bị từ chối với “Lệnh đó chỉ dành cho quản trị viên.” và một 🤷. Chủ sở hữu và quản trị viên gửi được mọi lệnh. Một lệnh slash vẫn tới được Claude từ Telegram thì vẫn bị kiểm tra: hook thấy rằng việc đó đã nhận một tin nhắn Telegram gửi lệnh này và bắt nó theo quy tắc của người gửi. Một lệnh slash bạn gõ trong terminal vẫn không bị giới hạn, trừ khi chính lệnh đó (với bất kỳ đối số nào) đã được gửi từ Telegram trước đó trong phiên: aipager không phân biệt được bản của bạn với việc Claude Code gửi lại bản từ Telegram, nên khi đó bản của bạn chạy theo quy tắc của việc Telegram gần nhất. Điều này kết thúc khi phiên kết thúc. - Một tin nhắn không có người gửi mới thì giữ quy tắc của việc. Trong lúc một việc từ Telegram đang chạy, Claude Code còn báo những tin nhắn mà không tin nhắn Telegram nào giải thích được: một tin nhắn gõ trong terminal và xếp hàng sau việc đó (được báo ngay lúc xếp hàng), một tin nhắn từ một phiên Claude khác trên máy, hoặc chính tin nhắn Telegram đó được gửi lại sau một lần thu gọn. Một tin nhắn như vậy không thêm người gửi Telegram nào, nên việc giữ nguyên quy tắc nó đang chạy, được siết chặt hơn bởi bất kỳ tin nhắn nào còn chờ được nhận và không bao giờ nới lỏng. Một tin nhắn mang chữ Telegram mà aipager không biết là của ai vẫn rơi về mức tối thiểu có sẵn, và một tin nhắn từ người gửi có ít quyền hơn vẫn hạ việc xuống quy tắc của người gửi đó.
Điều gì thực sự kìm giữ một người dùng bị giới hạn. Phiên Claude chạy dưới cùng người dùng hệ điều hành sở hữu cấu hình của aipager (token bot trong ~/.config/aipager/aipager.yaml, thông tin đăng nhập Claude trong ~/.config/aipager/daemon.env) và file quy tắc cho từng việc của nó (/tmp/claude-policy-<session>.json). Một shell chạy dưới người dùng đó có thể chạm tới cả hai qua những cách viết mà không mẫu nào lường trước (sed -i … /tmp/*-policy-*.json, một vòng for trên một glob, một dòng Python) và sửa quy tắc của chính nó thành quy tắc của chủ sở hữu. Vì vậy mẫu lệnh là một bộ lọc, không phải một ranh giới. Với một việc chạy dưới các vai trò bị giới hạn có sẵn (user, read_only), và với một việc mà aipager không biết là của ai, aipager áp những quy tắc sau, không quy tắc nào dựa vào mẫu lệnh (xem các giới hạn đã biết bên dưới để biết chúng không bao gồm những gì):
- Không có shell. Họ không dùng được
Bash, hay bất kỳ công cụ nào khác mà Claude Code dùng để chạy mã (PowerShell,Monitor,REPL,Workflow, các bộ lên lịch tin nhắnCronCreatevàRemoteTrigger, …; danh sách làsafety.CODE_EXECUTION_TOOLS). Một tin nhắn được lên lịch sẽ đến mà không có dấu Telegram, tức là không bị giới hạn, nên những công cụ đó cũng được tính. Vì cùng lý do đó họ không dùng đượcSendMessage(nó có thể chuyển một tin nhắn sang một phiên Claude khác trên máy, nơi tin nhắn sẽ chạy như một tin nhắn terminal), cũng nhưEnterWorktree/ExitWorktree(chúng đổi thư mục làm việc của phiên, thứ mà quy tắc tiếp theo dựa vào). - Việc ghi file nằm trong dự án.
Write,Edit,MultiEditvàNotebookEditchỉ ghi vào bên trong thư mục của phiên (thư mục làm việc mà Claude Code báo trong dữ liệu của hook, không bao giờ là một đường dẫn từ lần gọi công cụ) và scratchpad Claude Code của phiên (/tmp/claude-<uid>/<project>/<session id>/, hoặc/tmp/claude-<uid>/khi không xác định được). Mọi chỗ khác đều bị từ chối: các file khởi động shell của bạn,~/.ssh, các dự án khác, các file của aipager trong/tmp. Bên trong thư mục,.claude/,.git/và.mcp.jsoncũng bị từ chối, vì mỗi thứ trong số đó khiến Claude Code hoặc git chạy một lệnh. Symlink được lần theo trước khi quyết định. Một phiên có thư mục là thư mục home của bạn,/hoặc một thư mục nằm trên thư mục home thì không có thư mục dự án: ở đó một việc bị giới hạn chỉ ghi vào scratchpad của nó, và việc ghi ở bất kỳ chỗ nào khác bị từ chối với “phiên này chạy trong thư mục home (hoặc thư mục cấp trên), và vai trò này chỉ được ghi trong một thư mục dự án”. Để các thành viên bị giới hạn không bị kẹt ở đó,/newvà Mini App không cho họ bắt đầu một phiên trong những thư mục đó (thư mục riêng của aipager, mặc định cho một phiên mới, là thư mục home của bạn khi chạy dưới dịch vụ nền); họ có thể tạo một thư mục mới bên trong thư mục home của bạn và bắt đầu trong đó (phải là thư mục mới: không phải một thư mục bạn đã có ở đó, và không phảibinhaynode_modules, nơi các chương trình khác nạp mã). - Thành viên ở trong thư mục gốc của mình. Một vai trò không có
can_manage(uservàread_onlycó sẵn) làm việc bên trong thư mục gốc của chat, do bạn đặt trongaipager config(mặc định~/aipager-users/<label>;~/aipager-usersvà thư mục đó được tạo với quyền 0700)./new, Tiếp tục, Thay thế, Khởi động lại, việc đổi chế độ và Mini App chỉ bắt đầu hoặc khởi động lại phiên của một thành viên như vậy bên trong thư mục đó. Hook an toàn giữ điều này dù tin nhắn đến bằng đường nào (Telegram, Mini App, tin nhắn từ một phiên khác): thư mục gốc đi theo bản chụp quy tắc của việc, theo biên nhận của tin nhắn giữa các phiên và theo các quyền được ghim hoặc được lên lịch, và khi các thành viên có thư mục gốc khác nhau dùng chung một việc thì thư mục hẹp hơn thắng (hai thư mục không lồng vào nhau thì chỉ còn lại thư mục riêng của phiên). Việc ghi chỉ vào thư mục của phiên khi thư mục đó nằm trong thư mục gốc (một phiên nằm ngoài, ví dụ một phiên bắt đầu trước khi có thư mục gốc, vẫn tiếp tục chạy, nhưng các việc của nó chỉ ghi vào scratchpad: “phiên này chạy bên ngoài thư mục riêng của vai trò này”), vàRead,LSP,GrepvàGlobkhông chạm tới gì trong thư mục home của bạn nằm ngoài thư mục gốc (“trong thư mục home, vai trò này chỉ được đọc bên trong thư mục riêng của nó”), trừ scratchpad của phiên và file trả lời riêng của nó. Thư mục gốc của mọi chat khác đều là đường dẫn được bảo vệ với thành viên đó, dù nó nằm ở đâu, và thư mục chứa file gửi lên của mọi chat khác trong/tmp/aipager-files/cũng vậy (Read,Grep,Globvà các công cụ ghi; với một vai trò được cho Bash, một lệnh nêu tên một thư mục như vậy sẽ bị từ chối, ở mức làm hết sức có thể), cùng với các file mà mọi chat từng dùng chung ở đó trước khi có thư mục riêng cho từng chat. File được gửi lên trong chính chat của thành viên vẫn đọc được. Các thư mục ngoài thư mục home của bạn (/usr,/etc,/srv) vẫn đọc được. Một vai trò tự tạo cóbypass_role_denieshoặcbypass_safetynhưng không cócan_manageđược ghi và tìm ở mọi chỗ bên trong thư mục gốc của nó, và việc đọc của nó trong thư mục home của bạn cũng ở trong đó: chỉ chủ sở hữu và quản trị viên là không bị giới hạn. Vớibypass_safety, trong một phiên có thư mục nằm ngoài thư mục gốc của nó, nó chỉ ghi và tìm trong scratchpad, giống như mộtuser, và một khi một lần gọi công cụ của nó bị từ chối thì phần còn lại của việc bị dừng. - Việc tìm kiếm nằm trong dự án. Một
GrephoặcGlobchỉ được phép khi tất cả những điều sau đều đúng: thư mục nó tìm (pathcủa nó, hoặc thư mục của phiên khi không có, symlink được lần theo) nằm bên trong thư mục của phiên (không tính khi đó là thư mục home của bạn,/hoặc thư mục nằm trên thư mục home, giống như với việc ghi) hoặc scratchpad; thư mục đó không phải đường dẫn được bảo vệ và không chứa đường dẫn nào như vậy (một phiên bắt đầu trong thư mục home của bạn chứa~/.config/aipager, nên không tìm được từ gốc của nó); mọi glob đều là một mẫu tương đối đơn giản (không có/,~,$hay ký tự ổ đĩa ở đầu, không có.., không có#(chú thích đối với ripgrep) hay!(phủ định), không có ký tự thoát\); và bạn không có quy tắc đường dẫn được bảo vệ nào không bắt đầu bằng/hoặc~(**/.envcó thể nằm ở bất kỳ đâu trong dự án, nên khi có một quy tắc như vậy, mọi lần tìm bị giới hạn đều bị từ chối). Mọi thứ khác bị từ chối. Đây cố ý là một danh sách cho phép: ba vòng rà soát, vòng nào cũng tìm ra một cách viết glob mới đưa ripgrep ra khỏi thư mục mà một mô hình về cách nó phân tích đã dự đoán, và một trong số đó đã đọc được token của bot. - Các công cụ file tôn trọng đường dẫn được bảo vệ.
Read,LSPvà các công cụ ghi kiểm tra một đường dẫn thực sự dẫn tới đâu, kể cả qua symlink. File điều khiển duy nhất mà một việc được đọc là/tmp/claude-reply-<session>.txtcủa chính phiên đó, file mà aipager nêu tên trong tin nhắn khi bạn trả lời một tin nhắn cũ hơn; file trả lời của các phiên khác, và mọi lần ghi vào nó, vẫn bị từ chối. Trên macOS,/tmplà/private/tmpvà ổ đĩa không phân biệt chữ hoa chữ thường, nên cả hai cách viết, và mọi kiểu viết hoa, đều được nhận ra. - Một bước kiểm tra bị hỏng sẽ từ chối. Nếu việc quyết định về một lần gọi công cụ gây ra lỗi (một byte NUL trong đường dẫn, một bug), hoặc hook hết bộ nhớ trong lúc quyết định, lần gọi bị từ chối, trừ khi quy tắc của phiên đọc được và cho phép chủ sở hữu bỏ qua. Trước đây nó được cho qua.
Đường dẫn được bảo vệ của riêng bạn. Phần safety: của policy.yaml (deny_paths_no_access, deny_paths_no_write, deny_bash_patterns) được cộng thêm vào các đường dẫn được bảo vệ và mẫu lệnh có sẵn. Nó là một phần của mức tối thiểu mà mọi việc của mọi vai trò trừ owner đều nhận, kể cả quản trị viên, và nó áp dụng cho các công cụ file, việc tìm kiếm và lệnh Bash y như danh sách có sẵn. aipager ghi danh sách gộp vào /tmp/claude-policy-.floor-<uid>.json (bản thân nó cũng là đường dẫn được bảo vệ) cho các đường dự phòng của hook an toàn; nếu file đó không có, không đọc được hoặc thuộc về một người dùng hệ điều hành khác, hook dùng danh sách có sẵn, không bao giờ ít hơn. Một thay đổi qua lần tải lại không cần khởi động lại áp dụng cho các tin nhắn gửi sau lần tải lại đó, không áp dụng cho một việc đang chạy hay một tin nhắn đang chờ. Cho tới bản 0.7.20, phần này được đọc và hiện ra bởi aipager doctor --safety-check nhưng không được áp dụng.
Những việc aipager không biết là của ai. Một việc từ Telegram mà không có người gửi nào để bắt theo sẽ chạy theo mức tối thiểu, tương ứng với vai trò user có sẵn (không tính những giới hạn bổ sung bạn thêm cho user trong policy.yaml) cộng với phần safety: của bạn: không có công cụ chạy mã, việc ghi và tìm kiếm bị giới hạn, các đường dẫn được bảo vệ. Điều đó xảy ra với một tin nhắn có ghi chú quyền đã hết hạn (sau 24 giờ) hoặc không ghi được, và với một tin nhắn mang chữ Telegram mà không ghi chú nào khớp. Nút Thử lại chạy dưới quyền của người chạm nó khi chính người đó đã gửi tin nhắn đang được thử lại, nên một lần Thử lại của chủ sở hữu trên tin nhắn của chính mình vẫn giữ Bash; một lần Thử lại trên tin nhắn của người khác, hoặc trên tin nhắn mà aipager không biết người gửi, chạy theo mức tối thiểu, để chữ của không ai mượn được quyền của người chạm.
Việc đọc bên ngoài các đường dẫn được bảo vệ được phép với một vai trò không bị giữ trong thư mục gốc (một vai trò tự tạo có can_manage nhưng không có quyền bỏ qua): nó có thể đọc các dự án khác của người dùng hệ điều hành. user và read_only có sẵn không đọc được gì trong thư mục home của bạn nằm ngoài thư mục gốc của chúng (ở trên). Các file chứa thông tin đăng nhập của bạn là đường dẫn được bảo vệ với mọi vai trò không có bypass_role_denies (user và read_only có sẵn, các vai trò bạn tự tạo, và mức tối thiểu): ~/.ssh, ~/.gnupg, ~/.aws, ~/.config/gh, ~/.git-credentials, ~/.netrc, ~/.docker/config.json, ~/.kube, ~/.config/gcloud, ~/.azure, ~/.password-store, ~/.pgpass, ~/.npmrc và ~/.pypirc không thể bị đọc, ghi hay tìm. Đây là mặc định của vai trò, nên deny_paths_no_access riêng của một vai trò trong policy.yaml sẽ thay thế chúng cho vai trò đó, trừ khi một việc đang chạy có thêm một tin nhắn khác hoặc một tin nhắn gõ trong terminal nhập vào: từ lúc đó cho tới khi việc kết thúc, nó lại bị giữ theo danh sách này (quy tắc nghiêm nhất thắng). Một file chứa thông tin đăng nhập ở chỗ khác (một .env trong dự án, một token trong thư mục khác) thì không được bao gồm: hãy thêm nó vào deny_paths_no_access của vai trò. Chủ sở hữu không bị giữ theo những điều này. Việc ghi của quản trị viên không bị giới hạn. Grep/Glob của họ có thể tìm trong bất kỳ thư mục nào không chạm tới một đường dẫn được bảo vệ: một lần tìm bị từ chối khi thư mục nó tìm (path của nó, hoặc thư mục của phiên khi không có; với Glob, còn cả thư mục mà mẫu của nó nêu ra, như trong /home/you/.config/aipager/*, hoặc thư mục phía trên một mẫu không có ký tự đại diện, nơi Claude Code tìm tên đó ở mọi độ sâu), sau khi đã lần theo symlink, là một thư mục được bảo vệ, nằm bên trong một thư mục như vậy, hoặc chứa một thư mục như vậy. Vì vậy Grep trên ~, /, thư mục nằm trên thư mục home của bạn hoặc /tmp bị từ chối (chúng chứa ~/.config/aipager, ~/.claude hoặc các file /tmp/claude-* của aipager), còn Grep trên một dự án hay trên /tmp/myproj thì không. Một quy tắc đường dẫn được bảo vệ không bắt đầu bằng / hoặc ~ (như **/.env) có thể khớp ở bất kỳ thư mục nào, nên khi nó có trong quy tắc của một quản trị viên (hoặc trong phần safety:, phần mà mọi vai trò trừ owner đều nhận) thì mọi lần tìm của quản trị viên đều bị từ chối, như với các vai trò bị giới hạn; aipager doctor cảnh báo về một quy tắc như vậy trong phần safety:. Quản trị viên có Bash, nên quy tắc của họ vẫn chỉ làm hết sức có thể (bên dưới).
Giới hạn đã biết. Những điều sau không được bao gồm:
- Nếu chính hook bị lỗi trước khi bắt đầu quyết định (ví dụ không đọc được file trạng thái của nó, hoặc hết bộ nhớ trong lúc khởi động), Claude Code coi đó là “cho phép”. Một người dùng bị giới hạn không gây ra được điều này; mọi lỗi trong lúc quyết định đều dẫn tới từ chối.
- Những công cụ không phải công cụ file nhưng gửi được dữ liệu ra ngoài thì không được kiểm tra theo đường dẫn:
WebFetch(mọi thứ việc đó đọc được thì nó cũng gửi được tới một URL) và các công cụ gửi khác của Claude Code. Hãy cấm chúng cho một vai trò trongpolicy.yamlnếu điều đó quan trọng với bạn.SendUserFile, công cụ gửi file vào chat, thì có được kiểm tra (xem file mà Claude gửi). - Một agent chạy nền do một việc bị giới hạn khởi động vẫn tiếp tục chạy sau khi việc đó kết thúc, và các lần gọi công cụ sau đó của nó được xét theo tin nhắn mới nhất trong phiên: một tin nhắn sau đó của chủ sở hữu hoặc từ terminal sẽ gỡ bỏ các giới hạn của nó.
- Một vai trò bạn tự tạo có
can_managenhưng không có quyền bỏ qua nào thì không bị giữ trong thư mục gốc: nó vẫn có thể bắt đầu trong bất kỳ thư mục dự án nào mà chat của nó đã làm việc (một thư mục mà một phiên trước đó đã dùng), kể cả thư mục của bạn, và đọc các dự án khác của bạn. - Một việc không gán được cho thành viên nào (người gửi không rõ, thành viên đã bị xóa, tin nhắn từ một phiên khác mà không xác minh được người gửi) chạy theo mức tối thiểu bị giới hạn, mức này không có thư mục gốc. Về việc đọc, mức đó yếu hơn một
usercó sẵn: một việc như vậy có thể đọc bất kỳ file nào trong thư mục home của bạn không phải đường dẫn được bảo vệ (ví dụ các dự án khác của bạn), trong khi việc của mộtuserchỉ đọc bên trong thư mục gốc của nó. Một việc như vậy cũng đọc được file gửi lên của mọi chat trong/tmp/aipager-files/, còn việc của mộtuserchỉ đọc được file gửi lên của chính chat mình. Việc ghi của nó vẫn bị giới hạn trong thư mục của phiên và scratchpad. Một tin nhắn đã xác minh từ phiên của một thành viên mang theo thư mục gốc của thành viên đó. - Thư mục gốc chỉ ràng buộc các công cụ file. Một vai trò tự tạo mà bạn cho
Bashcó thể đọc ra ngoài nó qua shell (uservàread_onlycó sẵn không cóBash). Một vai trò tự tạo cóbypass_safetynhưng không cócan_managegiữ quyền bỏ qua của nó cho mọi thứ trừ đường dẫn của các công cụ file: không công cụ hay mẫu lệnh nào bị cấm với nó. - Một lý do từ chối nêu tên thư mục gốc của thành viên khác sẽ để lộ đường dẫn của thư mục đó cho Claude và trong chat.
- Việc của một thành viên đã đang chạy khi thư mục gốc xuất hiện (một lần cập nhật) không có thư mục gốc cho tới tin nhắn tiếp theo của thành viên đó.
- Bên trong dự án, một người dùng bị giới hạn có thể thay đổi những gì sau này chính bạn chạy hoặc đọc (mã nguồn,
Makefile, các script trongpackage.json,conftest.py, mộtCLAUDE.mdmà các việc của bạn nạp vào).
Một vai trò có Bash (admin, vai trò bỏ qua các quy tắc cấm của vai trò, hoặc bất kỳ vai trò nào được policy.yaml trả lại Bash: allow_tools có nêu Bash, hoặc một deny_tools đã được thay) chỉ bị giữ theo các quy tắc làm hết sức có thể: các mẫu lệnh bên dưới, thứ mà một người quyết tâm có thể lách qua. aipager doctor cảnh báo một lần cho mỗi vai trò như vậy mà một thành viên mang. Chỉ cho Bash những người mà bạn sẵn sàng cho một shell trên máy này.
Các quy tắc lệnh có sẵn. Trong số các mẫu Bash mà mọi việc từ Telegram có Bash không phải của chủ sở hữu phải tuân theo, có một mẫu chặn chữ claude: nó chặn một claude lồng bên trong (với bất kỳ đường dẫn, lớp bọc hay cờ nào) và mọi truy cập vào ~/.claude chỉ bằng một quy tắc, và nó cũng bắt được tên trơn của các file điều khiển /tmp/claude-* của aipager, vì tên chúng có cùng tiền tố (một mẫu không phải là sự bảo vệ thật cho những file đó: một cách viết bằng glob tránh được nó). Ngoại lệ duy nhất là thư mục scratchpad mà Claude Code tạo ra, /tmp/claude-<uid>/…: đúng thư mục đó, như một thành phần đường dẫn trọn vẹn, được miễn, nên các lệnh chạy từ scratchpad của một phiên không bị dừng. Thư mục đó thuộc về người dùng hệ điều hành chứ không phải một phiên, nên một việc có Bash có thể đọc scratchpad và đầu ra tác vụ của các phiên khác ở đó, như công cụ Read cũng làm được. Một glob hay một tên giống nó (/tmp/claude-1*, claude-1000x), một thư mục tạm khác, và mọi thứ đi tới được từ nó (…/../../home/you/.claude) vẫn bị chặn. Những lần xuất hiện khác của chữ này (git log --grep=claude, một file tên claude-notes.md, pip show claude-agent-sdk) cũng vẫn bị chặn, một cách cố ý: cùng một chữ đặt tên cho chương trình, các SDK của nó và các file điều khiển của aipager, và một mẫu không phân biệt được chúng. Các quy tắc bạn thêm trong policy.yaml nhìn thấy lệnh đúng như được gõ, kể cả scratchpad.
Tìm kiếm qua Bash. Một số phiên bản Claude Code không có công cụ Grep hay Glob (2.1.289 là một ví dụ), và khi đó Claude tìm bằng grep, rg hoặc find qua Bash, nơi quy tắc Grep/Glob ở trên không áp dụng. Với mọi vai trò trừ owner, các mẫu lệnh còn cấm một lần tìm hay liệt kê đệ quy toàn bộ thư mục home hoặc /: grep -r (-R, --recursive, -d recurse, egrep, fgrep, rgrep), rg (và rg --files), ag, ack (và ack -f), find, fd, tree, du và ls -R có thư mục là ~, ~user, $HOME, ${HOME} hoặc "$HOME", /home, /home/<user> (hoặc /home/$USER), /Users, /Users/<user>, /root hoặc / (có hoặc không có /, /. hay /* ở cuối), hoặc ~/.config hay ~/.local, nơi chứa các thư mục của aipager. Điều này vẫn đúng sau cd x &&, trong một pipe, bên trong bash -c "...", sau xargs, env hay timeout và sau các cài đặt kiểu LC_ALL=C. Câu trả lời khuyên hãy tìm bên trong một thư mục dự án. Những lần tìm trong một thư mục dự án (grep -rn x ~/proj, rg x src/, find . -name y), những lần tìm bên trong một dự án ĐỂ tìm một đường dẫn home (grep -rn "$HOME" src, từ đầu tiên sau các tùy chọn là chữ cần tìm, không phải thư mục) và những lần tìm không đệ quy (grep x ~/.bashrc, ls ~) không bị ảnh hưởng. Đây là cách làm hết sức có thể, như mọi mẫu lệnh: một mẫu nhìn thấy các từ chứ không thấy shell chạy chúng ở đâu, nên nó không thấy được một lần tìm không có thư mục, hoặc với . hay .., chạy từ (hoặc dưới) thư mục home (cd ~ && grep -r x .), một thư mục home được chạm tới qua một biến khác hay một glob, một thư mục home viết đầy đủ nằm ngoài /home, /Users và /root, cũng không thấy \grep, hay một thư mục đứng ngay sau một từ kết thúc bằng -e hoặc -f (bị coi là chữ mà -e/-f đưa cho grep và rg, nên ag x -f ~ lọt qua). Nó cũng không biết tùy chọn nào nhận một giá trị riêng, nên rg -t py "$HOME" . hoặc grep -rn -A 3 "/home/you" src sẽ đọc py hoặc 3 là chữ cần tìm và đường dẫn home là thư mục, và bị từ chối (hãy viết -A3, --include=*.py, hoặc đặt tùy chọn sau chữ cần tìm), một đường dẫn home đưa cho -e bên trong một cụm tùy chọn cũng vậy (grep -rne "/home" src; -r -n -e thì được); một lệnh được trích dẫn bên trong chữ của một lệnh khác (echo "a; rg x ~") cũng bị từ chối, và du -sh ~ (chỉ xem dung lượng) cũng vậy.
Vì sao một lần chặn dừng cả việc. Sau khi một lần gọi công cụ bị chặn, mọi lần gọi sau đó trong cùng việc cũng bị từ chối, cho tới tin nhắn tiếp theo. Nếu không có điều này, agent chỉ việc thử lại cùng hành động bằng cách viết khác (một glob, một lệnh khác, một đường dẫn được mã hóa) cho tới khi có một cách viết lọt qua các mẫu. Dừng cả việc khiến lần thử đầu tiên trở nên đắt giá; nó không biến một mẫu thành ranh giới, vì cách viết đầu tiên được thử có thể chính là cách lọt qua. Cái giá của một lần chặn nhầm là một việc bị dừng, đó là lý do các quy tắc ở trên được giữ hẹp.
Xem nhóm → quy tắc hoạt động thế nào để biết các quy tắc mà những ghi chú này mang theo, như phía bạn nhìn thấy.
File mà Claude gửi#
Mọi phiên aipager khởi động đều có công cụ SendUserFile của Claude Code (CLAUDE_CODE_ATTACHMENTS_BY_PATH=1), công cụ này đọc file và gửi vào chat của phiên (lệnh). Hook PreToolUse kiểm tra từng file của lời gọi trước khi Claude Code đọc nó:
- Mức sàn gửi, cho mọi việc, kể cả việc của owner và tin nhắn gõ trong terminal: cấu hình và trạng thái của aipager (
~/.config/aipager/**,~/.local/share/aipager/**,~/.local/state/aipager/**và các file điều khiển trong/tmp), toàn bộ~/.claude/**và~/.claude.json*(thông tin đăng nhập và tài khoản của Claude), các file thông tin đăng nhập mà mọi vai trò bị giới hạn đều không được chạm tới (~/.ssh/**,~/.gnupg/**,~/.aws/**,~/.git-credentials,~/.netrcvà phần còn lại của danh sách đó),/proc/**,/sys/**,/dev/**, và các tên trông như bí mật ở bất cứ đâu (.env,.env.*,*.pem,*.key,id_rsa*,id_ed25519*và các tên khóa khác,*.p12,*.pfx,*.kdbx,*.jks,*.keystore,.pgpass,credentials,*credentials*.json). Đường dẫn được so theo cách viết và theo nơi nó thật sự dẫn tới, nên một symlink tới một trong số đó cũng bị từ chối. Vài file vô hại cũng có những tên này (một file*.keycủa Keynote, một.env.example) và cũng bị từ chối. - Chính file đó: là file thường sau khi đã giải symlink, có một hard link, không rỗng, tối đa 50 MB.
- Quy tắc riêng của việc: những gì việc đó không được đọc (Read), và với thành viên (vai trò không có
can_manage, bị giữ trong một thư mục gốc) là quy tắc chặt hơn: mọi file, theo cách viết và theo nơi nó thật sự dẫn tới, phải nằm trong thư mục gốc. Khác với việc đọc của thành viên, không có ngoại lệ cho file ngoài thư mục home (/tmp,/etc) và cũng không có cho scratchpad của phiên.
Từ chối là từ chối nhẹ: Claude đọc lý do ("SendUserFile refused, nothing was sent: ... Tell the person which file could not be sent and why.") và việc vẫn tiếp tục; phiên không bị dừng như khi một công cụ bị chặn. Một lần kiểm tra bị lỗi sẽ từ chối file, kể cả với owner. Mỗi file bị từ chối có một bản ghi FileRefused trong log kiểm tra.
aipager không tin lời socket điều khiển khi nói về một file: mọi tiến trình cục bộ đều ghi được vào đó, và có thể nêu bất kỳ phiên, lời gọi hay lịch sử trò chuyện nào, và chỉ riêng hook của một lời gọi công cụ cũng đủ khiến một phiên đang rảnh bị coi là đang làm việc. Vì vậy một lời gọi chỉ được tính trong một việc mà aipager đã thấy bắt đầu bằng một prompt (một prompt nó gõ từ Telegram, hoặc một UserPromptSubmit) và trong việc nền của việc đó, cho tới khi phiên rảnh và không còn việc nào chạy; lúc bắt đầu, aipager ghi lại thời điểm và lịch sử trò chuyện mà nó theo dõi cho phiên (một UserPromptSubmit sau đó của cùng việc sẽ nêu lại lịch sử này). Một lệnh slash do aipager gõ không bắt đầu việc nào (lệnh chạy một việc sẽ tự gửi UserPromptSubmit của nó), và một việc không bao giờ bắt đầu (15 giây sau phiên vẫn rảnh và không có việc nào) sẽ bị đóng lại. Claude Code bắt đầu một số việc mà không có UserPromptSubmit: một tin nhắn gửi trong lúc một việc đang chạy và được nó lấy làm việc tiếp theo, và việc mà một hook Stop giữ cho tiếp tục (/goal). Một việc như vậy được tính khi chính aipager đã gõ tin nhắn đó, hoặc ngay khi lịch sử trò chuyện mà nó ghi lại cho thấy việc bắt đầu sau Stop gần nhất (một dòng hàng đợi dequeue, hoặc dòng Stop hook feedback: mà Claude Code ghi); trước đó không phần nào của lời gọi được đọc, trong tối đa 20 giây. Nó lấy danh sách file từ PreToolUse mà nó đã kiểm tra, hoặc từ PostToolUse khi cái trước bị lỡ (sau khi khởi động lại); không bao giờ lấy kết quả của Claude Code. Nó chỉ sao chép mỗi lời gọi một lần: một id lời gọi đã sao chép thì không bao giờ được sao chép hay gửi lại (nó giữ 256 id gần nhất cho mỗi phiên, kể cả sau khi khởi động lại). Nó tìm đường dẫn thật của từng file đúng một lần rồi kiểm tra lại file ở đó (mức sàn, các kiểm tra file và thư mục gốc của phiên), mở chính đường dẫn đó mà không đi theo link, chắc chắn file đã mở đúng là file đã kiểm tra và vẫn nằm ở đường dẫn đó, rồi sao chép vào ~/.local/state/aipager/outbox/<chat>/ (thư mục 0700, file 0600), nơi mọi việc bị giới hạn vốn đã không được chạm tới. Trước khi gửi, nó tìm lời gọi trong lịch sử trò chuyện đã ghi lại cho việc đó (với lời gọi của một subagent thì trong lịch sử riêng của subagent đó, nằm bên cạnh), không bao giờ trong một lịch sử mà datagram của một lời gọi công cụ nêu ra: một dòng của trợ lý có tool_use SendUserFile với id đó và đúng các file đó, được ghi từ cùng thư mục và không sớm hơn lúc việc bắt đầu, trong một file .jsonl dưới ~/.claude/projects (hoặc $CLAUDE_CONFIG_DIR/projects, theo giá trị đặt cho chính aipager) thuộc chính người dùng hệ thống của aipager. Claude Code ghi lịch sử trò chuyện muộn, nên aipager chờ lời gọi tối đa 20 giây, và trong lúc việc của phiên vẫn còn chạy (một agent nền, một lời gọi công cụ) nó giữ các file lại và tìm lại sau. Lời gọi vẫn không tìm thấy khi không còn gì chạy thì không được gửi và các bản sao bị xóa; lời gọi đến khi không có việc như thế đang chạy, hoặc đến lần thứ hai, thì thậm chí không được sao chép. Mỗi file của chúng có một bản ghi FileFailed trong nhật ký kiểm tra với lý do unverified. Chat chỉ nhận một dòng về chúng (⚠️ Không gửi được report.pdf: aipager không xác nhận được Claude đã gửi file này trong việc này.) khi chính lịch sử trò chuyện của Claude cho thấy đúng lời gọi đó trong lúc việc chạy, và dòng ấy nêu các file mà dòng lịch sử đó liệt kê, nên một lời gọi giả không thể đưa dòng nào hay tên nào vào chat; ngoài ra không có gì trong chat. Chỉ bản sao được gửi, chỉ vào chat của chính phiên đó, sau câu trả lời của việc, sau các file trước đó của phiên và không giữ chân việc tiếp theo; bản sao bị xóa ngay khi đã gửi, hoặc sau một ngày.
Những gì các kiểm tra này không che được: một tiến trình chạy dưới chính người dùng hệ thống của aipager có thể ghi lịch sử trò chuyện của Claude cũng như ghi vào socket (nó vốn đã tự đọc được các file đó). Một tiến trình của người dùng khác chạm được tới socket cũng có thể giả một UserPromptSubmit, thứ mà với các kiểm tra này sẽ bắt đầu một việc và nêu lịch sử trò chuyện của việc đó; sau đó nó có thể khiến aipager gửi vào chat của phiên đó một file mà một lời gọi SendUserFile thật đã nêu, nếu lời gọi đó được ghi vào lịch sử ấy từ cùng thư mục sau thời điểm đó (chẳng hạn một lời gọi mà một phiên khác đang thực hiện đúng lúc ấy), mỗi lời gọi một lần. Để làm vậy nó còn phải gửi id riêng của lời gọi đang chạy đó (tool_use_id), thứ mà người dùng khác không đọc được: nó nằm trong lịch sử trò chuyện dưới ~/.claude của người dùng aipager, và hook nhận nó qua đầu vào, không qua dòng lệnh. Một Stop giả sẽ kết thúc việc với các kiểm tra này, nên các file mà việc đó gửi sau đó không được gửi. Một phiên khởi động với $CLAUDE_CONFIG_DIR khác với của aipager, hoặc một việc mà aipager đã lỡ UserPromptSubmit của nó (gõ trong terminal khi aipager đang khởi động lại), sẽ không gửi được file nào. Một việc mà Claude Code bắt đầu không có hook theo cách khác (hai cách ở trên là những cách đã biết) cũng không gửi file; một file mà lịch sử trò chuyện của Claude cho thấy nó đã yêu cầu ở đó sẽ nhận dòng ở trên. Socket nằm trong $XDG_RUNTIME_DIR, nơi người dùng khác không chạm tới được, khi biến này được đặt; nếu không, nó nằm trong /tmp.
Log kiểm tra#
Mỗi lần chạm Cho phép / Từ chối, cùng mỗi câu trả lời cho AskUserQuestion, đều ghi thêm một dòng JSON vào ~/.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
}
Các trường (xem aipager/audit.py để có danh sách đầy đủ):
ts: dấu thời gian UTC theo ISO 8601, chính xác tới giây.session/label: tên nội bộ và tên dễ đọc.action: chuyện gì đã xảy ra (Allowed,Denied,Allowed always, một câu trả lờiAskUserQuestion, một lần tự động từ chối, …).tool/summary: tên công cụ và phần đầu vào đã được cắt ngắn, hoặc nội dung câu hỏi.user_id/username: ai đã chạm, để biết ai làm gì trong chế độ làm việc nhóm;scope_label/scope_chat_idkhi có nhiều chat được cấu hình.denied: true với mọi lần từ chối, do lần chạm hay do quy tắc.
Việc ghi chỉ làm hết sức có thể. Nếu ổ đĩa đầy hoặc ~/.claude/ không ghi được, aipager ghi một WARNING vào log và tiếp tục chạy: không mất gì trong im lặng, không crash. Xem aipager/audit.py.
Log kiểm tra trên đĩa chỉ được ghi thêm. Cùng với câu trả lời kiểm tra trong chat (một tin nhắn Telegram cho mỗi quyết định, nằm dưới thẻ làm việc), nó cho bạn hai bản ghi độc lập.
Ranh giới quyền hạn#
aipager không bao giờ nâng quyền. Không sudo, không setuid, không doas. Mọi file nó ghi đều nằm trong thư mục home của bạn, trong /tmp (các file điều khiển cho từng phiên của aipager, file tải về và log khởi động lại) hoặc trong thư mục chứa socket điều khiển của nó ($XDG_RUNTIME_DIR); danh sách đầy đủ ở kiến trúc. Mọi tiến trình con (claude, dtach, các trình cài đặt, claude update) đều chạy dưới người dùng của aipager.
Cài tính năng thoại từ Telegram ([📦 Cài tính năng thoại]) đi qua trình cài đặt sở hữu aipager (uv tool install --reinstall aipager[voice], pipx install --force aipager[voice]), và nếu không thì cài các gói của tính năng đó bằng chính Python của aipager (<python> -m pip install): luôn vào môi trường riêng của aipager, không bao giờ vào Python của hệ thống.
Nút [🔄 Khởi động lại aipager], dưới dịch vụ systemd, lên lịch systemctl --user restart aipager.service trong một dịch vụ người dùng tạm thời riêng; trên macOS nó chạy launchctl kickstart -k; một aipager bạn tự khởi động sẽ tạo một bản sao tách rời của chính nó, bản sao này chờ bản cũ thoát. Sau một lần cập nhật, chỉ dịch vụ systemd tự khởi động lại (xem lệnh → cập nhật). Tất cả đều chạy dưới cùng người dùng; không nâng quyền.
Kết nối mạng#
aipager không lắng nghe trên cổng TCP nào ngoài loopback. Máy chủ Mini App chỉ bind vào 127.0.0.1 (viết cứng trong mã: cố ý không có tùy chọn host) và chỉ có thể được truy cập từ bên ngoài qua tunnel được quản lý, mô tả bên dưới. Kết nối đi ra:
- Long polling HTTPS tới
api.telegram.org(bot Telegram nhận cập nhật). - HTTPS tới
telegram.orgđể lấy script SDK của Mini App (telegram-web-app.js), tải một lần khi máy chủ Mini App khởi động và làm mới nhiều nhất mỗi ngày một lần; được lưu đệm trong~/.local/share/aipager/webapp-sdk/. aipager phục vụ những byte đó cho trang từ chính nguồn của nó và không bao giờ chạy chúng. aipager không phân phối lại script này: không có gì được đóng gói kèm theo, và khi aipager không có bản nào, trang tải nó trực tiếp từtelegram.org, như trước đây. - Tunnel của Mini App tới Cloudflare, trong lúc nó bật (mặc định là bật). Chương trình
cloudflaredmà nó chạy được tải một lần từ các bản phát hành ởgithub.com/cloudflare/cloudflared, được kiểm tra với một SHA-256 đã ghim, và được lưu đệm trong~/.local/share/aipager/cloudflared/; mộtcloudflaredtrênPATHcủa bạn không bao giờ được dùng. Không điều nào trong số này xảy ra khi bạn dùng URL Mini App riêng (xem bên dưới). - Kiểm tra phiên bản, chỉ khi ai đó chạm Kiểm tra cập nhật hoặc Cập nhật (
/update, phần Cập nhật của Mini App), hoặc khi ai đó có quyền cập nhật mở thẻ Cài đặt của Mini App (tối đa mười phút một lần cho mỗi app đang mở):pypi.orgcho aipager, vàdownloads.claude.aihoặcregistry.npmjs.orgcho Claude Code, tùy theo cách nó được cài. Sau đó một lần cập nhật chạy trình cài đặt (nói chuyện với PyPI hoặc Homebrew) vàclaude update.aipager updatetrên dòng lệnh chỉ chạy trình cài đặt. - Việc cài tính năng thoại, khi bạn chạm nút của nó: trình cài đặt tải các gói từ PyPI, và mô hình nhận dạng giọng nói được tải từ Hugging Face (
huggingface.co) ở lần dùng đầu tiên. - HTTPS tới
raw.githubusercontent.com(file khóa báo cáo) và tới máy chủ nhận dữ liệu của Sentry, chỉ khi bạn xác nhận gửi một báo cáo sự cố: báo cáo bạn đã xem trước và một bản tóm tắt ngắn tạo từ nó, không gì khác. Xem Báo cáo sự cố.
Kết nối đi vào:
- Socket datagram Unix ở
$XDG_RUNTIME_DIR/aipager.sock(quay về/tmp/aipager.sockkhi không đặt$XDG_RUNTIME_DIR). Được aipager bind và đặt quyền bằngchmodlúc khởi động (aipager/dtach/hook_receiver.py). Quyền0o666để mọi tiến trình trên máy đều gửi được sự kiện hook tới nó: cùng mức tin cậy với~/.claude/settings.json, file vốn đã quyết định cái gì chạy hook.
Điều này có nghĩa: aipager không phải là một bề mặt tấn công từ xa. Một kẻ tấn công trên mạng không chạm tới được nó nếu chưa có chỗ đứng trên chính máy đó.
Chữ của Claude và những gì Telegram tự tải về. Máy chủ của Telegram tự tải một số địa chỉ có trong tin nhắn: một khối ảnh, video hoặc âm thanh trong tin nhắn định dạng phong phú, và trang đằng sau một bản xem trước link. Câu trả lời của Claude, các ghi chú của nó trên thẻ làm việc, phần mô tả agent và đối số của công cụ đến Telegram đúng như Claude viết, nên một prompt injection có thể khiến Claude viết ra một địa chỉ mang một bí mật ra ngoài, không cần lần gọi công cụ nào và không có yêu cầu cấp quyền nào. aipager chặn đường thứ nhất, và chặn đường thứ hai ở những chỗ Telegram có công tắc cho việc đó:
- Trong mọi tin nhắn định dạng phong phú, một ảnh viết dạng
được gửi thành một link thường kèm chữ alt của nó, còn mọi cú pháp ảnh khác và các thẻ thô<img>,<video>,<audio>,<source>,<picture>,<figure>,<iframe>,<embed>,<object>,<tg-document>,<tg-collage>,<tg-slideshow>,<tg-button>và<tg-button-row>được hiện thành chữ (aipager/bot/media_guard.py). Bên trong một khối mã, ví dụ một đoạn HTML, điều này làm hiện thêm một\hoặc<. - Bản xem trước link bị tắt trên mọi tin nhắn chữ thường và HTML mà aipager gửi hoặc sửa (kể cả các bản dự phòng dạng chữ thường của câu trả lời và thẻ), cả tin nhắn của chính nó. Các link Mini App của nó là nút bấm, thứ mà Telegram không tải về.
Tài liệu của Telegram không nêu tùy chọn xem trước nào cho tin nhắn định dạng phong phú; việc Telegram có tạo bản xem trước link cho loại tin nhắn này hay không chưa được kiểm tra trên dịch vụ thật.
Tunnel của Mini App#
Mini App bật sẵn theo mặc định, nên trên một bản cài chuẩn, aipager mở tunnel này mà không cần ai yêu cầu. Đó là một đánh đổi có chủ ý (một tùy chọn phải tự bật mà không ai phát hiện ra thì cũng như một tính năng không tồn tại), nhưng nó có nghĩa là một bản cài mới sẽ kết nối ra internet. Tắt nó bằng aipager miniapp disable, hoặc đặt enabled: false dưới miniapp: trong aipager.yaml; một giá trị false ghi rõ luôn được tôn trọng. Lưu ý rằng một khối miniapp: bị thiếu hoặc bị hỏng giờ sẽ được hiểu là BẬT chứ không phải TẮT.
Mini App (trừ khi có --url thay thế) khởi động một quick tunnel của Cloudflare được quản lý, trỏ tới máy chủ loopback của chính aipager, và đăng địa chỉ công khai https://*.trycloudflare.com mà nó được cấp làm nút menu Telegram và câu trả lời của /app.
Địa chỉ đó không phải bí mật, và cũng không cần là bí mật: mọi yêu cầu mà Mini App phục vụ đều được kiểm tra với chữ ký initData của Telegram (xem Ranh giới tin cậy ở trên), chính bước kiểm tra bảo vệ nó dù URL đã được lấy bằng cách nào. Chỉ biết tên máy chủ thì kẻ tấn công không có thêm gì so với việc đoán bừa một tên miền con trycloudflare.com: bước kiểm tra chữ ký mới là cánh cổng thật sự. Hãy đối xử với nó như với mọi URL không công khai nhưng cũng không bí mật khác: cứ để chạy cũng được, nhưng đừng dán vào một kênh công khai khi không có lý do.
Tên máy chủ chỉ được giữ trong bộ nhớ: nó không bao giờ được ghi vào aipager.yaml hay bất kỳ đâu dưới ~/.config/aipager/. Một lần khởi động lại có kế hoạch (khởi động lại sau cập nhật, Khởi động lại ngay, khởi động lại tại chỗ khi không có dịch vụ) giữ tunnel tiếp tục chạy, nên địa chỉ, nút menu và một Mini App đang mở vẫn hoạt động: aipager kế tiếp chỉ tiếp quản nó sau khi kiểm tra rằng đó là cùng một lần khởi động máy, cùng cổng, tiến trình đúng là cloudflared đó (dòng lệnh của nó), nó vẫn báo cùng địa chỉ (~/.claude/aipager-tunnel.json giữ một mã băm của địa chỉ, không bao giờ giữ chính địa chỉ) và địa chỉ đó trả lời. Mọi trường hợp khác đều dừng nó và khởi động một tunnel mới, và một tunnel được giữ lại mà không aipager nào tiếp quản trong 3 phút sẽ bị một tiến trình canh gác nhỏ dừng. Mọi cách dừng khác (Ctrl+C, docker stop, systemctl stop) đều dừng tunnel, và khởi động lại máy hoặc container sẽ cho một địa chỉ mới. MINIAPP_KEEP_TUNNEL=0 tắt việc giữ lại. Nếu aipager bị dừng đột ngột (hết bộ nhớ, kill -9), tunnel vẫn chạy cho tới khi aipager khởi động lại với tunnel được quản lý đang bật (Mini App bật, không có MINIAPP_PUBLIC_URL) và tiếp quản hoặc dừng nó, hoặc cho tới khi máy khởi động lại; aipager uninstall cũng dừng nó. Nếu tunnel ngừng chạy giữa chừng, aipager khởi động lại nó với khoảng chờ tăng dần và đăng lại địa chỉ mới; nếu sau vài lần thử mà nó vẫn không lên lại được, nút bị gỡ đi chứ không để trỏ tới một cổng không còn hoạt động.
Đặt MINIAPP_PUBLIC_URL (hoặc aipager miniapp enable --url https://…) sẽ tắt hẳn tunnel được quản lý (chương trình cloudflared không bao giờ được tải hay chạy), và URL được cho sẽ được dùng nguyên như vậy, với cùng bước kiểm tra initData bên dưới. Tự động phát hiện Tailscale (tailscale status --json) vẫn là một lựa chọn đơn giản hơn cho những ai đã chạy Tailscale và hoàn toàn không muốn phụ thuộc vào Cloudflare.
Chuyển tin nhắn thoại thành chữ#
faster-whisper chạy ngay trong tiến trình. Âm thanh được tải về dưới dạng .ogg vào thư mục riêng của chat trong /tmp/aipager-files/ (nơi lưu các file được gửi lên, quyền 0700, file 0600), được chuyển thành chữ ngay trên máy bằng CPU, và file vẫn nằm trong tầm kiểm soát của bạn. Không có âm thanh nào rời khỏi máy. Không có API bên thứ ba. Không cần khóa nào ngoài token bot vốn đã cần để nói chuyện với Telegram.
Nếu bạn xóa file .ogg sau khi chuyển thành chữ, bản ghi duy nhất của tin nhắn dưới dạng chữ là bản chép được gõ vào phiên claude (nơi nó tuân theo chính sách quyền riêng tư của Claude Code).
Tách biệt giữa các phiên#
Mỗi phiên Claude Code chạy trong dtach riêng của nó. Socket điều khiển ở /tmp/claude-dtach-<name>.sock thuộc về người dùng của aipager; dtach từ chối kết nối từ người dùng khác. Mỗi phiên bắt đầu trong thư mục riêng của nó, thư mục được chọn qua /new, Mini App, hoặc thư mục mà bạn đã chạy aipager session trong đó.
aipager không tách biệt các phiên ở mức hệ thống file: một phiên gắn với ~/projects/foo về nguyên tắc có thể đọc ~/projects/bar nếu quyền của claude code cho phép. Hãy dùng ~/.claude/settings.json riêng cho từng dự án hoặc một container (image Docker) để tách biệt chặt hơn.
Tin nhắn giữa các phiên Claude#
Claude Code cho phép một phiên gửi tin nhắn cho phiên khác (công cụ SendMessage của nó). Trong phiên nhận, tin nhắn đó là một tin nhắn như mọi tin nhắn khác, nên aipager quyết định quyền của nó theo cùng cách nó quyết định quyền của một tin nhắn Telegram: dựa vào ai thực sự đã gửi, không bao giờ dựa vào nội dung tin nhắn nói gì.
- Tin nhắn mang quyền của người gửi, không bao giờ hơn. Khi aipager cho phép một phiên gửi tin nhắn, nó ghi một biên nhận dùng một lần: dấu vân tay của đúng nội dung đó, tiến trình Claude đã gửi (số tiến trình và, trên Linux, thời điểm khởi động của nó) và quyền mà việc đó có. Claude Code ghi lại tin nhắn nhận được cùng số tiến trình của người gửi, con số mà kernel đã xác minh, không bao giờ tin vào cái tên mà người gửi tự xưng. Trước khi việc nhận chạy bất kỳ công cụ nào, aipager so khớp hai thứ đó và cho việc đúng quyền của việc gửi: tin nhắn của chủ sở hữu chạy với quyền của chủ sở hữu, của một thành viên thì với quyền của thành viên đó, một tin nhắn gõ trong terminal của phiên gửi thì không bị giới hạn.
- Không có biên nhận, quyền bị giới hạn. Một tin nhắn aipager không so khớp được (do một phiên hay tiến trình Claude mà aipager không chạy gửi tới, một biên nhận đã hết hạn sau một giờ, một người gửi trên máy khác) chạy với quy tắc nghiêm nhất, cộng thêm không được thay đổi file và không được truy cập web (chỉ đọc và suy nghĩ). Quyền trước đó của chính phiên nhận không bao giờ áp dụng cho nó.
- Các đầu vào khác từ bên ngoài phiên cũng vậy. Mọi thứ mà Claude Code ghi nhận là không đến từ chính người của phiên này cũng nhận những quyền bị giới hạn đó: một lần đẩy từ kênh MCP, một lời gọi từ xa, một tin nhắn mà plugin xếp hàng, một tin nhắn chuyển tiếp từ hộp thư của phiên khác, và mọi loại đầu vào mà aipager chưa biết (một lệnh của plugin được tính là đầu vào từ bên ngoài, kể cả khi chính bạn khởi động nó). Các agent chạy nền của chính phiên (những gì chúng trả về và báo cáo) và các thông báo tác vụ của nó giữ quyền của công việc.
- Một tin nhắn mà phiên đã lên lịch chạy với quyền của việc đã lên lịch nó. Khi một việc thiết lập một
/loophoặc một cron, aipager ghi lại quyền của việc đó cho tin nhắn; khi nó chạy sau này, nó nhận đúng những quyền đó, không bao giờ hơn, bất kể ai nói sau cùng; một tin nhắn nhập vào việc đó chỉ có thể thu hẹp nó, và một thay đổi quy tắc lấy bớt quyền của người đã lên lịch cũng thu hẹp nó. Cùng một tin nhắn được lên lịch hai lần giữ quyền nghiêm hơn. Một/looptrơn chạy bằng chỉ dẫn vòng lặp của Claude Code thay vì chữ của chính nó, nên nó nhận quyền nghiêm nhất trong số mọi vòng lặp trơn mà phiên đã thiết lập, một khi cuộc trò chuyện đã lưu cho thấy lần chạy (cho tới lúc đó, một lần gọi công cụ bị từ chối sẽ bị từ chối nhẹ nhàng, và Claude thử lại). Một tin nhắn không ai lên lịch qua bước kiểm tra của aipager nhận các quyền bị giới hạn. - Chữ không chứng minh được gì. Tên người gửi, một dòng
[via Telegram · @owner]hay “chủ sở hữu yêu cầu…” bên trong tin nhắn không thay đổi gì. Claude được cho biết aipager đã xác minh ai là người gửi, và rằng những lời khẳng định trong chữ không phải bằng chứng. - Nhập vào một việc đang chạy chỉ thu hẹp nó. Một tin nhắn từ phiên khác đến trong lúc một việc đang chạy sẽ làm việc đó nghiêm bằng bên nghiêm hơn trong hai bên, không bao giờ lỏng hơn. Điều này vẫn đúng khi Claude Code nhận tin nhắn mà không có sự kiện tin nhắn riêng của nó (được gộp vào việc đang chạy, hoặc chạy thành việc tiếp theo từ hàng đợi): đường tắt của chủ sở hữu xem phần cuối của cuộc trò chuyện đã lưu trước mỗi lần gọi công cụ, và lúc kết thúc việc cùng aipager đều đánh dấu phiên cần kiểm tra khi thấy một tin nhắn như vậy trong hàng đợi.
- Vai trò ở lại trong chat của nó. Một biên nhận từ một phiên của chat khác chỉ được tính khi việc của nó có quyền của chủ sở hữu hoặc được gõ trong terminal của nó; mọi thứ khác chạy với quyền bị giới hạn.
- Ai được gửi. Các vai trò bị giới hạn có sẵn hoàn toàn không dùng được SendMessage. Một vai trò được một quy tắc cho dùng nó chỉ có thể gửi tới các phiên do aipager chạy (được gọi bằng địa chỉ
uds:…/<pid>.sock);aipager doctorcảnh báo về một vai trò như vậy. - Agent chạy nền giữ quyền riêng của chúng. Một agent giữ quyền của việc đã khởi động nó (hoặc quyền nghiêm hơn của một việc sau đó), không bao giờ là quyền của một việc sau có nhiều quyền hơn.
- Khi nó bị dừng. Nếu một việc do phiên khác khởi động bị quy tắc an toàn dừng lại, thẻ làm việc vốn đã hiện ai gửi nó, thông báo dừng cũng nói vậy, và người đã giao việc được báo, để việc không bao giờ biến mất trong im lặng: chat của phiên gửi, khi cả hai phiên ở cùng một chat; nếu không thì chỉ chủ sở hữu, trong chat riêng, và chỉ khi chủ sở hữu (hoặc terminal) đã điều khiển việc gửi.
Giới hạn đã biết:
- Một việc có shell (của chủ sở hữu hoặc của quản trị viên) chạy dưới cùng người dùng hệ điều hành với aipager và có thể làm giả một biên nhận, cũng như nó vốn đã có thể sửa các file điều khiển khác của aipager. Đây là mô hình tin cậy sẵn có: chỉ cho shell những người mà bạn sẵn sàng giao cả máy.
- Các phiên Claude mà aipager không chạy hoàn toàn không do aipager quản lý, dù ai gửi tin cho chúng.
- Ngoài Linux, người gửi chỉ được so khớp theo số tiến trình (ở đó Claude Code không ghi thời điểm khởi động). Trên Linux, một bản ghi nhận không có thời điểm khởi động sẽ không khớp với gì cả.
- Claude Code có thể ghi tin nhắn vào cuộc trò chuyện đã lưu một chút sau lần gọi công cụ đầu tiên của việc. Cho tới khi nó ghi, một lần gọi mà quyền bị giới hạn sẽ từ chối được từ chối nhẹ nhàng (Claude được bảo thử lại; việc không bị dừng). Sau 8 lần gọi mà vẫn chưa có, việc chạy với quyền bị giới hạn.
- Đường tắt của chủ sở hữu đọc 1 MiB cuối của cuộc trò chuyện đã lưu, không bao giờ hơn. Một tin nhắn từ phiên khác dài hơn mức đó kết thúc bằng một dòng mà đường tắt coi là một tin nhắn như vậy (mọi dòng dài hơn 256 KiB bị cửa sổ cắt ngang đều được coi như thế). Một tin nhắn nằm hoàn toàn xa hơn về phía trước (hơn 1 MiB lịch sử được ghi sau nó trước lần gọi công cụ tiếp theo) sẽ do aipager bắt được trong lúc việc chạy, hoặc do lúc kết thúc việc bắt được khi nó vẫn còn trong hàng đợi, chứ không phải do đường tắt.
- Trong lúc một phiên có một
/looptrơn đã thiết lập, bất kỳ việc nào không do tin nhắn Telegram khởi động (kể cả một tin nhắn gõ trong terminal) có thể bị từ chối nhẹ nhàng ở những lần gọi công cụ đầu tiên, cho tới khi Claude Code đã ghi tin nhắn của việc đó và aipager biết được đó không phải lần chạy của vòng lặp; sau đó Claude thử lại. - aipager đọc việc của một tin nhắn được lên lịch từ những gì được thêm vào cuộc trò chuyện đã lưu kể từ khi việc bắt đầu; quá 8 MiB thì đọc 4 MiB đầu và 4 MiB cuối (phần ở giữa đã được kiểm tra lúc nó còn là phần mới nhất).
- Quyền được ghi lại cho một tin nhắn được lên lịch tồn tại cho tới khi phiên bị tắt (
/kill): một phiên đã kết thúc hay được tiếp tục vẫn giữ chúng, như Claude Code giữ các cron và một lần đánh thức/loopđang chờ. Mỗi phiên giữ tối đa 256 bản ghi, và lên lịch thêm một tin nữa quá mức đó sẽ bị từ chối với mọi người trong phiên, nên ai được lên lịch đều có thể dùng hết chúng (khi đó hãy bắt đầu một phiên với tên khác, hoặc/killphiên này). Một tin nhắn chạy mà không có bản ghi nhận các quyền bị giới hạn, hoặc trong một phiên có/looptrơn thì nhận quyền của vòng lặp đó, một khi Claude Code đã ghi nó vào cuộc trò chuyện đã lưu; cho tới lúc đó, aipager đọc việc theo việc trước nó. Điều đó xảy ra với một tin nhắn được thiết lập trước phiên bản này; sau khi khởi động lại máy (các bản ghi nằm trong thư mục tạm của hệ thống, nơi bị xóa khi khởi động lại máy, trong khi Claude Code vẫn có thể đưa lần đánh thức của một phiên được tiếp tục quay lại trong vòng một giờ); và với một cron lâu dài, thứ mà Claude Code chạy trong một phiên khác cùng thư mục khi phiên của chính nó đã kết thúc. Một phiên có ít quyền hơn lên lịch cùng nội dung có thể hạ quyền của một tin nhắn (không bao giờ nâng lên). - Một phiên bắt đầu mới dưới tên của một phiên trước đó (Thay thế) giữ các bản ghi của phiên đó, kể cả quyền của
/looptrơn của nó, cho tới/kill. - Một tin nhắn được lên lịch mà Claude Code chạy ngay sau một việc khác, không có sự kiện tin nhắn riêng (từ hàng đợi của nó), nhận quyền nghiêm hơn trong hai thứ: quyền của chính nó và quyền của việc mà nó nối theo (sau một việc gõ trong terminal thì đúng quyền của chính nó).
- Những lần gọi công cụ đầu tiên của một việc gõ trong terminal có thể bị từ chối cho tới khi Claude Code đã ghi tin nhắn của nó vào cuộc trò chuyện đã lưu (cho tới lúc đó aipager đọc việc theo việc trước nó).
- Một agent chạy nền được khởi động với quyền hẹp hơn có thể trả chữ về một việc sau của cùng công việc, việc đó đọc chữ với quyền của công việc, như với các thông báo tác vụ khác của Claude Code.
- Claude Code có thể giữ một tin nhắn chờ duyệt trong terminal nhận (cài đặt
crossSessionInboundcủa nó, hoặc khi hai phiên chạy ở các nhóm quyền khác nhau, ví dụ Chế độ Toàn quyền và Chế độ Auto). aipager không thể thả một tin nhắn như vậy từ Telegram; biên nhận chờ tối đa một giờ.
Tóm tắt các mối đe dọa#
| Mối đe dọa | Biện pháp |
|---|---|
| Người lạ gửi lệnh cho bot | Bộ lọc ID chat từ chối |
| Token bot bị đánh cắp | Dùng /revoke trong @BotFather, cấu hình lại |
| Một lần gọi công cụ của claude bị lợi dụng | Bước kiểm tra an toàn của aipager từ chối những gì quy tắc của một việc từ Telegram cấm; ở Chế độ Hỏi, quy tắc riêng của Claude Code trong settings.json quyết định nó hỏi về điều gì và lần chạm của một con người là cách cho phép duy nhất; ở Chế độ Auto (mặc định cho mọi phiên mới), bước kiểm tra an toàn riêng của Claude Code chạy những lần gọi nó cho là an toàn mà không hỏi và dừng những lần rủi ro để một người quyết định; một phiên ở Chế độ Toàn quyền (quản trị viên, và một người trong chat riêng của chính họ) không hỏi gì, nên chỉ còn bước kiểm tra an toàn của aipager chắn đường, và với các việc của chính chủ sở hữu cùng tin nhắn gõ trong terminal thì không còn gì chắn cả (ai quyết định) |
| Người dùng Telegram bị giới hạn leo lên quyền của chủ sở hữu | Không có shell, phiên chỉ được bắt đầu bên trong thư mục gốc của chính thành viên, việc ghi và tìm kiếm bị giới hạn trong dự án bên trong đó (không bao giờ là thư mục home), không đọc gì trong thư mục home ngoài thư mục gốc, thư mục gốc của các thành viên khác và file gửi lên của các chat khác không chạm tới được, file chứa thông tin đăng nhập không đọc được, đường dẫn được bảo vệ cho các công cụ file, bước kiểm tra hỏng thì từ chối (áp dụng quy tắc trong chế độ làm việc nhóm); chỉ làm hết sức có thể với một vai trò được cho Bash, và không bao gồm các trường hợp trong “Giới hạn đã biết” |
| Tin nhắn của một phiên tự xưng có quyền của chủ sở hữu | Tin nhắn chạy với quyền đã được chứng minh của việc gửi, không bao giờ với quyền trước đó của phiên nhận; không có bằng chứng thì chỉ đọc và suy nghĩ (tin nhắn giữa các phiên) |
| Một thành viên nhóm mượn quyền của người khác | Mỗi tin nhắn chạy với vai trò của chính người gửi nó trong chat mà nó đến; tin nhắn của một người gửi khác phải chờ việc đang chạy; nếu dù vậy vẫn nhập vào, quy tắc nghiêm nhất thắng; lệnh slash từ các vai trò bị giới hạn chỉ giới hạn trong các lệnh của bàn phím (áp dụng quy tắc trong chế độ làm việc nhóm) |
| Chuyện trò trong nhóm lọt vào một phiên | Trong một nhóm chỉ lệnh, tin trả lời bot, lần nhắc đến bot và lần chạm bàn phím có đánh dấu mới được xử lý (nhóm) |
| Một thành viên đã bị xóa vẫn còn quyền truy cập | aipager config tải lại aipager mà không cần khởi động lại; các tin nhắn đang bị giữ của họ bị bỏ và một việc của họ còn đang chạy mất quyền cũ; xóa họ khỏi nhóm Telegram sẽ khiến họ không đọc được nhóm nữa (nhóm → tải lại không cần khởi động lại) |
| Sửa trái phép log kiểm tra | Chỉ ghi thêm; ngăn chặn hẳn nằm ngoài phạm vi nếu không có một chương trình ký riêng |
| Một prompt injection khiến Claude viết ra một địa chỉ mang dữ liệu ra ngoài | Ảnh và thẻ media trong chữ của Claude được hiện thành link hoặc chữ, không bao giờ thành media, và bản xem trước link bị tắt trên tin nhắn chữ thường và HTML (kết nối mạng) |
| Kẻ tấn công trên mạng | Không có cổng đi vào, không chạm tới trực tiếp được |
| Leo thang quyền trên máy | Không sudo / setuid; aipager ở lại trong không gian người dùng |
| Âm thanh tin nhắn thoại lọt lên đám mây | Việc chuyển thành chữ diễn ra trên máy |
| Agent đọc thông tin đăng nhập Claude của chính aipager | Đúng như dự kiến, không phải lỗi: xem Thông tin đăng nhập Claude. Hãy giới hạn chính thông tin đăng nhập, chứ không phải file chứa nó |
Xem thêm#
- Kiến trúc: cách tiến trình được tổ chức.
- Sự kiện hook: aipager thực sự thấy gì từ claude.
- Lệnh của bot: phía do con người điều khiển.