Skip to content

abg claude: 继承的 ANTHROPIC_API_KEY 静默绕过订阅计费 / inherited ANTHROPIC_API_KEY silently bypasses subscription billing #242

Description

@PetarVukovic

问题 / The problem

abg 的入口是 #!/usr/bin/env bun,而 Bun 会自动把当前目录的 .env 加载进 process.env文档,无需 import、无需配置、无法 opt out)。runClaude()src/cli/claude.ts:114env: process.env spawn 子进程 —— 这本身是完全正常的透传,但此时的 process.env 已经不是用户 shell 的环境了

结果:项目 .env 里(本来就应该存在的)ANTHROPIC_API_KEY 进入 Claude Code,覆盖用户的 claude.ai 登录,静默切换到按量计费。Claude Code 确实会打印一行提示,但开头是 "claude.ai connectors are disabled",而不是"你现在在按 token 付费",并且立刻滚屏消失。

abg's entrypoint is #!/usr/bin/env bun, and Bun auto-loads ./.env from the working directory into process.env (docs) — no import, no configuration, no opt-out. runClaude() spawns the child with env: process.env (src/cli/claude.ts:114), an honest pass-through of an environment that is no longer the shell's.

Claude Code then finds ANTHROPIC_API_KEY set, prefers it over the claude.ai login, and bills every token to the metered API. It does print a warning, but it leads with "claude.ai connectors are disabled" rather than "you are now paying per token", on one line that immediately scrolls away.

Nobody wrote this bug. It emerges between a runtime convenience (Bun loading .env for your program) and a launcher pattern (passing process.env to a different program that reads credentials from the environment). Neither half is wrong on its own, and no code review of either component reveals it — the string ANTHROPIC_API_KEY does not appear anywhere in this repository, on any branch, in its entire history.

Notably, src/env-guard.ts already exists precisely because leaked/stale AGENTBRIDGE_* values in the environment cause confusing behaviour. This is the same class of problem, with money attached.

复现 / Reproduction

cd <any project with ANTHROPIC_API_KEY in .env>

env | grep -c ANTHROPIC                                  # 0  — not in the shell
bun -e 'console.log(!!process.env.ANTHROPIC_API_KEY)'    # true — but Bun loaded it

abg claude
# → Claude Code now bills to the API, not the subscription.
# → Confirm with /status inside the session, or: ps eww -p <pid> | grep ANTHROPIC

The variable never exists in the user's shell, so env | grep finds nothing, config files show nothing, and agent-bridge's own source shows nothing. It is effectively undiscoverable.

Found the expensive way: an unattended bridged pair ran for 2.5 hours on claude-opus-5, resending a ~400k-token conversation roughly every 20 seconds, entirely on API billing. ≈ $15 before it was noticed.

This is not an exotic setup — any LangChain / LangGraph / Anthropic SDK project keeps its key in .env, and that is exactly the kind of project people pair Claude and Codex on.

建议修复 / Suggested fix (warning only, no behaviour change)

最小的修复是只加警告、不改变任何行为 —— 默认剥离这个 key 会破坏那些刻意使用 API 计费的用户。

The minimal fix is to warn only and change nothing else — stripping the key by default would break users who deliberately run Claude Code on API billing. Concretely:

  • apiKeyBillingWarning(env) in src/cli/claude.ts — pure + exported, in the same style as mapChildExitCode ("Pure + exported so the mapping is unit-testable without spawning a process"), so the text is unit-testable.
  • Called in runClaude() on stderr with the [agentbridge] prefix, right after the max-permission notice and before spawn. Fail-open, never fatal, no prompt — matching every other guard in that function.
  • Detects ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN. Logs the variable name only, never the value.
  • Explains the Bun .env autoload, since that is the non-obvious part, and suggests env -u ANTHROPIC_API_KEY abg claude.
  • Silenced with AGENTBRIDGE_NO_API_KEY_WARNING=1.
  • Testable in src/unit-test/cli.test.ts without spawning a process, per the existing helpers there.

src/cli/resume.ts delegates to runClaude(), so no second call site is needed. src/cli/codex.ts is untouched — Codex does not read ANTHROPIC_API_KEY.

Sketch:

[agentbridge] ⚠️  ANTHROPIC_API_KEY is set — Claude Code will authenticate with it instead of your
[agentbridge]    claude.ai subscription, and every token in this pair is billed to the API.
[agentbridge]    abg runs on Bun, which auto-loads ./.env from the current directory, so this can be
[agentbridge]    set even when your shell has it unset.
[agentbridge]    Intended? Ignore this. Otherwise: env -u ANTHROPIC_API_KEY abg claude
[agentbridge]    Silence with AGENTBRIDGE_NO_API_KEY_WARNING=1

后续可考虑 / Possible follow-ups (deliberately not in that PR)

  • Strip ANTHROPIC_API_KEY from the child env behind an opt-in flag.
  • Disable Bun's .env autoload in the launcher. Note: a bunfig.toml with env = false would not help installed users — bunfig resolves relative to the invocation directory, not the package. It would need --no-env-file in the shebang/launcher, or explicit env construction.
  • abg doctor already reports an env check via inspectAgentBridgeEnv — it could also report which auth source the pair will actually use.

上面的方案仅供参考,具体实现方式由维护者决定。
The sketch above is only a suggestion — the actual shape of the fix is the maintainers' call.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions