在开发机上跑起opencode serve之后,很多人第一件事是掏出手机或另一台电脑,用浏览器打开http://<内网IP>:4096,想看看 Agent 改到哪一步了。能打开,能操作,感觉很爽。但这里有个默认行为值得警惕:OPENCODE_SERVER_PASSWORD为空时,这个 HTTP 服务不做认证,同一网段里任何人访问这个地址,都能看到你的会话列表、文件树,甚至切到 Build 模式直接驱动 Agent 改代码、跑 shell。这不是配置疏漏,是开箱默认状态。要堵住这个口子,同时又不让模型调用凭据散落到十几个配置文件里,可以在 TaoToken 官网 统一取 Key,再把 Base URL 设为https://taotoken.net/api填进模型 provider。下面按"服务密码"和"模型 Key"两条线拆开讲,给出可直接复制的命令、验证方法和存放位置对照。
1. OpenCode 的客户端-服务器架构,决定了 Web 端是一道真实的门
OpenCode 和普通的 IDE 插件不一样,它的核心设计是客户端-服务器:后端常驻本机,终端 TUI、桌面端、浏览器 Web、IDE 插件都只是接入这个服务的客户端。这个设计的收益很直接,代码不用离开本机,一套服务可以多终端同时接入会话,你甚至可以在外面用手机接回家里的开发机继续推进任务。
代价也在这个架构上。既然服务是一个 HTTP 端口,那它就具备了一切网络服务的攻击面:
- 谁监听了这个端口,谁就拥有这个服务;
- 端口绑到
0.0.0.0时,局域网内任何设备都能连; - 没有认证时,"连上"等于"完全控制",因为 OpenCode 的 Build 模式本身就有文件写入和命令执行权限。
把这两件事叠起来看就很清楚了:一个默认无密码的、能执行 shell 的 HTTP 服务,暴露在共享办公网、咖啡厅 Wi-Fi、宿舍路由器下面,风险等级和把 SSH 开着无密码登录差不多。
所以加固思路应该分两层,而不是只做一层:
- 接入层:用
OPENCODE_SERVER_PASSWORD给 HTTP 服务加一道 Basic Auth,解决"谁能连进来"。 - 凭据层:把模型 API Key 从明文配置里挪到环境变量,并统一从一个可控的入口获取,解决"连进来之后能用什么、Key 泄露了怎么办"。
这两层经常被混为一谈。下面先把边界讲清楚。
2. 两把钥匙不要混:服务密码与模型 API Key 的边界
OPENCODE_SERVER_PASSWORD和模型 API Key 是两套完全独立的认证体系,作用点不同,泄露后果也不同。
第一把钥匙:OPENCODE_SERVER_PASSWORD
它保护的是 OpenCode 本地 HTTP 服务的入口。设置之后,浏览器、IDE 插件、远程客户端访问服务时需要通过 Basic Auth 校验,默认用户名是opencode,密码就是你设置的值。它不参与任何模型调用,只决定"谁能操作这个本地服务"。
第二把钥匙:模型 Provider 的 API Key
它保护的是模型额度。OpenCode 作为调用方,拿着这个 Key 去模型供应商那边请求推理。它决定"这次对话要花谁的钱、走谁的通道"。
两者为什么必须分开管?因为泄露路径不一样:
- 服务密码泄露,攻击者可以操作你本机的仓库和 shell,这是最严重的一档;
- 模型 Key 泄露,攻击者可以消耗你的额度,甚至通过 prompt 反推你的业务内容;
- 如果 Key 明文写在
opencode.json里,那么任何能连上 Web 端的人,都有可能通过界面或配置读到它,两道门就变成了一道。
把 Key 放在环境变量里、并且只在一个地方集中管理,是更稳妥的做法。模型调用这一侧可以直接用 TaoToken 作为统一入口:到 TaoToken 官网 注册后创建 Key,Base URL 固定填https://taotoken.net/api,OpenCode、Claude Code、Codex 走同一个出口,轮换 Key 时只改一处。
| 凭据 | 保护对象 | 建议存放位置 | 是否进 Git |
|---|---|---|---|
OPENCODE_SERVER_PASSWORD | 本地 OpenCode HTTP 服务 | shell profile、systemd EnvironmentFile(权限 600) | 否 |
| 模型 API Key | 模型调用额度 | 系统环境变量 / 密钥管理工具 | 否 |
opencode.json | 行为策略(provider、权限) | ~/.config/opencode/或项目根目录 | 全局配置可提交,含密钥的不可提交 |
.env | 本地临时变量 | 项目根目录,且被 OpenCode 默认拒绝读取 | 否 |
3. 设置 OPENCODE_SERVER_PASSWORD 的三条落地路径
先给一个最小可用的命令组合,确认能跑通再谈持久化。
# 1) 生成一个足够强的密码,不要手敲 openssl rand -base64 24 # 2) 设置服务密码与用户名(用户名可省略,默认 opencode) export OPENCODE_SERVER_USERNAME='opencode' export OPENCODE_SERVER_PASSWORD='把上一步生成的字符串粘到这里' # 3) 启动服务。要远程访问才绑 0.0.0.0,只本机用就绑 127.0.0.1 opencode serve --hostname 0.0.0.0 --port 4096这里有个容易忽略的取舍:如果你只在本机用 TUI,绑127.0.0.1就够了,甚至可以完全不设密码,因为外部网络根本连不进来。但只要你打算用手机、平板或第二台机器接入,就必须绑0.0.0.0,而一旦绑了,OPENCODE_SERVER_PASSWORD就不是可选项。
路径一:shell profile 持久化(适合个人开发机)
把变量写进~/.zshrc或~/.bashrc,注意文件权限,不要让同机器其他用户读到。
# 追加到 ~/.zshrc echo "export OPENCODE_SERVER_USERNAME='opencode'" >> ~/.zshrc echo "export OPENCODE_SERVER_PASSWORD='你的密码'" >> ~/.zshrc chmod 600 ~/.zshrc source ~/.zshrc路径二:systemd 用户服务(适合常驻后台)
常驻服务用 systemd 管,密码不要直接写在 unit 文件里,用EnvironmentFile单独放,权限压到 600。
# ~/.config/systemd/user/opencode-server.service [Unit] Description=OpenCode local server After=network.target [Service] Type=simple Environment="OPENCODE_SERVER_USERNAME=opencode" EnvironmentFile=%h/.config/opencode/server.env ExecStart=%h/.local/bin/opencode serve --hostname 0.0.0.0 --port 4096 Restart=on-failure [Install] WantedBy=default.target# ~/.config/opencode/server.env,只写一行 OPENCODE_SERVER_PASSWORD=你的密码 chmod 600 ~/.config/opencode/server.env systemctl --user daemon-reload systemctl --user enable --now opencode-server用这种方式的好处是:改密码不用动 service 文件,重启服务即生效,也不会有密码残留在history或进程列表里。
路径三:Windows 用户级环境变量(适合 PowerShell 用户)
Windows 下curl | bash那套不适用,环境变量用下面两句设置,设置完需要重开终端。
[Environment]::SetEnvironmentVariable("OPENCODE_SERVER_USERNAME","opencode","User") [Environment]::SetEnvironmentVariable("OPENCODE_SERVER_PASSWORD","你的密码","User")三条路径选一条即可,不要同时设置,否则排查时很难判断进程到底读到了哪个值。
4. 访问验证:怎么确认密码真的生效了
设置环境变量和"认证生效"之间隔着一整条链路,必须实测。推荐按下面的顺序验证。
第一步,本机不带凭据请求,应该被拒绝:
curl -i -m 5 http://127.0.0.1:4096/期望看到类似HTTP/1.1 401 Unauthorized和WWW-Authenticate: Basic的响应头。如果直接返回 200 或正常页面内容,说明密码没生效。
第二步,带凭据请求,应该能通过:
curl -i -m 5 -u "opencode:你的密码" http://127.0.0.1:4096/第三步,从另一台机器验证边界:
# 把 IP 换成开发机内网地址 curl -i -m 5 http://192.168.1.20:4096/这一步要确认两件事:一是能连通但返回 401;二是如果连不通(连接超时),说明你绑的是127.0.0.1或者被防火墙拦了,这其实是更安全的默认状态,只是远程接入需要额外放行。
第四步,浏览器侧确认:
浏览器访问http://192.168.1.20:4096,应弹出 Basic Auth 输入框。如果没弹而直接进入界面,回到第一步重新检查。
密码设置了却不生效,通常是这几个原因:
- 先启动服务、后
export的,进程读到的还是旧环境,重启服务即可; - 在 A 终端设置,在 B 终端启动,两个 shell 环境不互通;
- systemd 的
EnvironmentFile路径写错或权限不对,服务静默用了空密码; - 前面挂了反向代理,把
Authorization头吃掉了; - 用了
opencode之外的启动方式(比如某个 IDE 插件自己拉起进程),没有继承你的环境变量。
排查这类问题,最直接的方式是打印进程环境确认:
# 先查进程 PID pgrep -af opencode # Linux 下确认环境变量是否被继承 tr '\0' '\n' < /proc/<PID>/environ | grep OPENCODE_SERVER看到密码字段是空值,就说明问题在启动环节,不在 OpenCode 本身。
5. 模型这一侧:把 Provider 指向 TaoToken 并让 Key 走环境变量
服务门锁上了,接下来处理模型调用。OpenCode 的 provider 配置写在opencode.json里,全局路径通常是~/.config/opencode/opencode.json,项目级就是仓库根目录。推荐用全局配置,把 Key 通过环境变量注入,而不是写死在 JSON 里。
先取 Key。到 TaoToken 官网 完成注册并创建 API Key,然后在开发机上把它设成环境变量:
# 写入 shell profile,避免每次手动 export echo "export TAOTOKEN_API_KEY='YOUR_API_KEY'" >> ~/.zshrc source ~/.zshrc接着配置 provider。OpenCode 通过 npm 包来加载兼容 OpenAI 协议的 provider,配置大致如下:
{ "$schema": "https://opencode.ai/config.json", "provider": { "taotoken": { "npm": "@ai-sdk/openai-compatible", "name": "TaoToken", "options": { "baseURL": "https://taotoken.net/api", "apiKey": "{env:TAOTOKEN_API_KEY}" }, "models": { "your-model-id": { "name": "Your Model" } } } }, "model": "taotoken/your-model-id" }这里有几个点必须注意:
第一,baseURL不要带 UTM 参数。它是工具调用的接口地址,固定写https://taotoken.net/api,后缀参数会污染请求路径。
第二,apiKey用{env:TAOTOKEN_API_KEY}这种引用写法。这样 Key 只存在于系统环境变量里,opencode.json可以放心提交到团队仓库,不会把密钥带出去。
第三,模型 ID 格式必须是provider/modelId。上面的taotoken/your-model-id需要替换成你在控制台模型列表里看到的真实 ID,照抄名字写错会直接报ProviderModelNotFoundError,这个报错基本都是在说"provider 前缀或者模型 ID 拼错了"。
第四,models里声明的 ID 要和最终model字段拼出来的后半段一致。有人只改了顶层model却忘了在models里补声明,结果就是模型列表为空。
改完配置后重启 OpenCode 服务,再在 TUI 里发一条消息,确认走的是 TaoToken 通道。如果报鉴权失败,先确认环境变量在当前 shell 里可见:
test -n "$TAOTOKEN_API_KEY" && echo "key loaded" || echo "key missing"6. 其他客户端的 Key 存放位置对照,别把变量名串了
同一个 TaoToken Key,在不同工具里的配置形态完全不同。最容易出的事就是把 Claude Code 的ANTHROPIC_*变量名照搬给 Codex,结果 Codex 完全不认,还会以为是 Key 失效。
Claude Code:settings.json里走env字段
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY" } }Codex:config.toml里声明 provider
model_provider = "taotoken" model = "your-model-id" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "responses"注意 Codex 走的是model_providers段落加env_key的机制,它读的是TAOTOKEN_API_KEY这个自定义变量,不是ANTHROPIC_*。两套体系的字段名、段落结构都不一样,互相套用必然失败。
CC Switch:三件套对应关系
在 CC Switch 里切换供应商时,本质上是填三个值:Base URL、API Key、模型名。对应到 TaoToken 就是:
| 字段 | 填写内容 |
|---|---|
| Base URL | https://taotoken.net/api |
| API Key | 控制台创建的 Key,即YOUR_API_KEY |
| 模型 | 控制台模型列表里的真实 ID |
把这张表和前面的opencode.json、settings.json、config.toml放在一起看,规律很清晰:接入地址和凭据是常量,配置语法是变量。换工具时改语法,不要改地址和 Key 的语义。
7. 加固清单:把服务密码、权限沙盒和 Key 管理一次性配齐
前面讲的是"要做的事",这一节把顺序和检查项固化下来,适合照着逐条核对。
服务侧:
OPENCODE_SERVER_PASSWORD必须非空,密码用openssl rand -base64 24生成,不要用生日和项目名;- 只在需要远程接入时绑
0.0.0.0,否则优先127.0.0.1; - 密码存放文件权限设 600,不进 Git,不进聊天记录;
- 修改密码后必须重启服务,并用第 4 节的
curl命令复验。
权限侧:
OpenCode 的权限沙盒配置写在opencode.json里,可以对文件编辑、shell 执行、外部抓取分别设定策略。默认保守一点,需要时再逐条放开:
{ "permission": { "edit": "ask", "bash": { "*": "ask", "git status": "allow", "git diff": "allow" }, "webfetch": "ask" } }要理解一点:Plan 模式只是"默认只读",不是安全屏障。手动改权限就能放开写操作,所以真正的边界在配置和密码上,不在模式名上。
Key 侧:
.env和.env.*默认被拒绝读取,这是保护机制,不要为了图方便去改;- 其他敏感文件(比如生产配置、证书私钥)要在 permission 里显式
deny; - API Key 只放环境变量,配置文件中一律用
{env:...}引用; - Key 需要轮换时,改一处环境变量并重启服务,比翻十几个配置文件可靠得多。
流程侧:
- 服务密码和模型 Key 分开轮换,互不影响,出问题时也更容易定位是哪一层失效;
- 团队共享开发机时,每个人用自己的系统账号和服务实例,不要共用同一个
OPENCODE_SERVER_PASSWORD。
8. 写在最后
OpenCode 的价值在于把模型选择权、运行环境选择权、代码数据控制权交回开发者手里,但"本地可控"这四个字不是自动成立的,它取决于你有没有把默认开放的那道门关上。OPENCODE_SERVER_PASSWORD是服务入口的门锁,模型 API Key 的存放方式是凭据管理的门锁,两把锁各自独立,缺任何一个都不算完整加固。
实际落地时,整套动作可以压缩成四步:生成强密码并注入服务环境、绑好监听地址、用curl复验 401、把模型 provider 的 Base URL 指向https://taotoken.net/api并让 Key 走环境变量。做完这四步,Web 端接入才算既方便又不裸奔。
需要动手时,可以从下面几个入口按顺序走:先用模型对话确认模型通道可用,再看 Coding Plan 了解适合持续编码场景的方案,接着到创建 API Key拿到YOUR_API_KEY,最后参照 Claude Code 文档里的配置格式,把同一套接入方式迁移到其他编码工具上,做到一处取 Key、多端复用。