mcpField
G
gpg-mcp

gpg-mcp

本地服务
github.com安全24 天前更新

关于 gpg-mcp

gpg-mcp 是面向 AI Agent 的 GnuPG MCP 服务,用于签名、验证、加密、解密和密钥管理。它通过固定类型的操作、审批与密钥白名单限制风险,私钥留在 gpg-agent 中。

主要功能

  1. 01签名与签名验证
  2. 02加密与解密
  3. 03OpenPGP 密钥管理
  4. 04高风险操作需审批
  5. 05审计高风险操作

适用场景

  1. 01在不向 Agent 暴露私钥时签署内容
  2. 02仅用白名单密钥解密消息
  3. 03验证签名并检查策略可接受性

MCP 相关

向 MCP 客户端提供固定类型的 GnuPG 操作工具;私钥材料与口令不会通过 MCP 边界传递。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"emiliano-go-gpg": {
"command": "uvx",
"args": [
"gpg-mcp"
],
"env": {
"GPG_MCP_CONFIG": "<GPG_MCP_CONFIG>"
}
}
}
}
启动命令uvx gpg-mcp
需要配置的环境变量
GPG_MCP_CONFIGOptional path to the gpg-mcp config file.

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypigpg-mcp0.1.0

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

gpg-mcp

一个专注于安全的 GnuPG Model Context Protocol 服务。

gpg-mcp 允许 AI Agent 签名、验证、加密、解密和管理 OpenPGP 密钥,且绝不会接触私钥材料。私钥保留在 gpg-agent 中;口令只会传给 pinentry;高风险操作需要明确的人类批准;破坏性操作则要求输入指纹后缀。

**一句话概括威胁模型:**MCP 服务是策略边界,而不是操作系统沙箱。它可以防止 Agent 接触私钥,也可以防止 Agent 在不知情的情况下使用私钥。若要防范已通过同一操作系统用户取得 Shell 访问权限的 Agent,请使用单独的用户运行签名程序,或使用硬件令牌(参见更强的隔离)。


为什么需要它

大多数“GPG MCP”包装器都只是薄薄一层 subprocess Shell:任何带有 command: str 参数的工具,或任何能请求 --export-secret-keys 的 Agent,都能让整个设计失去意义。gpg-mcp 则提供经过精心取舍的固定类型接口:

  • **不支持执行任意命令。**没有 run_gpg(args) 工具。
  • **不支持导出或备份私钥。**无法调用 --export-secret-keys、--export-secret-subkeys 和 --export-secret-ssh-key。
  • **不接受口令参数。**不支持 --passphrase、--passphrase-fd、--passphrase-file、loopback pinentry,也不会通过环境变量或标准输入传递口令。
  • **不允许覆盖 --homedir。**密钥环由配置固定。
  • **不接受短密钥 ID。**操作必须使用完整的 40 位或 64 位十六进制指纹。
  • **不自动获取密钥。**验证时不会悄悄从网络获取密钥。
  • **不允许隐藏接收者。**解密前会检查密文,并拒绝未加密给允许使用的密钥的消息。
  • **提供响应防护。**如果 gpg 输出任何看起来像私钥材料的内容,调用就会失败,并丢弃输出。

安全模型

