Skip to content

fix: keep the route token off the Claude CLI command line - #6

Merged
EricWvi merged 4 commits into
ora-space:mainfrom
ObsisMc:fix/481-token-out-of-argv
Oct 22, 2026
Merged

EricWvi merged 4 commits into
ora-space:mainfrom
ObsisMc:fix/481-token-out-of-argv

Conversation

@ObsisMc

@ObsisMc ObsisMc commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

On a platform route the temporary token no longer appears on the Claude Code CLI's command line.

Cause. The route went through the adapter's providers/set. The pinned claude-agent-acp 0.94.0 builds one provider env (createEnvForProvider: the routing reset, ANTHROPIC_BASE_URL, ANTHROPIC_CUSTOM_HEADERS with x-api-key: <token>) and puts it both in the CLI's process environment and in the env of the programmatic settings. The Claude Agent SDK 0.3.303 serializes object settings into a --settings <json> argument, so the token was in /proc/<pid>/cmdline for anyone who can list processes.

Fix. The route is split by who may read it:

agent/start + modelContext
  → spawn the bundled adapter with
      ANTHROPIC_CUSTOM_HEADERS = x-api-key: <token>          ← only here
      ANTHROPIC_BASE_URL, placeholder ANTHROPIC_AUTH_TOKEN, every other upstream/credential reset
      the frozen model aliases (unchanged)
  → return AgentModelApplied
