當 pty-mcp 遇上 cred-mcp
AI agent 如何透過 HPKE 密封通道完成 SSH 登入、sudo、API 認證——明文全程不進 LLM context。
這不是 password manager,也不是 terminal wrapper。
這是一條把「操作能力」和「憑證可見性」拆開的 AI infrastructure boundary。
問題
AI agent 要操作真實系統,就一定會碰到需要密碼的時刻:SSH 登入、sudo 提權、API key 認證。這個問題比想像中難解:
把密碼貼在 chat 裡
密碼進了 LLM context,出現在 conversation history、session log、甚至 API response 裡。
寫在 .env 讓 AI 讀
AI 讀了 .env 就等於密碼進了 context。任何讀過這段對話的 model 都看到了。
每次操作者手動貼
人坐在旁邊等 AI 告訴他「現在可以貼密碼了」——這不是自動化,是更繁瑣的手動流程。
用 send_secret 彈對話框
pty-mcp 的 send_secret 需要人工點擊——適合互動場景,但 agent 無法在無人值守時自動完成。
理想的解法是:AI 知道去哪拿密碼,可以自己完成整個工作流,但密碼明文從頭到尾都不進 LLM context。
解法:兩個 MCP server 的協作
pty-mcp 管 PTY——本地 shell、SSH、serial port,AI agent 的互動式終端。cred-mcp 管憑證——OS keychain、Vaultwarden,密碼不出明文。把兩者接起來,就得到一個 AI 可以完整自動操作、但密碼永遠不進 context 的系統。
接起來的機制是 HPKE(Hybrid Public Key Encryption)。整個設計有一個核心原則:AI 只充當不可信的快遞——它傳遞公鑰和密文,從不接觸明文。
HPKE 協議:完整流程
AI 呼叫 get_credential_bundle(session_id),pty-mcp 生成 Ed25519 identity key(長期身份)和 X25519 session key(單次會話)。回傳的 ConsumerBundle 只含公鑰,AI 拿到的是可以公開的資訊。
AI 呼叫 request_authorization(item_id, bundle),cred-mcp 對照 consumer registry:identity key 是否已登記?這個 consumer 是否允許請求這個 vault 項目?通過後發出單次使用的 auth token,綁定 (item, consumer, purpose)。
AI 呼叫 vault_seal(item_id, bundle, token),cred-mcp 從 Vaultwarden 取出明文,用消費者的 X25519 session key 做 HPKE 加密(DHKEM-X25519 + HKDF-SHA256 + ChaCha20Poly1305),回傳 SealedBox。明文在 cred-mcp 程序內完成加密,不序列化進任何 response。
AI 呼叫 inject_secret(session_id, sealed_box),pty-mcp 用本地保存的 session 私鑰解密,把明文直接 WriteRaw 進 PTY,立即清零記憶體。回傳 {"success": true}——AI 永遠沒看到密碼。
實際程式碼
場景一:SSH 登入需要密碼
場景二:sudo 提權
安全保證
明文不進 LLM context
所有 MCP tool response 只含 metadata 或密文。明文只短暫存在於 cred-mcp 的 vault read / seal 流程,以及 pty-mcp 的 decrypt / WriteRaw 流程;不進 tool response、LLM context、conversation history 或 audit log。
Session key 單次使用
每個 session_id 的 session key 只能用一次。重複呼叫 inject_secret 使用同一 session_id 必定報錯,防重放攻擊。
Auth token 單次使用 + 綁定
授權 token 消耗後作廢,且綁定 (item, consumer, purpose)——不能用來請求其他 vault 項目。
Consumer registry ACL
cred-mcp 用 registry YAML 管理哪些 consumer 可以請求哪些 vault 項目。未登記的 identity key 直接拒絕。
Audit log(雙端)
pty-mcp 記錄 bundle_generated / secret_injected;cred-mcp 記錄 authorization_issued / vault_sealed。不含明文或私鑰材料。
只有 pty-mcp 能解密
SealedBox 用 pty-mcp 的 X25519 session public key 加密。cred-mcp 不持有 pty-mcp 的 session private key——它只能封裝 SealedBox,產出的密文只有 pty-mcp 能解。
為什麼要這麼複雜?
最常見的反應是:「這四個 tool call 可不可以合成一個?」可以,但那樣就錯了。
四步拆開有明確理由:
- Step 1(生成 bundle)在 pty-mcp——session key 的 private 部分永遠不離開 pty-mcp。如果 bundle 生成在 cred-mcp,private key 就要傳給 pty-mcp,又回到「密碼在 response 裡」的問題。
- Step 2(授權)獨立——把「我可以請求這個嗎」和「給我密碼」分開,讓 cred-mcp 可以實作更細的策略(biometric 確認、rate limit、審計)而不影響加密流程。
- Step 3 和 Step 4 分離——SealedBox 由 AI 傳遞,但 AI 傳遞的只是密文。這讓流程可以加入人工審核點:AI 拿到 SealedBox 後,可以先問「確定要注入嗎?」再呼叫 inject_secret。
這個設計的本質是:讓 AI 成為一個不可信的信差(untrusted courier)——它知道流程、會傳遞訊息,但接觸不到實際內容。
適用場景
- 無人值守的自動化:定期備份、部署腳本、監控腳本——需要密碼但沒有人坐在旁邊點擊對話框
- 多步驟工作流:SSH 進去 → sudo → 改設定 → 重啟服務,整條鏈 AI 自動完成
- 合規環境:需要完整 audit trail 但不能讓密碼出現在 AI session log 裡
- 多台主機管理:每台 SSH 密碼不同,統一存 Vaultwarden,AI 按需取用
企業為什麼需要這個
企業不可能把 AD、firewall、ERP、database 的帳密直接交給 LLM。過去的選擇只有兩個:
- 讓 AI 看到密碼——不可接受的安全風險
- 每碰到密碼提示就必須暫停——要嘛把密碼貼進 chat,要嘛人工接手完成那一步。自動化鏈斷掉了。
pty-mcp x cred-mcp 的目的,是讓 AI 可以執行被授權的操作,而不是取得可攜出的秘密。這個差距決定了企業能不能真正讓 AI agent 進入內網執行維運工作。