服务的保障
  1. 私钥绝不会跨越 MCP 边界。 列出秘密密钥时仅返回元数据。 签名和解密由 gpg-agent 执行。
  2. 服务永远不会接触口令。 强制使用 --pinentry-mode ask;pinentry 会直接与用户交互。服务无法从 argv、环境变量或 stdin 读取口令。
  3. 签名和解密仅使用允许列表中的密钥。 共享 ~/.gnupg 时,空允许列表会同时拒绝这两种操作。使用专用密钥环时,密钥环本身就是允许列表边界。
  4. 解密前会检查收件人。 解密前会使用 gpg --list-packets 确认密文是发给允许的密钥。默认拒绝隐藏收件人,即使同一条消息也有可见的允许收件人。
  5. 嵌入的签名无效时会中止解密。 如果 verify=true(默认值),且解密后的消息带有无效、过期或已吊销的签名,则不会返回明文。valid 是策略判定结果;加密学验证结果会单独报告。多个签名按每个 NEWSIG 序列聚合;在不支持 --proc-all-sigs 的 GnuPG 上,则按数据包聚合。只要有任意一个签名不符合策略,就会拒绝操作,与事件顺序无关。
  6. 授权采用默认拒绝的注册表机制。 每个高风险工具都会指定一项已注册操作。未知操作必须经过审批。复合操作(sign_and_encrypt,以及带有 sign_with 的 encrypt)会继承其所有组成操作的审批要求,因此仅对签名或仅对加密设置审批门槛都无法绕过。
  7. 导入前会验证吊销证书。 证书必须包含吊销签名,必须拒绝秘密密钥数据包,必须精确指向所请求的指纹,并且必须能在临时密钥环中确实吊销该密钥。apply=false 是真正的试运行,不会触及真实密钥环。
  8. 破坏性操作需要输入指定内容确认。 删除、吊销或应用吊销证书时,操作员必须输入指纹的最后 8 个字符。此要求由 destructive_confirmation 控制,且独立于 require_approval。
  9. 每项高风险操作都会记录审计日志。 日志记录操作名称、指纹、负载的 SHA-256、大小和决策,绝不记录明文、负载预览或口令。
  10. 不使用 shell。 命令使用 execve 风格的 argv 数组。不进行 shell 插值,也不会因用户字符串导致选项注入。
  11. 使用最精简的环境。 不会向 gpg 传递 LD_*、GPG_AGENT_INFO、GPG_TTY、SSH_AUTH_SOCK 等变量。只保留 pinentry 所需的显示和会话变量。
  12. 二进制文件和路径经过安全检查。 gpg、gpgconf 和 zenity 会解析为绝对路径,必须是常规文件,且不得允许组或其他用户写入。不安全的 zenity 会导致本地审批默认拒绝,而不是信任被替换的对话框。
  13. 资源使用有上限。 限制输入和输出大小,设置每项操作的超时;超时后会终止专用进程组;使用权限为 0700 的临时目录和权限为 0600 的文件。状态输出超出有效状态上限时会默认拒绝,而不会对部分内容进行解析。
  14. 通用导入器无法执行吊销。 import_public_key 会拒绝秘密密钥数据包和任何吊销签名,包括嵌入密钥材料中的吊销签名。吊销必须使用 import_revocation,该操作会验证目标并要求输入指定内容确认。
  15. 会检查实际用于解密的密钥。 解密后,DECRYPTION_KEY 状态必须指明一个允许的主密钥或子密钥;否则会丢弃明文。即使密文含有可见的允许收件人,也不能在不知情的情况下使用另一把秘密密钥解密。
  16. GnuPG 配置具有确定性。 执行器使用 --no-options 和 --pinentry-mode ask,因此恶意的 gpg.conf 无法启用 loopback pinentry 或更改其他与安全相关的默认设置。
  17. 检查文件系统路径时不会跟随符号链接。 配置路径通过目录文件描述符和 O_NOFOLLOW 逐级遍历,打开的文件会通过 fstat 验证,因此验证与使用之间不会发生竞态,也不会被路径替换重定向。密钥环路径保持未解析状态(不会解析 GNUPGHOME 的符号链接);serve、doctor 和 init 会验证其完整的祖先路径,并拒绝符号链接形式的密钥环或祖先目录。祖先目录必须由用户或 root 所有;除非设置了 sticky bit,否则不得允许组或其他用户写入。拒绝未知密钥。对现有审计文件执行 fstat;拒绝非常规文件或由其他用户所有的文件,并将组和其他用户的访问权限收紧为 0600。当 audit_required = true 时,审计失败会导致操作失败。init 使用 O_EXCL|O_NOFOLLOW 安全创建配置文件;如果 gpg-agent.conf 是符号链接,则会拒绝操作,而不会通过该链接写入或修改其目标文件权限。缺失的配置、密钥环和审计目录会相对于已验证的目录描述符逐级创建,因此在拒绝符号链接祖先目录之前,不会被其重定向或创建任何内容。审计路径也使用相同的目录描述符遍历方式,因此不安全的祖先目录无法重定向日志。
  18. 解密路由明确无歧义。 symmetric_decrypt 会拒绝同时带有公钥收件人的混合密文。decrypt 会拒绝仅使用对称加密的密文,并且除非 GnuPG 报告实际使用了允许列表中的公钥,否则不会返回明文;它绝不会回退到对称解密路由。因此,仅对 symmetric_decrypt 设置门槛的自定义策略无法通过 decrypt 绕过。对于混合输入,只容许 GnuPG 报告的、可恢复的对称口令失败,而且仅限于同时有公钥报告 DECRYPTION_KEY 和 DECRYPTION_OKAY 的情况;通用 ERROR/FAILURE 事件和无法解释的非零退出码都会被拒绝。
  19. 多重签名处理采用默认拒绝机制。 对于不支持 --proc-all-sigs 的 GnuPG 版本,分离签名会按数据包逐个处理。对于明文、内联和嵌入式签名,在支持时会使用 --proc-all-sigs。在较旧的 GnuPG 上,若消息中第一个签名无效,枚举可能会在第一个无效签名处停止,但仍会拒绝该消息;若所有签名均有效,则会枚举每个签名。
  20. 对称解密同样严格。 必须出现 DECRYPTION_OKAY;拒绝 DECRYPTION_FAILED、BADMDC、ERROR 和 FAILURE;无法解释的非零退出码会视为致命错误。使用 verify=false 时,仅当明确识别出嵌入式签名结果,才会容许非零退出码;绝不会忽略无关的 GnuPG 失败。
验证结果语义

verify(以及 decrypt 返回的内嵌签名结果)会区分四个概念:

字段含义
cryptographically_validgpg 输出了 VALIDSIG;签名的数学验证通过
policy_acceptable此外,还需有 GOODSIG,且签名未过期、密钥未过期、未被吊销,也不存在缺少公钥的情况
validpolicy_acceptable 的别名,为了兼容性而保留
identity_trusted签名者具有完全信任或终极信任;单独报告,不是接受签名的必要条件

只有当每个内嵌签名都符合 policy_acceptable 时,解密才会释放明文。数学上有效但已过期或已被吊销的签名绝不够用,坏签名也不能被后续的好签名掩盖。结果包含一个 signatures 列表,每个 NEWSIG 序列对应一项。

它不保证什么
  • 同一用户下的隔离。 任何以你的操作系统用户身份运行的进程都可以读取 ~/.gnupg、连接 gpg-agent 套接字,或编辑 ~/.config/gpg-mcp/config.toml。如果 Agent 可以访问 shell,MCP 策略就不是操作系统边界。请使用更强的隔离。
  • 恶意 MCP 客户端。 在 approval = "client" 模式下,客户端负责呈现审批对话框。恶意客户端可以伪造该对话框。请使用 approval = "local"(与客户端无关的 zenity),或单独的用户/硬件令牌。
  • 遭到入侵的 gpg 或 gpg-agent。 遭到入侵的系统二进制文件可以为所欲为。二进制文件检查可以减少 PATH 劫持风险,但无法防范供应链入侵。
  • 语义安全。 服务可以展示要签名的内容(哈希值和预览),但无法判断内容是否适合签名。这个判断应由人来做。
