☰
treg shell 模式深度解析:基于 PATH shim 的团队 CLI 透明凭据注入
2026/9/25 3:12:27 网站建设 项目流程
  • 后端
  • API网关
  • MCP 服务
  • dsh-plugin

【免费下载链接】treg

OpenRouter for agent tools. Join community here: https://discord.gg/6mQYYfFMAn

项目地址:https://gitcode.com/GitHub_Trending/treg/treg
点击查看免费下载

导读

treg shell(Shell mode)是 treg 项目提供的一种透明 CLI 拦截机制:打开一个子 shell 后,团队成员可以像本地安装一样直接运行团队注册过的 CLI(stripe、gh、neonctl等),无需键入任何密钥、也无需手动执行treg run。本文以 docs/context/interface/shell.md 为骨架,结合 src/treg/shell.py、src/treg/cli.py 及 tests/test_shell.py 源码级证据,完整讲解其 PATH shim 原理、shim 脚本契约、会话生命周期、本地/服务端双路由(--server-for)、--proxy拦截叠加,以及它为什么刻意停留在"treg run的薄封装"这一安全姿态。读完你将能理解并正确使用treg shell start/stop的全部参数,并清楚它与treg <command>、treg serve这两扇前门之间的关系。

核心机制:PATH 上的影子 CLI,而不是 shell 钩子

treg shell的拦截方式有一个关键设计决策:它不是 preexec/DEBUG trap 之类的 shell 钩子,而是在PATH上放置"影子 CLI"(shim)。

从源码看(src/treg/shell.py 中start_session):

  1. 在$XDG_RUNTIME_DIR或$TMPDIR下创建私有会话目录(session_base_dir()优先选择 Linux 的XDG_RUNTIME_DIR、macOS 的TMPDIR,两者都是 0700 级且不被全局列举;tempfile.mkdtemp(prefix="treg-shell-")创建后还会显式chmod 0o700);
  2. 为每个注册 CLI 在会话目录的bin/下写入一个 shim(见下节);
  3. 以该目录排在PATH首位的方式启动$SHELL。

这样,"这个命令是否注册过"的判定就交给 shell 自身的名字解析免费完成:输入stripe时最先命中我们的 shim,从而被路由进 treg;而ls、git没有对应 shim,正常解析到系统真实二进制。整个过程没有任何 shell 钩子参与。

shim 脚本契约:shim_script/write_shims

每个 shim 的实际内容(shim_script生成)形如:

#!/bin/sh # treg shell shim → tool stripe-tool (runs locally) # Generated by `treg shell start`; do not edit — it vanishes when the shell exits. exec env PATH="$TREG_SHELL_REALPATH" /path/to/treg run stripe-tool -- "$@"

这里有两个精妙之处,对应测试 tests/test_shell.py 中test_shim_execs_treg_run_with_clean_path_and_verbatim_args验证的契约:

  • 干净的$TREG_SHELL_REALPATH防止自递归:它是 shim 目录被前置之前捕获的原始 PATH。treg run内部用shutil.which(<bin>)解析真实二进制时,走的是这条干净 PATH,因此永远不会解析到 shim 自身;同时真实 CLI 得以正常运行。
  • 字面量--隔离参数:--将 treg 自身的参数解析与用户参数隔离开,_run_local随后剥离它,最终真实 CLI 收到的正是用户原样输入的内容(测试中验证了["run", "stripe-tool", "--", "balance", "--live"]的精确 argv 透传,退出码也原样返回)。

此外,shim 内嵌了一段cobra__complete*旁路:当第一个参数以__complete开头时(gh及大多数 Go CLI 的 shell 补全调用),直接exec真实二进制,不经过 treg(src/treg/shell.py 中shim_script的bypass分支):

case "$1" in __complete*) exec /opt/homebrew/bin/gh "$@" ;; esac

原因是补全属于本地 shell 元数据、不需要凭据;若强行路由进 treg,会逐键刷爆审计日志并消耗每日额度。测试 tests/test_shell.py 中test_completion_call_bypasses_treg_real_bin_runs通过真实 shell 验证:gh __complete sta只命中真实二进制,gh repo list才走treg run。

哪些 CLI 会被影子化:plan_shims

plan_shims(tools, server_for)(src/treg/shell.py)决定拦截清单:

  • 保留每个带有cli.bin且cli.enabled为真的工具——cli.enabled是拥有者的本地运行显式 opt-in,未启用的工具即使强行treg run也会 403;
  • 为每个 shim 分配路由local或server(见下文--server-for);
  • 返回排序后的(bin, tool_name, route)条目列表与警告列表;
  • 若两个工具争抢同一个 bin,先到者胜;
  • bin 不是普通文件名(含路径分隔符、.、..)则跳过——shim 是我们要写入的文件,绝不允许 bin 名逃出 shim 目录(src/treg/shell.py 中os.sep/os.altsep检查)。

