Skip to content

[ac#1336] 网络出网策略层:loopback CONNECT 代理 + 授权目的地注册表(单一权威) - #1338

Merged
jinjunnn merged 1 commit into
alphafrom
feat/1336-egress-policy-proxy
Sep 10, 2026
Merged

[ac#1336] 网络出网策略层:loopback CONNECT 代理 + 授权目的地注册表(单一权威)#1338
jinjunnn merged 1 commit into
alphafrom
feat/1336-egress-policy-proxy

Conversation

@jinjunnn

Copy link
Copy Markdown
Owner

Fixes #1336
Refs #1073

REQ-137 的策略层:packages/ui-mac 内新增 授权目的地注册表(单一权威)+ loopback CONNECT 策略代理(咽喉点)。负责父票 AC2 全部AC1 的策略半场。不改 process-fence-*.ts、不改 sidecar.ts 启动、不设任何代理 env、不改可写集 —— 强制层接线全归 #1337

注册表长什么样

packages/ui-mac/src/main/network-egress-registry.ts —— 一张 readonly EgressDestination[],每行 { host, port, category, source },装载时自检(host 形状 / 端口范围 / 出处非空 / 无重复,坏了就抛)。唯一裁决函数 isEgressAuthorized(host, port):精确 lowercase(host):port,端口是键的一部分;不做通配 / 后缀 / 尾点归一 / IP↔名字等价。

初值 = 勘破 2026-08-25-network-egress-seam.md §2.2 清单,逐条对着今日代码坐标复核后登记(与散文不同的三处,以代码为准,注册表抬头与 §2.2 落地记录都写明):

host:port 类别 出处
models.opencode.ai:443 引擎:模型目录 packages/core/src/models-dev.ts:160(§2.2 写 models.dev,今天代码是这个)
codepuppy.cn:443 / alpha-gateway.tidelabs.click:443 / account.codepuppy.cn:443 / alpha-cloud.tidelabs.click:443 引擎:平台 web / gateway / account / cloud shared/alpha-config.tsALPHA_ENDPOINTS 派生(§2.2 写的 alphacodeone.com 已迁域;注册表不自持第二份域名)
registry.npmjs.org:443 包管理 npm/bun(sidecar 内 arborist 装 provider) §2.2 E13;#1334 Q1.3
pypi.org:443 / files.pythonhosted.org:443 包管理 uv §2.2 E13
github.com:443 / api.github.com:443 git https / gh / LSP 下载 §2.2 E13;lsp/server.ts:183,600
download-cdn.jetbrains.com:443 / api.releases.hashicorp.com:443 / www.eclipse.org:443 LSP 自动下载 lsp/server.ts:1330,1632,1207(eclipse 是 §2.2 同一静态枚举漏列的)
127.0.0.1:11434 本机模型 ollama §2.2 lsof

刻意不在:BYOK baseURL、远程 MCP URL(§2.2 的动态类别,静态表登记不了)、ssh *:22、Q4 拍到的 release-assets.githubusercontent.com、Q3 语料里的 example.com —— 加不加归 #1073 裁,没自己编。

代理怎么做 DNS 解析

network-egress-proxy.ts 跑在 Electron main(不在围栏内)。顺序:解析 CONNECT authority → 查注册表 → net.connect({ host, port, autoSelectFamily: true })名字拨号 —— getaddrinfo 在 main 进程侧跑(围栏刻意不放行 mDNSResponder,#1334 Q3)。未登记的名字一次 DNS 都不发(测试用 dial spy 断言零调用)。隧道内 TLS 端到端,不看内容、不注入证书(老勘破 §4.2)。三种运行时(bun 1.3.14 / node 22 / Electron 42 内嵌 node 24)都用同一份探针验过 http.Serverconnect 事件与按名拨号成立。

失败形态各自可辨、各带结构化记录({event, id, at, method, authority, host, port, verdict, reason, status, detail}):403 unregistered(唯一的「闸门说不」)/ 400 bad-authority(不猜默认端口)/ 405 method-not-connect(不做明文转发)/ 502 dial-failed(ENOTFOUND / ECONNREFUSED / 10 s 超时 ETIMEDOUT)。拒绝体带前缀 alpha egress policy: —— bun fetch 把代理 403 当目标响应返回(§4.2),正文是应用侧唯一的归因线索。

AC2 判据与反向用例的实际输出

判据文件:packages/ui-mac/src/main/network-egress-proxy.test.ts(12 条)+ network-egress-registry.test.ts(4 条),两者登记进 scripts/gate-files.tsv(精确条数 12 / 4)。

bun test src/main/network-egress-registry.test.ts src/main/network-egress-proxy.test.ts16 pass / 0 fail / 177 expect()。判据里两条反向用例打印的原文:

反向一(生产注册表、默认接线,CONNECT 到未登记的 loopback 靶站):

[ac1336 AC2 反向 · 生产注册表] 403 "alpha egress policy: 127.0.0.1:63717 denied (reason=unregistered) — destination is not in the authorized registry (packages/ui-mac/src/main/network-egress-registry.ts)" record={"event":"egress.connect","id":1,"at":"2026-09-10T08:38:31.580Z","method":"CONNECT","authority":"127.0.0.1:63717","host":"127.0.0.1","port":63717,"verdict":"deny","reason":"unregistered","status":403} target.connections=0

「代理确实收到了请求」由那条 egress.connect 记录自证(空输出 ≠ 拦住);靶站 0 连接。

反向二(控制臂:注册表换成恒答「允许」的替身,同一个判据函数 judgeUnregisteredDenied 必须当场判红):

[ac1336 AC2 控制臂 · 恒答允许的替身] ok=false — record verdict=allow reason=- status=200 (expected deny/unregistered); status 200 (expected 403); body not identifiable: ""; target saw 1 connection(s) — the stand-in tunnelled to an unregistered destination

测试断言的就是 ok === false 且 detail 点名「status 200」「target saw 1 connection(s)」「record verdict=allow」。另一条:未登记公网名 ac1336-unregistered.invalid:443 ⇒ 403 且 dial spy 零调用。

本地门(worktree .worktrees/ac-1336,bootstrap 过;electron dist 软链自主 checkout只读)

命令 读数
north-star bash scripts/north-star-guard.sh ✓ zero upstream package edits,rc 0
typecheck cd packages/ui-mac && bun run typecheck tsgo -b rc 0(首版 4 条 TS2740/TS2339 —— bun/@types 把 connect 事件的 socket 标成 Duplex —— 已改)
unit(base) bun test src @ origin/alpha@3197acf,改动前同一棵树 4961 pass / 0 fail,355 files,282 s,rc 0
unit(head) bun test src @ abea306 4977 pass / 0 fail,357 files,279 s,rc 0 —— 相对 base +16 pass / +2 files = 本票的 16 条,新增红 0
闸门文件点名 bash scripts/assert-gate-files.sh ✓ 200 个闸门文件全部在位且真的跑过,GATE_RC 0;其中 network-egress-registry.test.ts 实际 4 = 登记 4,network-egress-proxy.test.ts 实际 12 = 登记 12
docs gate python3 scripts/check-doc-links.py docs/architecture/2026-08-25-network-egress-seam.md ✓ 2 relative link(s) resolve,rc 0
模块体积棘轮 bash scripts/assert-module-size.sh 抬基线前 ⚠ 55592 > 55006 (+586)(只响不拦);抬到实测值后 ✓ 55592 行(= 基线),rc 0
write-sites / wall-clock / gate-file-registry 三个判官单跑 52 pass / 0 fail(新模块不在 sidecar import 闭包、零写盘、零墙钟断言)

worktree 缺 Electron 二进制那条陷阱:本树把主 checkout 的 electron/dist 软链 + path.txt 拷入后 process-fence-apply.test.ts 在 base 与 head 上都绿,不在 fail-set 里。

文档影响

docs/architecture/2026-08-25-network-egress-seam.md:§2.2 表后加「落地记录」(注册表是权威、表是快照、三处按代码校正、哪些刻意不在),§5 策略层加模块指向。scripts/check-doc-links.py ⇒ 2 links resolve。没有新文档、没有第二张清单。

没做的(主动)

  • 不接线:profile 网络行、sidecar env HTTP(S)_PROXY、main 侧起代理 —— #1337。本票单独合入后用户可观察行为零变化
  • 注册表是静态的:BYOK / 远程 MCP 这两类动态目的地进不了表,接线后它们会被 403(可观察)。要不要让注册表吃运行时来源(alpha-endpoints.ts 的 env/pin/discovery 解析结果也属此类:今天只登记了 ALPHA_ENDPOINTS 默认值),归 #1073 / 后续票裁,没预留抽象。
  • example.com 与 AC3 的张力:#1334 Q3 语料含 curl https://example.com,choke 臂当时用的是全放行的取证代理。按本注册表它会 403 —— AC2 与 AC3 语料在这一格互相顶着,请在 #1073 裁决(加进表 / 从语料里换成注册表内目的地)。
  • 没在 ssh / bun 运行时的 ALL_PROXY 语义上验(老勘破 §7 未验项,本票不动)。
  • 模块体积棘轮:packages/ui-mac/src/main 基线人手从 55,006 抬到 55,592(实测)。其中 +339 是本票两个模块;另外 +247 在 origin/alpha@3197acf 上就已超基线(前几张票没抬,棘轮只响不拦)—— 一并抬到实测值并在 TSV 里写明。

🤖 Generated with Claude Code

https://claude.ai/code/session_01HE3Jmp5ZDzEvnCNhgCDreN

REQ-137 (#1073) AC2 全部 + AC1 的策略半场。只在 packages/ui-mac 内新增,不改
process-fence-*.ts、不改 sidecar 启动、不设任何代理 env(强制层接线归 #1337)。

- network-egress-registry.ts:授权目的地的唯一权威。初值照勘破 2026-08-25 §2.2,
  逐条对着今日代码坐标复核:models.opencode.ai(models-dev.ts:160,不是 models.dev)、
  平台四族从 shared/alpha-config 的 ALPHA_ENDPOINTS 派生(不自持第二份域名)、
  LSP 静态枚举补 www.eclipse.org。精确 host:port 匹配,fail-closed。
- network-egress-proxy.ts:只认 CONNECT;解析 authority → 查注册表 → 才拨号
  (未登记的名字零 DNS);DNS 在 main 进程侧由 net.connect 按名字解析(围栏刻意不
  放行 mDNSResponder);403 unregistered / 400 bad-authority / 405 method-not-connect /
  502 dial-failed 各带结构化记录,拒绝体带可识别前缀;隧道 TLS 端到端,不注入证书。
- 判据:registry.test(独立字面量锚 + 授权语义)、proxy.test(真代理 + 真 TCP 靶站:
  正臂隧道;反向 —— 未登记目的地 403 + 记录 + 靶站 0 连接;控制臂 —— 恒答允许的替身
  被同一判据当场判红)。两文件登记进 gate-files.tsv;模块体积棘轮基线人手抬到实测值。
- 文档:2026-08-25 勘破 §2.2 加落地记录(注册表是权威,表是快照),§5 指向模块。

Refs #1073

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HE3Jmp5ZDzEvnCNhgCDreN
@jinjunnn
jinjunnn merged commit 59b26f1 into alpha Sep 10, 2026
5 of 6 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.

[REQ-137][CODE] 给 AI 的联网加一个看门人:本地策略代理 + 授权目的地注册表

1 participant