威胁表
威胁控制措施
Agent 请求获取私钥没有任何工具会返回私钥;响应防护会阻止意外输出
Agent 尝试使用 --export-secret-keys不会构造此选项;不执行原始命令
Agent 将选项作为 UID/指纹提供验证完整指纹;UID 拒绝以 - 开头或包含控制字符的内容;创建密钥时使用参数文件
Agent 悄悄签署恶意标签/发行版允许列表 + 审批,展示密钥、大小、SHA-256 和预览
Agent 解密被盗的密文允许列表 + 审批 + 接收方预检
Agent 将机密加密给攻击者的密钥审批中列出命名接收方 + 可选的 allowed_recipients
Agent 导入私钥检测并拒绝私钥数据包
Agent 删除密钥destructiveHint + 手动输入指纹后缀
Agent 撤销密钥destructiveHint + 手动输入指纹后缀
Agent 提交其他密钥的撤销证书在临时密钥环中验证证书,并将其绑定到请求的指纹
Agent 请求执行撤销试运行以修改状态apply=false 只会在临时密钥环中验证
Agent 利用只针对某个复合操作组成部分设置门控的自定义策略复合操作会继承所有组成操作的审批要求
Agent 从审计账本读取载荷审批预览仅显示在界面中;审计只存储哈希/元数据
Agent 在可见接收方中隐藏一个接收方除非明确启用,否则会拒绝任何隐藏接收方
Agent 替换为伪造的本地审批对话框对 zenity 执行与 gpg 相同的可信二进制检查
Agent 从有问题的签名中读取明文有问题/已过期/已撤销的嵌入式签名不符合策略要求,并会中止操作
Agent 用后续的有效签名掩盖有问题的签名按 NEWSIG 汇总;只要有任何签名不符合要求,就会安全地失败
Agent 未经审批获取密钥以修改密钥环fetch_key 是已注册的写入操作,须经审批,并会报告导入的指纹
Agent 通过 UID 向审批对话框注入换行符或双向文本单行转义,并在审批前进行验证
预先存在的审计文件可被所有用户读取使用 fstat + fchmod 设置为 0600;拒绝非普通文件或非当前用户所有的文件
从 require_approval 中移除破坏性操作默认情况下,destructive_confirmation 仍要求手动输入后缀
通过通用导入器提交撤销证书在数据包层面拒绝撤销签名;请使用 import_revocation
解密时悄悄使用不在允许列表中的私钥检查 DECRYPTION_KEY 状态;不匹配时丢弃明文
巨大/不完整的状态文件被视为成功强制执行有效状态上限;状态不完整时安全地失败
混合消息绕过密钥允许列表进行对称解密symmetric_decrypt 拒绝公钥接收方;decrypt 要求报告一个位于允许列表中的公钥
仅含对称加密的输入使用限制较少的 decrypt 审批decrypt 在执行前拒绝仅含对称加密的密文
较旧的 GnuPG 在遇到第一个无效签名时停止逐一验证分离的数据包,并在汇总时安全失败
配置在验证与打开之间被替换使用目录文件描述符遍历路径 + O_NOFOLLOW + 对打开的文件描述符执行 fstat
路径祖先目录被其他用户替换使用目录文件描述符遍历祖先目录;拒绝非粘滞且可写的祖先目录
init 跟随被植入的 gpg-agent.conf 符号链接使用 lstat/O_NOFOLLOW,并检查文件类型和所有者
符号链接形式的密钥环祖先目录重定向 GNUPGHOME在服务启动/doctor/init 时验证未解析路径及其完整祖先链
多字节文本的分配量远超输入上限在接受输入前以有界数据块进行 UTF-8 编码
不可逆操作后记录结果审计失败记录意图和结果阶段;结果记录失败时会说明操作可能已完成
恶意 gpg.conf 改变执行行为使用 --no-options 和显式的 --pinentry-mode ask
组/所有者以外的用户可写配置改变策略配置完整性检查会拒绝不安全文件和未知密钥
审计写入悄悄失败audit_required = true 时操作会失败
压缩炸弹/输出过大限制流式读取;终止进程组
PATH 劫持使用绝对二进制路径,并检查所有权/权限模式
通过 argv/env/stdin 泄露密码短语仅使用 Pinentry;不存在密码短语处理代码路径
无人值守提示超时后终止进程组;无法绕过审批

架构

MCP client (agent)
   │  stdio JSON-RPC
   ▼
gpg-mcp (Python, mcp SDK v2)
   │  fixed argv, stdin/stdout, --status-file
   ▼
gpg 2.2+ (2.4 shown) ──► gpg-agent ──► pinentry (human passphrase)
   │
   ▼
GNUPGHOME (encrypted private keys + public keyring)

服务端从不打开私钥文件,而是请求 gpg 执行操作;只有在 pinentry 成功后,gpg-agent 才会解锁密钥。


安装

PyPI:gpg-mcp。MCP Registry:io.github.emiliano-go/gpg-mcp。

在客户端配置中固定版本,避免重启后服务端构建版本发生变化。

# run without a global install
uvx [email protected] serve

# or install a pinned CLI
uv tool install gpg-mcp==0.1.0
# or
pipx install gpg-mcp==0.1.0
# or
pip install gpg-mcp==0.1.0

首次运行。如果你没有安装命令行工具,请在这些命令前加上 uvx [email protected]:

gpg-mcp init
gpg-mcp doctor

无需全局安装的 MCP 客户端配置:

{
  "mcpServers": {
    "gpg": {
      "command": "uvx",
      "args": ["[email protected]", "serve"]
    }
  }
}

如果你使用 uv tool 或 pipx 安装:

{
  "mcpServers": {
    "gpg": {
      "command": "gpg-mcp",
      "args": ["serve"]
    }
  }
}

要求:Python 3.11+、GnuPG 2.2+(推荐 2.4)、pinentry;使用 approval = "local" 时还需要 zenity。支持 Linux 和其他类 Unix 系统;不支持 Windows。

可在以下地址搜索 Registry 条目: https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.emiliano-go/gpg-mcp。


快速开始(从源代码安装)

要求:Python 3.11+、GnuPG 2.2+(推荐 2.4)以及 pinentry 程序。

git clone https://github.com/emiliano-go/gpg-mcp.git
cd gpg-mcp
uv sync
uv run gpg-mcp init

init 会在 ~/.local/share/gpg-mcp/gnupg 中创建一个隔离的密钥环(模式为 0700),并在 ~/.config/gpg-mcp/config.toml 中创建配置文件(模式为 0600)。它会写入一份保守的 gpg-agent.conf:

default-cache-ttl 60
max-cache-ttl 300
ignore-cache-for-signing
no-allow-external-cache
no-allow-mark-trusted
pinentry-timeout 120

ignore-cache-for-signing 表示每次签名都会再次请求 pinentry。只有在了解其中的权衡后,才将其移除。

检查安装情况:

uv run gpg-mcp doctor

将其添加到 MCP 客户端(以下为 JSON mcpServers 映射示例):

{
  "mcpServers": {
    "gpg": {
      "command": "gpg-mcp",
      "args": ["serve", "--config", "/home/you/.config/gpg-mcp/config.toml"]
    }
  }
}

然后,在 Agent 中使用 create_key 创建密钥(在 pinentry 中输入密码短语),再使用返回的指纹调用 sign、encrypt 或 decrypt。

**客户端超时:**审批加上 pinentry 可能需要比客户端默认 MCP 工具超时时间更长的时间。请将超时设置为至少 120 秒;生成密钥和更改密码短语时,300 秒是较为合适的默认值。对于 opencode:

"experimental": { "mcp_timeout": 300000 }

如果客户端在对话框打开时超时,请取消对话框。服务端不会自动批准,而是会拒绝该操作。

专用密钥环初始为空。gpg-mcp 不会复制或导出现有的私钥。若要复用现有密钥,请自行通过带外方式迁移,或继续使用 ~/.gnupg,并显式配置 allowed_sign_keys/allowed_decrypt_keys 允许列表。


配置

配置路径:~/.config/gpg-mcp/config.toml,也可使用 --config 或 GPG_MCP_CONFIG。

键默认值含义
gnupg_home~/.local/share/gpg-mcp/gnupg隔离的密钥环。权限必须为 0700。
approvalclientclient(MCP 请求确认)、local(zenity;需要显示环境)、none(仅用于测试)
require_approval所有高风险操作必须经过批准的操作
destructive_confirmationtrue对破坏性操作输入指纹后缀进行确认,独立于 require_approval
allow_test_modefalse在服务运行时使用 approval = "none" 的必要条件
audit_requiredtrue如果无法写入审计账本,则拒绝高风险操作
allowed_sign_keys[]允许用于签名的完整指纹。为空时,专用密钥环中允许任意密钥;~/.gnupg 中不允许任何密钥
allowed_decrypt_keys[]解密规则相同
allowed_recipients[]加密时可选的收件人允许列表
enable_networkfalse搜索/获取/发送至密钥服务器
enable_symmetricfalse对称加密/解密
allow_unprotected_keysfalse允许创建 protect=false 的密钥(仅用于测试密钥环)
allow_hidden_recipientsfalse允许解密隐藏收件人的消息
max_input_bytes10485760输入上限
max_output_bytes20971520输出上限;状态文件上限为 max(1 MiB, this value)
timeout_s120每项操作的超时时间
keygen_timeout_s300密钥生成/口令变更的超时时间
keyserverhkps://keys.openpgp.org仅可通过配置指定;只允许 hkps:// 或 https://
audit_log~/.local/share/gpg-mcp/audit.jsonl仅追加的 JSONL 文件,权限为 0600

环境变量覆盖项:GPG_MCP_CONFIG、GPG_MCP_HOME、GPG_MCP_APPROVAL、 GPG_MCP_ENABLE_NETWORK、GPG_MCP_ENABLE_SYMMETRIC、GPG_MCP_AUDIT、 GPG_MCP_ALLOW_TEST_MODE、GPG_MCP_AUDIT_REQUIRED。


工具

只读:

工具用途
list_keys列出公钥或密钥元数据(绝不包含密钥材料)
get_key获取单个指纹对应的完整公钥元数据
export_public_key导出 ASCII 装甲格式/二进制公钥
export_ssh_key导出用于身份验证子密钥的 OpenSSH 公钥
inspect检查 OpenPGP 数据包/密钥而不导入;拒绝秘密密钥
inspect_recipients显示密文的接收者而不解密
verify验证分离式、明文或内嵌签名
card_statusOpenPGP 智能卡状态
agent_infogpg 版本、套接字和 Agent 缓存选项
doctor二进制文件、密钥环和策略健康检查

加密:

工具用途
sign分离式、明文或内嵌签名
encrypt加密给完整接收者指纹
sign_and_encrypt签名并加密
decrypt通过允许列表中的密钥解密公钥或混合加密密文;仅对称加密的输入必须使用 symmetric_decrypt
symmetric_encrypt / symmetric_decrypt使用口令进行对称加密(需选择启用)

密钥管理:

工具用途
create_key创建密钥;通过 pinentry 输入口令
add_subkey、add_uid、set_expiry修改密钥
change_passphrase通过 pinentry 输入旧/新口令
import_public_key导入公钥材料;拒绝秘密数据包和撤销签名
delete_key删除公钥或秘密密钥与公钥;需输入指定确认内容
set_ownertrustnever/marginal/full;ultimate 仅可通过 CLI 设置
generate_revocation验证并返回已存储的撤销证书
import_revocation在临时密钥环中验证;apply=false 可执行真正的试运行
revoke_key验证并应用已存储的证书;需输入指定确认内容

网络(需选择启用):

工具用途
search_keyserver搜索已配置的密钥服务器
fetch_key获取并导入公钥;需审批,并报告已导入的指纹
send_key发布公钥
刻意不提供
  • export_secret_key/秘密密钥备份。
  • run_gpg/原始 argv 透传。
  • 任何口令参数。
  • 工具中的 --homedir。
  • 每次调用时指定任意密钥服务器 URL。
  • --edit-key 交互式菜单。
  • 来自用户输入的 --trust-model。

审批模式

client(默认)

服务会要求 MCP 客户端显示审批表单。表单包含操作、完整密钥指纹和身份信息、载荷大小、SHA-256 以及简短预览。该功能通过 MCP resolver 实现,因此同时适用于旧版和当前协议版本。

如果客户端未声明支持 elicitation(例如某些 CLI 客户端),服务会回退到本地 zenity 对话框。这仍然需要人工确认,且独立于 MCP 客户端。如果 elicitation 和安全的本地对话框都不可用,调用会以 approval_unavailable 错误安全失败;绝不会自动批准。

如果组合操作中的任一组成操作需要审批,则该组合操作也需要审批。sign_and_encrypt 继承 sign 和 encrypt 的要求;encrypt(..., sign_with=...) 也会继承 sign 的要求。因此,自定义设置 require_approval = ["sign"] 仍会对带签名加密进行审批,require_approval = ["encrypt"] 仍会对 sign_and_encrypt 进行审批。

当 destructive_confirmation = true(默认值)时,破坏性操作必须输入指定的指纹后缀,即使该操作已从 require_approval 中移除也一样。将其设为 false 只会取消输入指定内容的确认要求;如果该操作仍在 require_approval 中,仍需审批。对于一次性测试密钥环,approval = "none" 会绕过这两项要求。

对于撤销导入,审批表单会显示从证书本身推导出的身份信息,而不只是作为参数传入的指纹。如果证书对应的是其他密钥,则会在请求审批前拒绝导入。

local

服务器使用 zenity(如果没有可用的显示器,则默认拒绝)。此设置独立于 MCP 客户端;如果你不完全信任客户端,这是更好的选择。破坏性操作会使用第二个 zenity --entry 提示来输入指纹后缀。

none

仅用于一次性测试密钥环。除非已配置 allow_test_mode = true(或 GPG_MCP_ALLOW_TEST_MODE=1)并且配置了专用的 GNUPGHOME,否则 serve 会拒绝此模式。审计账本会记录 decision: skipped。切勿对真实密钥启用此模式。


密钥环隔离

gpg-mcp init 会使用专用密钥环。这样你就能:

  • 将 gpg-agent 缓存与日常 GnuPG 使用分开;
  • 设置策略边界,让空的允许列表意味着“仅限此密钥环”;
  • 为测试密钥和 Agent 专用密钥提供安全位置。

专用密钥环无法防范同一 OS 用户。有关这一点,请参见下文。


更强的隔离

单独的 OS 用户(软件)

使用拥有密钥环的另一个用户运行签名器,并仅向 MCP 服务开放范围有限的 Unix 套接字。Agent 用户无法读取私钥,也无法连接到 Agent 套接字。这是最强的软件隔离设计,也是自然的下一步;当前版本为求简便,仍采用同一用户模式。

硬件令牌(适用于高价值密钥)

将私钥存放在 OpenPGP 智能卡或 YubiKey 上。密钥不会存在于磁盘上,每次签名都可以要求输入 PIN 并进行实体触碰。gpg-mcp 无需更改即可使用:card_status 会报告卡及其密钥,list_keys 会返回私钥元数据。在卡上生成密钥需通过 gpg --edit-card 在带外完成。

systemd 加固

如果将服务器作为服务运行,至少要:

NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=%h/.local/share/gpg-mcp
RestrictAddressFamilies=AF_UNIX

并在单独用户设计中以专用用户身份运行。


审计账本

每次高风险操作都会向 audit.jsonl 追加一个 JSON 对象:

{"ts":"2026-10-24T20:00:00Z","event":"operation","phase":"outcome","tool":"sign",
 "fingerprint":"F759...E793","mode":"detached",
 "payload_sha256":"...","size":42,"result":"ok"}

账本不包含明文、负载预览、口令或任何私钥材料。审批摘要会呈现给用户,但绝不会写入账本。文件采用仅所有者可读写的 0600 权限,以追加方式写入。每次写入时,都会对现有审计文件执行 fstat:拒绝非普通文件和由其他用户拥有的文件,并使用 fchmod 将已有的组/其他用户可访问文件收紧至 0600。gpg-mcp 仅会收紧其自行创建的审计目录权限;绝不会对 /tmp 这类现有的任意父目录执行 chmod。

启用 audit_required = true(默认值)时,如果无法写入审计条目,高风险操作就会失败;仅对一次性测试密钥环设置 audit_required = false。记录包含 phase:执行前写入 approval/intent,执行后写入 outcome。如果追加结果记录失败,错误会明确说明操作可能已经完成;不会重试操作。账本无法防范同一 OS 用户篡改。


测试

uv run pytest            # never prompts; uses an ephemeral keyring
uv run ruff check .

# Optional: the two operations that intentionally need a human at pinentry
GPG_MCP_INTERACTIVE=1 uv run pytest tests/test_interactive.py -s

