跳转至

安全

v1.6.0 新增

  • 沙箱的 API key: 在 Odoo 14.0 至 19.0 上,scripts/deploy_local.py 会让 Odoo 为沙箱的 admin 创建一个 API key,并把它保存在沙箱的 .env 中,从不打印。参见 本地基础设施。
  • 副本检查与选项检查(--check-copies 和 --check-options)不运行任何程序、不联系任何地方,并且无论输入中有什么,都从不打印敏感值。参见 凭据。
  • 已知限制 10 已解决,只剩一种情况: 在 Odoo 10 至 12 上,与模型某个字段同名的上下文键会被当作值来读取。参见 权限策略。

v1.5.1 新增

  • 一个不读取任何插件选项或凭据的会话开始 hook: hooks/session_start.py 取代了 hooks/check_options.py。参见 凭据。
  • 服务器只读取插件选项传给它的凭据: 它不再读取 ODOO_HTTP_BEARER。
  • 本地诊断 只含元数据,并按允许清单检查。参见 隐私。
  • 插件目录的检查报告了什么,以及在哪里核实每一项发现。
  • 策略新增一项已知限制:在 Odoo 10 至 12 上按位置传递的上下文(参见 权限策略)。

v1.5.0 新增

  • 在 Odoo 用户的权限之上增加 权限策略,默认配置档为 standard。参见下文的 权限策略。
  • 本地 审计日志,以哈希链接,不含任何值或密钥。
  • 按完整身份区分会话: 会话键包含一个按进程计算的密钥 HMAC,基于请求头的多租户路径已被移除。
  • odoo_connections 不再返回 URL 中的 user:pass@。
  • 选项 hook 也会检查两个策略选项。

凭据

  • 作为插件 时,凭据是 插件选项(/plugin → odoo-tools → Configure options)。API key 和密码是 sensitive 选项:存储在操作系统的安全凭据存储中,绝不会写入 settings.json 或你的 shell 配置文件,并且只传给本地的 MCP 服务器进程,该进程只把它们发送到你配置的 Odoo URL。对于插件,不要在 shell 中导出 ODOO_*。
  • 插件有两个 SessionStart hook,它们都不联系任何服务:
    • 第一个运行 python3 --version,并且仅在 python3 无法运行时(MCP 服务器将无法启动)打印一条警告;
    • 第二个是 hooks/session_start.py(v1.5.1 新增,取代 hooks/check_options.py),只在 python3 为 3.9 或更高版本时启动,并且不读取任何插件选项或凭据。它只读取 CLAUDE_PLUGIN_DATA 和 CLAUDE_CODE_ENTRYPOINT(启动会话的 Claude 应用的名称)、事件的来源和会话 id、服务器诊断文件(diagnostics/server.jsonl 及其轮换后的 server.jsonl.1)的末尾、容器模式的标记是否存在、它自己的每日时间戳以及插件的版本:不读取插件文件夹之外的任何内容。当最近 14 天既没有记录到与 Odoo 的成功连接,也没有记录到带齐全部连接设置的服务器启动时,它会说明 Odoo 工具在哪里运行(不在 Cowork 或 Claude chat 中),在会话开始时每天最多提示一次,并告诉 Claude,同时附上一行供 doctor 使用的诊断信息(安装或升级插件后的第一次会话,以及没有数据文件夹的地方,只告诉 Claude);当上一次服务器启动被权限策略锁定时,它会连同 E 代码说明这一点。它为每个会话在 diagnostics/hook.jsonl 中写一行元数据(从不通过符号链接写入),为每日提示保存两个时间戳文件,并且从不阻止会话(总是以 0 退出)。使用 /odoo-tools:deploy-local 的容器模式时,它什么也不说。
  • Claude Code 会把插件的每个已保存选项(包括敏感选项)以 CLAUDE_PLUGIN_OPTION_* 的形式导出到插件每个 hook 进程的环境中。本插件的 hooks 不读取其中任何一个(由 server/tests/test_product_rules.py 检查)。
  • 副本检查与选项检查(run_stdio.py --check-copies 和 --check-options,引导路线和 /odoo-tools:odoo-doctor 会在 Claude Code 中运行它们)从标准输入读取一条 Claude Code 命令打印的 JSON(claude plugin list --json 或 claude plugin configure <id> --json):它们不运行任何程序、不打开 Claude Code 的任何文件、不联系任何地方,并且无论输入中有什么,都从不打印敏感值。对于 Odoo URL,选项检查只复述协议、主机和端口,从不复述路径、查询或片段(它们可能带有门户链接的令牌),并会把带有任何 @ 的 URL 视为带有用户名或密码而拒绝,且不显示它的任何部分。两项检查都读取 UTF-8,从 Windows 的管道中读取时也是如此。参见 两个 odoo-tools 副本。
  • 在独立模式下(Docker、python -m odoo_mcp),服务器从 环境 中读取凭据(ODOO_URL、ODOO_DB、ODOO_LOGIN、ODOO_API_KEY/ODOO_PASSWORD)。服务器从不从客户端请求中获取 Odoo 的 URL 或密钥:基于请求头的多租户路径已在 v1.5.0 中移除,ODOO_MULTITENANT 仍会被读取,但保留不用。服务器不读取任何其他凭据:它过去从环境中读取但从未使用的 ODOO_HTTP_BEARER 已在 v1.5.1 中移除。
  • MCP 服务器 不会将密钥持久化到磁盘,也不会将其写入日志。会话由 url|db|login 加上一个按进程计算的密钥 HMAC 来标识,因此相同的 URL、数据库和登录名配上另一个密钥,永远不会复用已认证的会话;密钥本身从不成为任何键或日志行的一部分。
  • Settings 对象会 在 repr()/str() 中隐去凭据,以便一次意外的日志记录不会将其泄露(由 tests/test_security_redaction.py 保证)。
  • 优先使用 API keys(Odoo ≥ 14)而非密码。使用 最小权限 的 Odoo 用户,并在探索或演示时在插件选项中开启 Read-only mode。
  • 会处理哪些数据、会联系哪些服务:参见 隐私。

备用 CLI 确实会写入一个包含凭据的文件

与 MCP 服务器不同,备用 CLI(/odoo-tools:odoo-setup-cli)会把你的凭据保存在 ~/.claude/tools/odoo-cli 中的一个本地 .env 文件里。设置过程会将它创建为仅你的用户可读、并带有占位符:由你自己在自己的编辑器中粘贴密钥;Claude 绝不会在聊天中向你索取它。如果你使用该接口:请保持这些严格的权限(相当于 chmod 600),不要将它提交到版本控制,并在停止使用 CLI 时将其删除。请以对待任何其他存于磁盘的密钥的相同频率来轮换 API key。

为智能体使用专用用户

为智能体创建一个专门的 Odoo 用户(例如 ai-bot@yourcompany.com),只授予它所需的权限。从该用户处生成 API key,并定期轮换。这样,即使 key 被泄露,你也能限制其影响范围。

你公司的数据会怎样

有必要区分两条数据路径:

  1. 从 MCP 服务器到 Odoo(直接)。 服务器运行在你的基础设施上,并通过 JSON-2 / JSON-RPC / XML-RPC 直接与你的 Odoo 实例通信。Transgenia 不充当中间人:我们不做代理,不存储也不查看你的数据或凭据。
  2. 从 AI 智能体到模型提供方。 当你通过一个智能体(Claude Code 或其他 MCP 客户端)操作 Odoo 时,每个工具的 结果(你查询到的 Odoo 记录)会返回给客户端,并发送给模型提供方(例如在 Claude 的情况下是 Anthropic),以便模型对其进行处理。这是任何 AI 智能体流程所固有的;并非本插件额外引入的。

实际影响:由你决定 查询什么 以及 使用哪个 Odoo 用户(最小权限)。请查阅你的模型提供方的数据处理政策,并避免带出智能体并不需要的敏感字段。对于受监管的数据,只查询必不可少的内容,并考虑在源头进行匿名化。

供应链

  • 零运行时依赖。 从 v1.2.0 起,MCP 服务器只使用 Python 标准库(MCP 协议以及 JSON-2、JSON-RPC 和 XML-RPC 传输都基于它实现)。插件通过 python3 ${CLAUDE_PLUGIN_ROOT}/server/run_stdio.py 启动它:没有软件包启动器(uv、uvx、npx),没有安装步骤,启动时也不下载任何东西。实际运行的代码就是插件文件夹中随附的、可直接阅读的代码。
  • run_stdio.py 会把随附的源代码放在 sys.path 的最前面,因此机器上安装的其他 odoo_mcp 包无法替代经过审查的代码。
  • 可选附加项(cache、metrics、otel)只有在你自行安装时(独立模式)才会被使用;插件不会安装任何附加项。
  • 可选的备用 CLI 是唯一会安装软件包的部分:在你用 /odoo-tools:odoo-setup-cli 配置它时,会运行 npm install。