对应测试test_plan_shims_filters_and_sorts覆盖了未启用、无 bin、无 cli 配置、路径逃逸、重复 bin 等全部过滤分支。

会话生命周期:环境变量、信号与 teardown

start_session会向子 shell 发布四个环境变量(src/treg/shell.py 顶部常量):

变量含义
TREG_SHELL标记存在活跃会话,阻止嵌套会话(cmd_shell_start检测到它会直接拒绝)
TREG_SHELL_DIR私有会话目录(shim 位于<dir>/bin)
TREG_SHELL_REALPATH干净的原始 PATH(shim 转交给treg run用)
TREG_SHELL_PIDtreg shell start进程 PID,treg shell stop靠它发信号

然后通过_run_subshell运行交互式子 shell,退出时拆除会话目录(_teardown用shutil.rmtree幂等清理——Phase 1 磁盘上没有任何凭据可擦,每次调用treg run都现取 grant 且从不持久化)。

信号处理是正确性的关键(_run_subshell):父进程忽略SIGINT/SIGQUIT(它们属于交互前台子进程),而将SIGTERM/SIGHUP转为"停止子进程"。这样treg shell stop(向TREG_SHELL_PID发信号)和用户关闭终端(SIGHUP)两条路径都能回到 teardown,不会留下孤儿 shell。可选的--ttl会启动一个 daemon 计时器,N 分钟后自动关闭会话(threading.Timer,测试test_run_subshell_ttl_closes_the_session验证 1 秒计时器能终止一个要睡 30 秒的 shell)。

shell 适配方面,_shell_argv针对 zsh(写$ZDOTDIR/.zshrc)与 bash(--rcfile)分别生成 rc 文件,恢复用户原始环境、确保 shim 目录始终排在 PATH 首位,并在提示符前加上(treg)标记(zsh 的PROMPT、bash 的PS1)。

命令接线在 src/treg/cli.py 的cmd_shell_start/cmd_shell_stop:进入前要求已登录(treg login)且TREG_SHELL未激活;GET /tools拉取工具清单后交给plan_shims;若既没有可运行 CLI 也未开--proxy,则明确报错并提示注册入口(treg upload clis或treg tool update <name> --local-run on)。

双路由:--server-for与本地/服务端分层

默认情况下,每个被影子化的 CLI 都在本地运行(treg run <tool>)。而:

treg shell start --server-for stripe,render

会把stripe、render路由到treg run --server——密钥完全不落在这台机器上,输出流式返回。但仅限server_runnable的工具:若请求的工具不支持服务端运行,则回退本地并给出警告(src/treg/shell.py 中plan_shims的 fallback 分支;server_for既可按 bin 匹配也可按工具名匹配,测试test_plan_shims_routes_server_for_runnable_tools与test_plan_shims_matches_server_for_by_tool_name分别验证)。--ttl则给整个会话设下硬性时间上限。

为什么服务端运行可行、而本地模式需要独立设计?这取决于凭据注入方式:auth_mechanism为env/argv的 CLI 可以由服务端持有密钥并注入(见 docs/context/architecture/local-run.md),因此server_runnable只对这两种机制成立;config_file(CLI 自读~/.config)与device机制只能本地运行。

--proxy:拦截的另一半

必须强调treg shell的"兄弟门":treg <command>(cmd_with,详见 docs/context/architecture/local-proxy.md)对单条命令做同样的捕获,且不需要子 shell——对多数人来说它才是默认入口。--proxy则用于这样一个会话:团队的 CLI(经 shim)与裸 HTTPS 调用(经代理)要同时生效。

两者的分工:shim 捕获成员敲键盘输入的注册 CLI(stripe balance);treg shell start --proxy额外捕获Agent 自行发起的 HTTPS 调用——来自一个从没听说过 treg 的脚本(比如直接写api.stripe.com请求的程序)。它目前是opt-in:因为拦截是这套体系里第一个可能破坏 Agent 自身调用的功能(证书固定型客户端会拒绝我们的 CA),意外比一个显式开关更糟。

实现细节(cmd_shell_start→_start_local_proxy,src/treg/cli.py):

  • 生成/加载机器级 CA(ensure_ca),把 allow-list 种子直接复用为 shims 已经拉取过的工具清单——包括所有注册主机、甚至没有 CLI 的工具,因此不产生第二次请求(测试test_cmd_shell_start_seeds_the_allow_list_from_the_tool_listing验证 hosts 被小写化、排序、无 host 工具被跳过);
  • 启动代理后交给start_session两样东西:extra_env(代理 URL + 信任包)与on_close(退出时停止代理);
  • extra_env在 treg 自身变量之后应用,并跳过PATH/TREG_SHELL*,所以任何附加项都无法破坏名字解析或伪造嵌套会话(测试test_extra_env_cannot_break_the_shims);
  • 启动横幅(_print_banner)会列出被捕获的主机,并声明其余流量原样直出——成员绝不能"偶然发现"自己被拦截(测试test_the_banner_names_the_captured_hosts断言横幅中出现完整主机名与"Every other address goes straight out");
  • --proxy-port可将代理移出默认端口18791;--renew-ca强制重新生成 CA。

CLI 参数一览(src/treg/cli.py 解析器定义):

参数作用
treg shell start启动 treg 子 shell
--server-for a,b将指定工具路由到服务端运行(密钥不上本机;仅对server_runnable工具生效,其余回退本地并警告)
--ttl MINN 分钟后自动关闭会话(默认无限制)
--proxy同时捕获 Agent 自行发起的、发往注册 API 的 HTTPS 调用;其余流量直出
--proxy-port PORT--proxy监听端口(默认 18791)
--renew-ca启动--proxy前重新生成本机 CA
treg shell stop离开 treg shell(等价于exit/Ctrl-D)

命令同时以treg cli shell start|stop形式提供别名(见 docs/context/interface/cli.md)。

阶段划分:为什么没有内存驻留 Agent

当前是Phase 1:shim 直接调用treg run,免费复用了完整的 local-run 链路——grant、deny、runner-proof、OAuth leaf、审计 → 计量/额度(见 docs/context/architecture/local-run.md)。

原计划的Phase 2 内存驻留会话 Agent 被刻意放弃:treg run的每次命令级专用用户隔离(treg-run系统用户、sudo -u treg-run交接,成员的同一 uid 无法读取另一个 uid 进程的/proc/<pid>/environ)使每次调用结束后成员进程内不留任何凭据痕迹——这比一个长期持有 RAM 中凭据的 Agent 姿态更强。因此 shell 模式始终只是treg run之上的一层薄便捷层,真正的安全保证存在于 local-run 沙箱(隔离 + egress 白名单 + 输出脱敏 + deny 规则)之中。

这一决策在 src/treg/shell.py 模块 docstring 与_teardown注释中反复强调:"Phase 1 磁盘上没有任何凭据可擦;treg run每次调用现取 grant、从不持久化"。

测试印证:客户端契约被逐条锁定

tests/test_shell.py 在无真实服务器、无真实厂商 CLI 的情况下,用假treg与真实/bin/sh验证了本节全部核心断言:

  • shim 在干净 PATH 上以原样 argv 调用treg run <tool> -- …,且退出码透传(test_shim_execs_treg_run_with_clean_path_and_verbatim_args);
  • server 路由 shim 生成treg run --server <tool>(test_server_route_shim_execs_treg_run_server);
  • 补全调用直达真实二进制(test_completion_call_bypasses_treg_real_bin_runs);
  • 会话 wiring:shim 目录排在 PATH 首位、REALPATH干净无 shim 目录、--ttl分钟转秒、退出后会话目录被拆除(test_start_session_wires_path_and_tears_down);
  • treg shell stop在会话外报错、在会话内向控制器发SIGTERM(test_stop_signals_the_controller);
  • 嵌套会话、未登录、无可运行 CLI 三种启动前置守卫(test_cmd_shell_start_*)。

已知边界

  • --proxy依赖cryptography(编译型依赖),位于[proxy]extra 而非基础安装;缺库时会收到明确的安装提示(pip install "tools-registry[proxy]"或按实际安装方式给出等价命令,src/treg/cli.py 的ensure_proxy_dependency)。
  • 证书固定(pinning)的客户端会拒绝 treg 的 CA 而单独失败,当前没有按主机的"永不拦截"名单。
  • Node 23 及更早版本的fetch()完全无视代理变量(Node 24 才提供NODE_USE_ENV_PROXY),这类调用会绕过捕获,详见 docs/context/architecture/local-proxy.md。
  • 某些厂商 CLI(如gh)在发起任何网络请求前就自行完成认证,因此仍需要treg run路径,代理无法拦截。
  • 后端
  • API网关
  • MCP 服务
  • dsh-plugin

【免费下载链接】treg

OpenRouter for agent tools. Join community here: https://discord.gg/6mQYYfFMAn

项目地址:https://gitcode.com/GitHub_Trending/treg/treg
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询