默认测试套件会在临时密钥环中使用 pinentry-program /bin/false,因此任何意外提示都会导致测试失败,而不是阻塞测试。它会创建一个临时密钥环并运行真实的 gpg 操作。测试会验证以下内容:

  • 没有响应包含 PRIVATE KEY BLOCK;
  • 没有命令行包含被禁止的选项或口令;
  • 拒绝导入私钥和导入私钥撤销证书;
  • 默认策略要求对 import_revocation、revoke_key 和 delete_key 进行审批并输入确认文本;
  • 复合操作会继承其组成操作的审批要求;
  • 撤销预演不会修改真实密钥环,且会拒绝针对其他密钥的证书;
  • 过期签名、过期密钥、已撤销密钥和缺少公钥的情况,策略一律不予接受;
  • 多个嵌入式签名无论顺序是好是坏,都会失败关闭;
  • fetch_key 会注册为写操作并要求审批;
  • 审批对话框会转义多行/双向文本 UID,并在审批前进行验证;
  • 已有的 0644 审计文件会收紧为 0600,同时拒绝符号链接和 FIFO;
  • 即使从 require_approval 中移除该操作,破坏性确认仍是必需的;
  • 无法通过 import_public_key 使用撤销证书撤销密钥;
  • 实际的 DECRYPTION_KEY 必须列入允许列表;
  • 超出配置上限的状态输出会失败关闭;
  • 忽略恶意的 gpg.conf;
  • decrypt 会拒绝仅使用对称加密的输入,而混合输入可通过允许列表中的公钥成功解密;
  • 只对 symmetric_decrypt 设置门控的自定义策略无法通过 decrypt 绕过;
  • 会安全地遍历配置目录的所有祖先目录,并拒绝非粘滞且可写的祖先目录;
  • init 会拒绝符号链接形式的 gpg-agent.conf,且悬空配置符号链接无法创建其目标;
  • 审计路径会拒绝非粘滞且可写的祖先目录;
  • 会正确枚举并汇总多签名明文、内联和嵌入式消息;
  • 混合解密只容忍可恢复的对称口令失败模式;
  • 对称解密会拒绝无法解释的非零退出码;仅当 verify=false 时,才会忽略已识别的签名失败;
  • 创建配置、密钥环和审计目录时,无法通过符号链接祖先目录写入;
  • 在 serve/doctor/init 阶段会拒绝符号链接密钥环路径和可写的密钥环祖先目录;
  • 无效的安全遍历路径不会泄露目录文件描述符;
  • 无论是否使用 --proc-all-sigs,都会枚举多个签名并在失败时关闭;
  • symmetric_decrypt 会拒绝混合对称/公钥密文;
  • 会处理审计短写、意图/结果阶段以及结果失败;
  • 会拒绝悬空配置符号链接和不安全的配置父目录;
  • 超大分配前,多字节 UTF-8 内容会先触及字节数上限;
  • 会拒绝不安全的配置文件、未知配置键和隐式的 approval="none";
  • audit_required=true 时,不安全的审计文件会触发错误;
  • 会验证密钥生成参数组合并对部分导入进行分类;
  • 工具注册表与每个已公开工具的读/写注解相符;
  • 解密会拒绝发给其他密钥的密文以及混合隐藏/可见收件人的密文;
  • 审计账本不包含载荷预览,也不会更改现有目录的权限模式;
  • 会拒绝不安全的 zenity/二进制文件权限;
  • 选项注入尝试会在验证阶段失败。

CI 会在安装 GnuPG 的 Python 3.11、3.12 和 3.13 环境中运行 ruff 和 pytest(.github/workflows/ci.yml)。 main 分支受保护,必须通过全部三个 CI 任务;直接推送会被拒绝。


限制

  • 文本和 base64 输入有大小上限;大型文件应通过带外方式处理。
  • 密钥服务器的搜索输出是供人阅读的文本,而非结构化列表。
  • 撤销操作使用创建密钥时生成的证书。如果证书缺失,请通过带外方式使用 gpg --gen-revoke 生成。目标密钥必须已存在于配置的密钥环中;未知密钥的撤销证书会被拒绝。import_revocation 处理的是完整主密钥撤销,不处理选择性子密钥或 UID 撤销。
  • 智能卡密钥生成和 PIN 管理不在本项目范围内。
  • approval = "client" 依赖客户端如实呈现征询表单。如果客户端无法发起征询,则会回退到经过验证的本地 zenity 对话框。
  • 仅支持 Linux/Unix:此实现依赖 Unix 权限、os.getuid、POSIX 进程组和 zenity;不支持 Windows。
  • generate_revocation 会向调用方返回可用于撤销的证书。请将结果视为敏感信息并离线保存。审批提示会说明相关后果。
  • 在不支持 --proc-all-sigs 的 GnuPG 版本中,明文、内联和嵌入式签名枚举可能会在遇到第一个无效签名时停止;但仍会拒绝该结果。分离签名使用逐数据包回退处理。

许可证

MIT。请参阅 LICENSE。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。