插件目录的检查报告了什么

Claude 插件目录为本插件列出了以下发现。下面是每一项(名称与目录中显示的英文一致),以及在哪里核实它:

  • Uses a credential from the user's machine(.claude-plugin/plugin.json、server/src/odoo_mcp/config.py)。MCP 服务器从环境中读取两个凭据,即 Odoo 的 API key 和 Odoo 的密码,这个环境由 .claude-plugin/plugin.json 根据 sensitive 选项 odoo_api_key 和 odoo_password 填充。它只把它们发送到你配置的 Odoo URL,也就是它们所属的服务:server/src/odoo_mcp/transport/ 中的三种传输方式只连接该 URL,并拒绝重定向。如果服务器从环境中读取任何其他凭据,server/tests/test_product_rules.py 就会失败。
  • scripts/deploy_local.py 为 /odoo-tools:deploy-local 的本地 Docker 沙箱生成随机密码(secrets.token_urlsafe),把它们保存在沙箱文件夹中一个仅你的用户可读的 .env 里,并且只用于该沙箱在 127.0.0.1 上的容器。它们不是你的凭据。在 Odoo 14.0 及更高版本上,它还会让沙箱的 Odoo 为其管理员用户创建一个 API key(在沙箱的一次性容器中运行 odoo shell,使用 Odoo 自己的 res.users.apikeys),检查这个 key 能否登录,并把它以 ODOO_API_KEY 保存在同一个 .env 中,从不打印。由你把它复制到插件的 Odoo API key 选项中;Claude 从不读取它。
  • 测试(server/tests/):使用伪造的金丝雀凭据,证明任何日志行、审计行、诊断记录、支持报告或 hook 输出都从不包含凭据。插件从不运行这些测试。
  • Uses hooks. 即 凭据 中所述的两个 SessionStart hook:hooks/session_start.py 只读取插件的数据文件夹和 CLAUDE_CODE_ENTRYPOINT,从不读取插件选项或凭据(由 server/tests/test_product_rules.py 检查),并且总是以 0 退出;另一个只运行 python3 --version。
  • Runs a local program for an MCP server. python3 ${CLAUDE_PLUGIN_ROOT}/server/run_stdio.py:只使用标准库,不安装也不下载任何东西(参见 供应链)。
  • Contains a download-and-run command. 只有两个命令会在你的电脑上下载并运行代码,而且只在你调用并确认时才会这样做:/odoo-tools:deploy-local 会拉取并运行 Docker 镜像(Transgenia 按版本系列标签提供的官方 PostgreSQL 和 Odoo 镜像的原样副本,以及与插件版本相同的 MCP 服务器镜像),/odoo-tools:odoo-setup-cli 会为可选的 CLI 运行 npm install。MCP 服务器和 hooks 不下载也不安装任何东西。

自动检查(CI)

.github/workflows/security.yml 流程会在每次 push/PR 时运行:

  • gitleaks:扫描历史记录和 diff,查找被提交的密钥。它通过 go install 在固定标签(v8.18.4,每个模块都经 Go 校验和数据库验证)上构建,不会用 curl 下载并运行二进制文件。
  • leak-guard:查找被禁止的内部/租户标记(客户数据、基础设施主机、税务标识符、硬编码的凭据),如果它们出现则让构建失败。该仓库是 vendor-neutral(供应商中立)的:只包含通用工具,外加公开的品牌/署名以及服务报价。组织特定的标记从仓库机密(secret)中读取,而不是写在流程文件里(插件文件夹就是仓库根目录,因此流程文件会随插件分发给每个安装者),并且对这些标记的匹配只按文件名报告。
  • .gitignore:会排除 .env、*.env、虚拟环境和构建输出,以免本地凭据被意外提交。