every session/new|load|resume|fork from Ora
  → ModelGuard sets _meta.claudeCode.options.settings =
      { apiKeyHelper: "", env: <routing, no token> }          ← becomes --settings
  • The flag settings outrank a repository's .claude/settings.json and user settings, so neither can point the token at another base URL or restore another credential. That protection was the reason for using providers/set, and it is kept.
  • A repository setting that replaces ANTHROPIC_CUSTOM_HEADERS can only drop the token; the request still goes to the gateway and is refused there.
  • providers/set is no longer sent. The adapter is on its route from spawn, so ClaudeClient.ready is running. The bridge's own request, its 10-second timeout and its route-refusal classification are gone. A gateway that is down now fails the first turn instead of agent/start, because the adapter no longer lists the gateway's models at start.
  • ModelGuard keeps refusing authenticate, logout, auth/* and providers/*, and keeps stripping the caller's model, env, extraArgs and executable options. A caller's settings is replaced with the route's.
  • The token now goes from this plugin to the host in the spawn request's env. The Node's child process handler (crates/plugin-lifecycle/src/childprocess.rs in ora-space/desktop) passes the env to the process and does not log it.

Tests.

  • tests/model_route_test.ts: the token is only in ANTHROPIC_CUSTOM_HEADERS of the spawn environment and never in the session settings. Every session setup carries the route's settings, with or without caller options. A start sends nothing to the adapter. An Effect restart spawns on the same route. deno task test: 45 passed.
  • tests/argv_test.ts (deno task test:argv, new argv CI job): runs the pinned adapter and CLI from npm at the versions in upstream.lock.json on a route. It fails if any process they start shows the token in its arguments. It also checks that the token is in the CLI's environment and that the base URL is in its --settings. Results on linux-arm64 with 0.94.0 / 0.3.303 / CLI 2.1.303:
    • The test passes on this branch.
    • A copy of this branch with the token added to the session settings fails with "the token is on the command line of …/claude".
  • I also ran the pinned pair by hand against a fake gateway:
    • A repository .claude/settings.json that sets another ANTHROPIC_BASE_URL gets no requests.
    • With the header also overridden, the gateway receives the repository's value, never the token.

Not covered here.

  • Reading the process table as another identity on a real Node. The token is now in the process environment, which the same identity can still read through /proc/<pid>/environ, so this check needs a different identity.
  • The same check for the OpenCode adapter.
  • agent/start now succeeds once the adapter is spawned, so an adapter that exits right away (for example a missing bundled CLI) only shows up at initialize.
  • tests/argv_test.ts drives session/new only; session/load, session/resume and session/fork are covered by the unit tests.

ADR

Implements part of decisions/node/agent-runtime/20261017-plugin-owned-model-adapters.md (D4: the temporary token stays in memory or the execution's own environment) for this plugin. The ADR stays approved: acceptance on a real Node and the OpenCode check are still open, and moving it to implemented is ora-space/circus#467.

The --agent-configuration release switch is not touched.

Refs ora-space/circus#481

The pinned claude-agent-acp merges a providers/set route's headers into
the env of the programmatic settings, which the Claude Agent SDK passes to
the CLI as a --settings argument, so the temporary token was readable by
anyone listing processes.

The token now reaches Claude Code only through the adapter's spawn
environment (ANTHROPIC_CUSTOM_HEADERS). The routing without it, with
apiKeyHelper cleared and every other upstream and credential source
reset, goes into the flag settings of every session setup, which still
outrank a repository's or user's settings. providers/set is no longer
sent, so a running adapter is already on its route.
Runs the claude-agent-acp and Claude Code CLI versions upstream.lock.json
pins on a platform route and fails if any process they start shows the
token in its arguments. It needs the real pair and Node, so it is its own
task (deno task test:argv) and CI job, which installs the pair from npm
at the pinned versions.

@wanglongan587 wanglongan587 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查版本:6f8fd2031f88ac1ef8c7d8a257316b8318842d7d(base 434872a,3 个 commit,9 个文件)。结论:Approve(无阻塞)。

我核对过的

  • 本地在 detached worktree(审查 head)上跑 deno task check、deno task lint、deno task test:全部通过,45 passed | 0 failed。把 9 个改动文件 LF 归一后在临时目录跑 deno fmt --check 通过(Windows 检出 CRLF 会误报);git diff --check 通过。
  • PR CI:check、argv 均 SUCCESS(run 39711150478,head 就是 6f8fd20)。argv job 日志明确显示真实跑了 pinned pair:the pinned adapter and CLI never put the token on a command line ... ok (646ms),1 passed | 0 failed,不是被 skip;job 按 upstream.lock.json 从 npm 装了 adapter 0.94.0 / SDK 0.3.303。
  • 按 pinned @agentclientprotocol/claude-agent-acp@0.94.0 的 dist 源码核对:
    • 没有 providers/set/authenticate 时,resolveProviderConfig() 为 null;session 路径 providerEnv = createEnvForProvider(null) = {},settings 就是本插件经 _meta.claudeCode.options.settings 给的 configuredSettingsObject,env 为 process.env + caller env + {}(acp-agent.js 8426-8453)。
    • createEnvForProvider 的 resetRouting 与本 PR 的 routingEnvironment 一致;占位 bearer 的实际值就是 acp-proxy(gateway-models.js)。
    • OPTION_REBUILDS_SESSION.settings: true,computeSessionFingerprint 覆盖 settings,session/load|resume 会按 settings 重建 query。
    • defaultProviderConfig() 只被 unstable_listProviders 使用,而 ModelGuard 拒绝 providers/*。
  • 跨仓库核对 desktop crates/plugin-lifecycle/src/childprocess.rs(desktop upstream/main df5da60):request env 经 ProcessSpec::env 添加、host env 之后应用(ProcessSpec 注释是 "Adds or overrides"),spawn 路径不记录 env;plugin-sdk 也写明 env 是 "Added to the environment the child inherits from the host; it is not a replacement." PR 描述里 host 不记录 env 的声明在 desktop main 上成立。
  • 路由与模型收紧没有被删:ModelGuard 仍拒绝 authenticate/logout/auth/*/providers/*,session/set_model/model config 只允许冻结模型,agent/list_models 在 route 下直接回答冻结模型;tests/model_route_test.ts 覆盖 token 只出现在 spawn env、每个 session setup 都带 settings(含 load/resume/fork)、Effect restart 用同一 route。
  • 文件列表全是交付物;3 个 commit 分别是 fix(含行为测试)、argv/CI、docs,拆分合理。ADR 依据 specs 0434c821 的 plugin-owned-model-adapters 仍为 approved;本 PR 只实现一部分、真实 Node 与 OpenCode 检查留给 #452、implemented 由 #467 承接,这个判断正确。

非阻塞(建议顺手改,不影响合并)

  1. 两处注释仍在描述旧的 route 接受语义:
    • src/handlers/lifecycle.ts:18-21:"It is confirmed only after the adapter accepted the route built from it, which is what the host waits for before it lets ACP begin"。现在 route 由 spawn env 应用,startClaude 在 spawn 后直接返回 applied,没有"adapter 接受"的握手;建议改成"spawn 成功即 route 已应用"。
    • src/handlers/effects.ts:229:"a route the adapter refused or never answered"。这个失败模式已随 providers/set 一起删除,现在 restart 的失败只有 spawn 失败;建议改掉,避免以后有人按旧语义加回请求/超时。
      这两处不在本 PR 改动文件里,但 PR 改变了它们描述的契约。
  2. agent/start 不再做 adapter 活体探测:现在 spawn 成功即返回 applied。gateway 不可用已在 PR 和运行 issue 写明;但 adapter 进程 spawn 后立即退出(例如 bundled CLI 缺失/不可执行,adapter-entry.mjs 抛错)也会先返回成功,到 ACP initialize 才暴露。#466 里"bundled CLI 缺失未归为 AGENT_UNUSABLE"一项与这个变化相关,建议在 PR 描述或 #466 明确承接/补测,避免以后把"start 成功"当成"adapter 可用"。
  3. argv 测试只覆盖 session/new:load|resume|fork 的 settings 注入目前只有 ScriptedAdapter 单测。pinned adapter 的指纹和 options 路径表明 settings 会生效,风险低,但真实 pair 没有这些路径的证据,可在后续验收或测试 harness 支持时补一条。
  4. PR 描述可以链到 #467:现在写 "a separate specs run",而 #481 正文已写明由 #467 承接;写上编号更便于 owner 核对 ADR 落地。
  5. 小项:test:argv 用 --allow-all,其余 task 都是最小权限;argv job 的 npm 安装没有校验 upstream.lock.json 的 integrity/sha256(package task 有),作为回归门禁可以接受,但可考虑复用同一份校验,避免测试和实际打包的版本来源不一致。

未验证

  • 真实 Node 上以另一身份读进程参数(#452 验收第 1 项):本次只证明同一身份下 CLI argv 不含 token、environ 含 token。该修复把 token 从 argv 移到 env 后,Linux 上同 UID 仍可读 /proc/<pid>/environ,所以真实 Node 验收必须在 Separate placement/不同身份下做;Shared placement 的 D4 问题仍由 #466 第 1 项承接。
  • OpenCode adapter 的同类检查(#452 验收第 3 项)。
  • 真实 prompt/网关请求、仓库 .claude/settings.json 覆盖:PR 描述里是手工 fake 网关验证,没有在 CI 自动化。
  • 真实 bundled 编译产物(Bun 单文件)没有被 argv 测试直接跑到;测试用的是 npm dist/index.js + 模拟 adapter-entry.mjs 的 CLAUDE_CODE_EXECUTABLE。
  • Windows/macOS 的 argv 可见性没有测试(测试按设计只在 Linux 跑 /proc)。

交付物 / ADR

9 个文件都是交付物;Refs ora-space/circus#481 正确。ADR 保持 approved 符合"只实现一部分"的要求;真实 Node 验收和 OpenCode 检查仍由 #452 承接,implemented 由 #467 的 specs 运行承接。parent #452 的 owner 结论(发布门禁,不是合并门禁)仍然成立:本 PR 合并不会打开发布开关。

结论:可以合并;上面的非阻塞项建议在合并前或后续 commit 处理,若改 head 请重新打 status:in-review。

agent/start no longer waits for the adapter to accept a route, and a
restart can no longer fail on one; the comments said both still could.

@wanglongan587 wanglongan587 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复审:Approve,head ad1b8ac0e80279c7ca686d93556282b1cac76c06

前次结论针对 head 6f8fd20(3 个功能/测试 commit)。本次增量只有 1 个 commit ad1b8ac,内容是两处注释,git diff 6f8fd20..ad1b8ac 为 2 文件、5+/4-,其余文件与 commit 未动。

增量核对

  • src/handlers/lifecycle.ts:18-22:改为"adapter 带着由冻结上下文构建的 route 环境变量 spawn 后即确认",与 startClaude 的实际顺序(modelRoute → useRoute → await client.start(cwd) → 返回 route.applied)一致;ready 现在就是 running,不再有 providers/set 的接受阶段。
  • src/handlers/effects.ts:229:restart 失败原因改为"host 无法 spawn adapter",与 ClaudeClient.start 唯一抛错点(#spawn 失败后 rethrow)一致;finally 里的 #release() 语义未变。

src/ 与 AGENTS.md 里已无"adapter 接受/拒绝 route"的旧语义;AGENTS.md 与 model-guard.ts 中出现的 providers/set 都是"不使用它"的正当说明。

验证(head ad1b8ac)

  • 本地独立 worktree:deno task check、deno task lint、deno task test 全过,45 passed | 0 failed;两个改动文件按 LF 归一后 deno fmt --check 通过(Windows 检出是 CRLF,本地 core.autocrlf=true 造成,不是 PR 内容),git diff --check 干净。
  • PR CI run 39714036149,head ad1b8ac:check 与 argv 均 SUCCESS。argv 日志确认真实执行了 pinned pair(the pinned adapter and CLI never put the token on a command line ... ok (703ms),1 passed | 0 failed),不是 skip。
  • 上游 main 仍是 434872a,PR MERGEABLE,无新冲突。

非阻塞项的去向

  • spawn 后立即退出(如 bundled CLI 缺失)要到 ACP initialize 才暴露:已记在 ora-space/circus#466(2026-10-22 评论),PR 描述 Not covered 也写明。
  • tests/argv_test.ts 只驱动 session/new:同上记入 #466,描述写明。
  • 两处过时注释:本 commit 已修。
  • PR 描述指向 ora-space/circus#467(ADR 转 implemented),并补了"令牌改在进程环境后,同一身份仍可读 /proc/<pid>/environ,真实 Node 验收必须换身份"。
  • specs decisions/node/agent-runtime/20261017-plugin-owned-model-adapters.md 在 specs upstream/main 0434c82 上仍是 status: approved,与本 PR 声明的"部分实现、留给 #467"一致。

前提与未验证项(同前次)

  • ora-space/circus#452 验收第 1 项(真实 Node 上换身份读进程参数)未做;第 3 项(OpenCode)未做。
  • 真实 bundled 编译产物、Windows/macOS 的 argv 未测;仓库 .claude/settings.json 覆盖只经过作者手工 fake 网关验证。

结论

增量正确,无阻塞意见。审过 head ad1b8ac0e80279c7ca686d93556282b1cac76c06,可以合并;合并后按 ora-space/circus#452、#467 收尾。

@EricWvi
EricWvi merged commit 8115695 into ora-space:main Oct 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants