lapidakis/Deckard

GitHub: lapidakis/Deckard

一个驻留在 Mac 上的 MCP 服务器,以多层安全策略管道代理 Apple 原生服务给 AI agent,解决 agent 直接操作邮件、日历、云盘等敏感数据时的信任边界问题。

Stars: 3 | Forks: 0

# Deckard 一个驻留在 Mac 上的 MCP 服务器,通过 stdio 或 HTTP 将 Apple 原生服务(Mail、日历、iCloud 云盘、语音备忘录、提醒事项)代理给 AI agent。一个信任边界,一份审计日志,一个执行安全保障的统一入口。 这座桥梁建立在一个简单的前提下:与你的 iCloud 通信的 LLM agent 应该更像是一个具有受限权限的服务账户,而不是一个完全受信任的用户。每一次调用都会穿过相同的策略管道(auth → ACL → redaction → injection-tagging → approval-gate → audit),并且针对每个 token,每一层都是可配置的。 **当前状态:** **v1.0.0-beta.3 (公开测试版)。** 涵盖 6 项服务的 43 个工具,经过代码签名和公证的 Developer ID 构建,**111 个单元测试**(包含一个遍历所有已注册工具的 schema 验证器),带有首次启动引导流程的 daemon + 菜单栏 UI,通过 Sparkle (UI) 和 `deckard self-update` (CLI) 进行自动更新,每次推送均触发 CI。专为个人家庭实验室使用而设计;安全模型记录在 [`docs/security-model.md`](docs/security-model.md) 中。已知的测试版问题和路线图请见 [`CHANGELOG.md`](CHANGELOG.md)。 ## 为什么会有这个项目 大多数“AppleScript MCP”项目将 Mail 或日历作为简单的 RPC 暴露出来:工具触发后,结果作为普通字符串返回,agent 可以读取用户能读取的所有内容。这对于受信任的 prompt 和演示截图来说没问题;但在任何 agent 可能被攻破、邮件内容可能包含恶意代码,或者操作链可能处于无人值守运行状态的系统中,这种做法是错误的。 Deckard 位于 agent 和 macOS 之间,并增加了以下功能: - **默认拒绝的 ACL** 及每个 token 的配置文件。一个“triage”agent 只能获得 `mail.list_messages` + `mail.mark_read` 权限,除此之外没有任何其他权限。一个“trusted”agent 可以获得完整的操作界面,但 `mail.send` 仍需通过批准对话框进行路由。一个“readonly”实验性 token 无法向任何地方写入任何内容。 - **出站数据脱敏。** 在任何工具结果到达模型之前,符合机密信息特征的子字符串会被替换为 `[REDACTED:]`。涵盖云凭证(AWS / GCP / Azure / DO)、API 密钥(OpenAI / Anthropic / Stripe / Google / Twilio / npm)、GitHub PAT、Slack/JWT token、RSA 私钥块、类似 SSN 的模式——以及**一次性 token**:2FA / OTP / 验证码/ 登录码、魔法链接 URL 参数、内联的 `password:` / `passwd=` 值、`PIN: 1234`。OTP 规则要求匹配的值至少包含一个数字,因此像 "expired" 或 "invalid" 这样的常见单词不会触发它。可以通过配置文件随时添加新规则。 - **入站 prompt 注入标记。** 邮件正文、日历事件备注、语音备忘录标题、云盘文件内容——任何不是由用户自己编写的内容——在返回时都会被包裹在 `` 标签中,以便 agent 将其视为数据而非指令。当检测到已知的注入模式(如“忽略之前的指令”、角色扮演前缀等)时,该包裹器会升级为强烈的警告横幅。 - **破坏性操作的批准门控。** `mail.send`、`drive.write`、`calendar.delete_event`、`reminders.delete_reminder`——将它们的 ACL 设置为 `approve`,每次调用都会弹出一个 macOS 对话框,在执行前显示即将发生的操作(收件人、正文预览、文件路径、事件标题)。每个 token 的 `interactive_approval = "never"` 允许受信任的远程 token 跳过对话框(审计日志记录为 `approved_by_policy` 以供取证)。 - **具有范围配置文件的多 token 认证。** 不同的 agent 获得不同的密钥和不同的能力。审计日志显示 `caller: "bearer:triage"` 而不是 `bearer:default`。 - **Tailnet 监听器**(选择加入)。当在配置中启用 `[tailscale] enabled = true` 时,daemon 也会绑定到 tailnet IPv4 地址。Peer ACL 委派给 tailscaled——在 Tailscale 管理控制台中设置它们,而不是在这里。每个请求都会运行 `tailscale whois`,因此 tailnet 调用的审计行会记录 `transport=tailnet caller=ts:laptop:user@github`。Bearer 认证在顶层依然适用。 - **批量邮件操作。** `mail.move_message`、`mail.mark_read`、`mail.mark_unread` 接受单个 `id` 或一个 `ids: [string]` 数组(最多 500 个)。无论 N 有多大,批处理路径都只是一次 osascript 调用——一次 Mail.app 激活,一条审计记录,一个批准对话框。 - **只追加的审计日志**,具有可配置的保留期(默认 30 天)。每次调用都会记录调用者、传输方式、工具、参数键、决定、延迟、字节数、错误信息。 - **默认仅限本地回环。** Tailscale 绑定是通过配置选择加入的;不会在公共网络接口上监听任何内容。 - **针对每个工具的工具列表过滤。** Agent 只能看到其 token 有权调用的工具。被拒绝的工具不会出现在 `tools/list` 中,因此不会将上下文浪费在它们无法使用的功能上。 - **Developer ID 代码签名 + 公证。** TCC 授权在重新构建后依然保留;Gatekeeper 在首次打开时即接受发布产物。无需在每次全新执行 `swift build` 时重新授权。 - **带验证的自动更新。** 菜单栏应用使用 Sparkle(EdDSA 签名的 appcast)实现“检查更新…”;无头 daemon 提供了一个 `deckard self-update` 子命令,在替换二进制文件并 `kickstart` LaunchAgent 之前,会验证 SHA-256 + codesign + Developer ID team + Gatekeeper 评估。 它不会做以下事情: - 它不会在 agent 的内容离开你的网络之前对其进行验证——那是 agent 运行时的职责。 - 它不能阻止用户将 token 错误配置为“允许一切”。它记录了相关危险;但它无法读懂你的心思。 - 它不承诺在有人能够物理接触到机器的情况下保证安全。任何能够读取 `~/Library/Application Support/Deckard/tokens.toml` 的人都拥有所有的 bearer token。 ## 安装 已在 macOS 14+ (Sonoma) 和 macOS 26 (Tahoe) 上测试。Apple Silicon。 ### 公开测试版 — DMG(推荐) 从 [Releases 页面](https://github.com/lapidakis/Deckard/releases) 获取最新的 DMG。将 `Deckard.app` 拖入 `/Applications`,双击,菜单栏图标即会出现。首次启动会打开一个 6 步引导窗口(欢迎 → Daemon → Token → 权限 → 连接 → 完成),引导你完成 token 创建,通过深层链接显示所需的 TCC 授权,并提供一个可复制粘贴的 `claude mcp add` 代码片段。 引导完成后: - Daemon 监听地址为 `http://127.0.0.1:8787/mcp` - 审计日志位于 `~/Library/Logs/Deckard/audit.jsonl` - 默认 token 位于 `~/Library/Application Support/Deckard/tokens.toml` - 登录时自动重启 发布产物已进行代码签名和公证——Gatekeeper 在首次打开时即会接受。如果你愿意,可以在运行前校验 DMG 的 SHA-256 附带文件。 当 daemon 运行时,书本图标会变为绿色,停止时则仅显示轮廓。点击可查看状态;点击“Open Settings…”可打开多标签设置窗口。随时可以通过“设置 → 状态 → Show Onboarding…”重新打开引导流程。 ### 无头模式 — Homebrew 适用于放在架子上的 Mac Mini 或任何服务器风格的部署。Homebrew tap 会拉取与 DMG 相同的已公证发布 tarball;brew 负责处理版本固定和更新。 ``` brew tap lapidakis/deckard brew install deckard deckard config init deckard auth add default --profile trusted deckard install # writes the LaunchAgent and bootstraps the daemon ``` 更新:`brew upgrade deckard`。`deckard self-update` 会在由 Homebrew 管理的二进制文件上拒绝执行——brew 拥有元数据,自我更新会导致元数据失效。 ### 无头模式 — 直接 tarball 如果无法使用 brew(离线安装、锁定的服务器),请直接获取 tarball 产物: ``` TAG="v1.0.0-beta.3" # latest tag from the Releases page curl -L -o deckard.tar.gz \ "https://github.com/lapidakis/Deckard/releases/download/${TAG}/deckard-${TAG}-arm64.tar.gz" curl -L -o deckard.tar.gz.sha256 \ "https://github.com/lapidakis/Deckard/releases/download/${TAG}/deckard-${TAG}-arm64.tar.gz.sha256" # 在提取之前验证 checksum(sidecar 为 ` `)。 shasum -a 256 -c deckard.tar.gz.sha256 tar -xzf deckard.tar.gz sudo mv deckard /usr/local/bin/ deckard config init # writes config.toml with defaults deckard auth add default --profile trusted # mints a bearer token deckard install # registers + bootstraps LaunchAgent ``` `deckard install` 会写入 `~/Library/LaunchAgents/com.lapidakis.deckard.plist` 并在 `gui/` 下引导它,以便 daemon 在登录时启动并在崩溃时重新生成。首次调用各项功能(Mail / 日历 / 提醒事项 / Apple Events)会触发 TCC 提示——每个功能点击一次“允许”;由于二进制文件使用稳定的 Developer ID 签名,这些授权在重新构建后依然有效。 获取用于你的 MCP 客户端的 bearer token: ``` deckard auth show default ``` 卸载: ``` deckard uninstall # bootouts + removes the LaunchAgent sudo rm /usr/local/bin/deckard # or `brew uninstall deckard` if you used the tap rm -rf ~/Library/Application\ Support/Deckard ~/Library/Logs/Deckard ``` ### 从源代码构建 适用于开发人员、贡献者,或者任何拥有 Developer ID 且倾向于自签名构建的用户: ``` git clone https://github.com/lapidakis/Deckard.git cd Deckard make build # auto-detects your Developer ID; falls back to adhoc .build/debug/deckard config init .build/debug/deckard install # registers LaunchAgent, starts the daemon make ui # builds the menubar app bundle ``` 代码签名脚本(`scripts/codesign.sh`)按以下顺序解析签名身份:`$DECKARD_SIGN_IDENTITY` → 你的钥匙串中检测到的第一个 `Developer ID Application:` → 带有警告的临时签名。临时签名构建可以运行,但 TCC 授权在重新构建后不会保留——每次执行 `make build` 都会重新提示 Mail/日历/提醒事项权限。 CI 会在每次推送时运行 `swift test`(参见 `.github/workflows/ci.yml`)。破坏 schema 验证器或任何其他测试的 PR 都会被阻止合并。 ## 在 Claude Code 中使用 ``` TOKEN=$(.build/debug/deckard auth show default) claude mcp add --transport http deckard http://127.0.0.1:8787/mcp \ --header "Authorization: Bearer $TOKEN" ``` 在任何 Claude Code 会话中通过 `/mcp` 进行验证——应该会显示 `deckard ✓ connected` 以及默认 token 的 ACL 允许的任意数量的工具。 ## 文档 - [架构](docs/architecture.md) — 模块、数据流、设计原则 - [安全模型](docs/security-model.md) — 威胁模型、分层防御 - [配置](docs/configuration.md) — `config.toml` 和 `tokens.toml` 参考 - [运维](docs/operations.md) — 安装、更新(包括 `deckard self-update` 和 Sparkle 一次性设置)、审计、故障排除 - [语音备忘录冒烟测试](docs/testing/voice-memos-smoke.md) — 端到端测试脚本示例 ## 包含哪些功能 (43 个工具, v1.0.0-beta.3) **内置** - `health.ping` — 存活探针;有效载荷极小,非常有用的诊断工具 **Mail (阶段 1)** — 通过 NSAppleScript 子进程操作 Mail.app - `mail.list_mailboxes`、`mail.list_messages`、`mail.search` - `mail.get_message` - `mail.create_draft`(安全——在 Mail.app 中打开供用户确认)、`mail.send`(需经过批准门控) - `mail.mark_read`、`mail.mark_unread`、`mail.move_message` — 每个都接受单个 `id` 或 `ids: [string]`(最多 500 个),返回 `BatchResult { matched, missing, failed, elapsed_ms }` **日历 (阶段 2)** — 原生 EventKit - `calendar.list_calendars`、`calendar.list_events`、`calendar.search_events` - `calendar.get_event`、`calendar.now` - `calendar.create_event`、`calendar.update_event`、`calendar.delete_event`(均需经过批准门控) **云盘 (阶段 3)** — 带有遍历守卫的文件系统 - `drive.list`、`drive.stat`、`drive.read`、`drive.search`、`drive.usage` - `drive.materialize`(强制下载 `.icloud` 占位符) - `drive.write`(需经过批准门控;配置中可选沙箱前缀) **语音备忘录 (阶段 4)** — 只读 - `voice_memo.list_recordings`、`voice_memo.get_recording` - `voice_memo.read_audio`(base64 `.m4a`,硬性上限 25 MiB) **提醒事项 (阶段 4.5)** — EventKit `.reminder` 实体 - `reminders.list_lists`、`reminders.list_reminders`、`reminders.get_reminder` - `reminders.create_reminder`、`reminders.update_reminder`、`reminders.complete_reminder`、`reminders.delete_reminder` **通讯录 (阶段 4.6)** — Contacts 框架(`CNContactStore`) - `contacts.search`、`contacts.get`、`contacts.list_groups`、`contacts.list_in_group` - `contacts.create`、`contacts.update`、`contacts.delete`、`contacts.set_groups`(均需经过批准门控) 各工具的详细信息请见 [`docs/configuration.md`](docs/configuration.md)。 设计原则 **默认拒绝。** 每个工具的初始状态均为 `deny`。ACL 会单独开启各项功能。不存在你可能忘记关闭的“允许一切”模式。 **信任边界在桥梁处,而不在 agent。** agent 可能是恶意的、被攻破的,或者是被输入了 prompt 注入的内容。桥梁的职责是在到达触及 iCloud 的代码之前,使这每一层恶意输入都变得安全,并在输出到达模型之前使其变得安全。 **一份只追加的审计日志。** 每一次调用,每一个决定,都带有保留期。如果发生了某些事情,它就会被记录在日志中,无论哪个 agent 发起了调用。 **在已有原生框架的地方使用原生框架。** Calendar/提醒事项使用 EventKit,地址簿使用 Contacts 框架,Drive 使用 FileManager + brctl,语音备忘录和 Mail 自己的索引使用 sqlite3。只有在没有其他办法时才使用 AppleScript(Mail.app,没有公共框架)。 **拒绝花哨的抽象。** 六个服务模块,一种形状:`*Adapter`(与 macOS 通信)→ `*Tools`(MCP 处理程序)→ 注册到同一个调度管道。新的阶段无需触碰 `BridgeCore` 即可插入。 **操作文件,而不是网络 API(针对 UI)。** 菜单栏应用直接读取 `tokens.toml`、`config.toml` 和 `audit.jsonl`。同一台机器,同一个用户。无需维护控制协议。 ## 路线图 - iMessage(阶段 5)— 读取 `chat.db`,通过 AppleScript 发送,发送者允许列表 - 菜单栏 UI 中的 ACL 编辑器(当前为只读;修改需通过 CLI 进行) - 菜单栏 UI 中的 Token CRUD(创建已包含在引导流程中;轮换 / 撤销 / 设置配置文件仍仅限 CLI) - 从 daemon 到菜单栏 UI 的 XPC 通道,用于批准对话框——这将使 `.approve` 的结果可靠地提示远程 token,而无需回退到 `interactive_approval = "never"` - 通过 Apple Speech 框架进行语音备忘录转录(目前为 agent 端的 STT) - `SessionHolder.recreate()` 应在交换传输之前排空正在进行的请求——以此修复在陈旧会话自我修复路径中罕见的“Transport already started”竞态条件
标签:AI代理, AI安全, AppleScript, Chat Copilot, DLP, iCloud, JSONLines, MCP服务器, RAG安全, Red Canary, stdio, Streamlit, Swift, Tailnet, 个人云计算, 代理网关, 安全策略, 审计日志, 家庭实验室, 密钥脱敏, 提示词设计, 敏感数据过滤, 日历代理, 服务账户, 权限管理, 模型越狱, 访问控制, 邮件代理, 零信任, 默认拒绝