传输

  • 使用带 HTTPS 的 Odoo URL。JSON-2、JSON-RPC 和 XML-RPC 都运行在你所提供的 URL 上;http:// URL 会以不加密的方式发送。
  • JSON-2(Odoo saas~18.4 / 19.0+)在每次请求中以 Authorization: bearer 发送 API key,绝不会在重定向中发送(重定向会被拒绝),不发送 cookies,也绝不会在工具结果中显示 Odoo 服务器的堆栈跟踪(错误中的 debug 字段)。它只接受 API keys,因此使用密码的配置会停留在经典端点上。
  • 所有传输方式都会使用系统信任存储验证 TLS 证书(包括主机名)(遵循 SSL_CERT_FILE;如果安装了可选的 certifi 包,也会使用其 CA 列表)。没有关闭验证的选项。所有传输方式都应用 ODOO_TIMEOUT(默认 120 秒;插件选项 Request timeout)。
  • JSON-RPC 不跟随 HTTP 重定向(被重定向的 POST 会变成没有请求体的 GET);请配置你的 Odoo 的最终 URL。
  • Odoo 前面的基本认证: ODOO_URL 可以带有 user:pass@;JSON-RPC 会将其作为 Authorization 请求头发送,任何错误、日志或工具结果都不会复述该密码(自 v1.5.0 起,odoo_connections 会从它报告的 URL 中去掉 user:pass@)。JSON-2 需要同一个请求头来传递它的 API key,因此这样的 URL 会停留在经典端点上。Odoo URL 不是 sensitive 选项:该密码保存在插件配置中,而不是安全凭据存储中。
  • 服务器仅在 stderr 上记录日志;stdout 保留给 MCP 通道。

审计日志

自 v1.5.0 起,每次工具调用(包括被拒绝的调用)都会留下一行 JSON:工具、它触及的模型和操作名称(op:模型[:方法])、拒绝发生的层和规则、计数、耗时、错误的异常类名以及策略摘要。它从不保存参数值、域、字段值、id、文件内容、URL、令牌、密钥、密码或错误消息文本;没有通过策略名称检查的名称会写成 ?。Odoo 的数据库和登录名只出现在服务器启动时写入的 start 行中。

  • 插件把每日文件(odoo-tools-audit-YYYY-MM-DD.jsonl)写入它的数据文件夹(ODOO_AUDIT_DIR),只有你的用户可以访问(文件夹 0700,文件 0600,从不经过符号链接),并保留 90 天(ODOO_AUDIT_RETENTION_DAYS;0 表示一直保留)。
  • 没有文件夹时,日志行会写到 stderr 上的 odoo_mcp.audit 日志记录器;ODOO_AUDIT_DIR=off 不写入任何内容。
  • 每一行都带有上一行的哈希,python3 server/run_stdio.py --verify-audit 文件 会点名第一处被编辑、被调换顺序或被插入的行,或不完整的最后一行。仅凭一个文件,它无法发现从某天末尾删掉的整行,也无法发现被删除的某天文件(幸存的前缀仍然是连续的链)。在本地,这是证据而不是防范:能写入该文件夹的人就能改写这些文件。
  • 写入失败从不会中断工具调用。

详细说明和格式见 审计日志。

权限策略

在本地(stdio)模式下,权限策略限制的是 Claude 通过 odoo-tools 的 MCP 工具 能做什么。它防范的是错误,以及隐藏在 Claude 所读数据中的指令。它不限制持有 API key 的人,也不是一个沙箱,无法约束在同一台机器上拥有不受限 shell 访问权限的智能体。对所有人都有效的边界是 Odoo 用户本身:为智能体提供一个专用的、最小权限的 Odoo 用户(不是管理员),以及一个带过期时间的 API key。在未来的远程模式中,服务器会保管 Odoo 凭据,届时同一套策略将成为真正的权限强制。

  • 它是什么。 位于 Odoo 访问权限、记录规则和字段组之上的一个上限,由插件选项 Permission profile(readonly、默认的 standard、builder、full)和一个可选的 Permission policy file 设定;后者在启动时从一个显式的绝对路径读取一次(从不从默认位置读取)。它从不授予 Odoo 拒绝的权限,也从不弱于 Read-only mode。full 保留 1.4 的行为。参考:权限策略。
  • standard 关闭了什么。 已保存的密钥(系统参数、邮件服务器、LDAP 和支付服务商的凭据、IAP 和银行同步令牌、CFDI 证书和私钥、外发邮件队列)无法读取;用户、组、访问规则、模块、银行账户、联系人合并、chatter 历史和财务序列只读;发送邮件、短信、信件或分享链接的模型,以及外发方法(内部备注以外的 message_post、message_subscribe、send_mail 等)被关闭;服务器动作、自动化、视图和其他 ir.* 记录只读;删除被拒绝;类凭据字段会从读取结果中移除,并在参数中被拒绝。full 仍然拒绝创建、更改或撤销 API keys 以及卸载模块,并且为了执行这一点,也会拒绝策略无法检查其参数或关联模型的调用。
  • 两层。 每个工具声明的访问,会在会话存在之前根据其参数做出决定(A 层:没有任何内容到达 Odoo)。然后,工具发出的每个 Odoo 调用都要经过一个受守卫的会话(B 层),在发送之前连同其值和路径再检查一次(x2many 命令、域和导出路径、附件的父模型、用户的联系邮箱),并把工具没有声明的任何调用当作缺陷拦下。没有权限映射的工具无法被注册、列出或调用。
  • 无效的策略会锁定服务器:只有 odoo_version 可用,它会在不联系 Odoo 的情况下解释错误。
  • 是安全带,不是沙箱。 在 stdio 下,拥有不受限 shell 的智能体可以读取服务器的环境、编辑策略文件或插件选项,或者直接调用 Odoo。针对策略文件夹的 Claude Code permissions.deny 规则、Bash 的确认提示以及指令行(策略的拒绝是最终结果)会让这更困难,但并非不可能。当 Odoo 用户是管理员时,odoo_version 会发出警告,因为此时 Odoo 不做任何限制。
  • 已知限制(详见 已知限制):业务方法内部的副作用(一封确认邮件、已有的自动化)属于 Odoo;方法返回的字符串(带令牌的门户 URL)不会被隐去;ir.attachment 以外的指针模型只有在文件拒绝业务读取时才重要;名称无害的关联字段可能携带被屏蔽的数据;many2one 链接不作为触及项检查;已发布的内容在效果上是一条外发渠道;计数和聚合可以用来推断;在机密性方面 builder 等同于 full,因为自动化会在服务器上运行代码;在 Odoo 10 至 12 上,按位置传递给策略对照表之外的业务方法的对象中,与模型某个字段同名的键会被当作值来读取,因此它不受上下文允许清单的约束。

只读模式

  • 开启 Read-only mode(ODOO_READONLY=1)时,服务器不再列出非只读工具(odoo_create、odoo_write、odoo_unlink、odoo_execute、odoo_translate_set、两个 Studio 风格工具和两个导入工具:也包括 odoo_import_preview,因为它回滚的预演可能会消耗序列号并触发 webhooks),并且在按名称调用它们时,会在访问 Odoo 之前以清晰的错误拒绝它们。读取、导出、odoo_telemetry_preview 以及只读的 Online 工具仍然可用。只要它处于开启状态,任何配置档或策略文件都无法恢复写入。
  • 备用 CLI 没有对应的功能,也没有权限策略:它的 write 命令总会尝试执行调用。在那里请使用最小权限的 Odoo 用户。
  • odoo_online_profile 只按名称、范围和日期列出用户的 API keys:Odoo 不会把 key 本身保存在可读的字段中,插件也从不创建、轮换或撤销 key。odoo_record_documents 只有当文件是所请求记录的附件且大小不超过 max_bytes(最大 5 MiB)时才返回该文件。

本地基础设施(/odoo-tools:deploy-local)

  • 只有在你调用并确认后才会运行;没有任何 hook 会运行 Docker。
  • Odoo 仅发布在 127.0.0.1,不会暴露给你的网络。
  • 密码使用 Python 的 secrets 在本地生成,保存在 ~/.odoo-tools/sandbox/.env(0600,文件夹 0700),并且从不打印;Claude 不会读取该文件。
  • 在 Odoo 14.0 及更高版本上,Odoo 为沙箱的 admin 创建的 API key 以 ODOO_API_KEY 保存在同一个文件中,同样从不打印,即使在错误信息中也不会出现。由你把它复制到插件的 Odoo API key 选项中;切勿把它粘贴到聊天中。Odoo 在 Preferences > Account Security 中把它列为 "odoo-tools sandbox";如果你在那里删除它,下一次 sandbox up 会再创建一个。
  • 镜像来自 ghcr.io/transgenia(官方镜像的原样副本,每周更新),并可回退到 Docker Hub。
  • ghcr.io/transgenia/odoo-mcp-tools 镜像以非特权用户运行,通过 --env-file 接收凭据,从不出现在命令行中。

报告漏洞

请写信至 dev@transgenia.org,附上详情和复现步骤。请 不要 为安全报告开启公开 issue。我们力求在 5 个工作日内确认收悉。