[ac#1337] 把路封死:围栏只放行 main 进程内策略代理那一个端口,并如实声明覆盖面 - #1339
Merged
Conversation
REQ-137 的强制半场。profile 尾部追加 #1334 Q1.2 那四行(deny network* + loopback bind/inbound + 只放行 localhost:<代理端口> 出网;只写加法、DNS 不放行);策略代理在 Electron main 进程内 fork 之前起、跨 respawn 复用、起不来即拒 fork;sidecar 的八个代理变量由 main 整份改写指向它 (用户自己的代理 / NO_PROXY 不存活,main 的 process.env 不动)。trimUntilCompiles 的归因:非字节墙 的编译失败一个工作区都不丢即抛。注册表按 #1073 裁决二加 release-assets.githubusercontent.com:443。 判据:network-egress-fence.test.ts(Electron 的 node + 真 .node + 真 seatbelt + 真代理:唯一那扇门通、 逃逸语料逐条 EPERM、代理关掉后 ECONNREFUSED 且不回退直连、bare 臂对同一判据必红)、 process-fence-{profile,plan,wiring}.test.ts、sidecar-env.test.ts、network-egress-disclosure.test.ts (AC5:终端悬停卡 zh/en + 勘破文档锚点段落的覆盖面声明,过度声明必红)。出货形态六格 + 误伤语料 + 逃逸语料在 docs/verification/2026-09-10-req137-1337-packaged-egress/(47 pass / 0 fail / 3 obs, 两处 provider 目录各 26 包)。 Fixes #1337 Refs #1073 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HE3Jmp5ZDzEvnCNhgCDreN
`#1073` owner 2026-09-10 裁决一。注册表里 `127.0.0.1:11434`(ollama 一类本机模型)是一行做不到的话: 围栏只放行 `(allow network-outbound (remote ip "localhost:<代理端口>"))`,别的 loopback 端口一律 EPERM; 而 NO_PROXY 含 loopback ⇒ 这类目的地本来就不经代理。两边一夹,注册表对它永远不生效。 主 session 实测(三臂):围栏下连 127.0.0.1:11434 = EPERM,连放行的代理端口 = ECONNREFUSED (即围栏放行),无围栏对照 = CONNECTED。**BYOK 指向 loopback 的 baseURL 撞的是同一堵墙**, 且本就属于「动态」类别、静态表里登记不了 —— 删这行不改变 BYOK 的处境。 - 注册表删行 + 写明理由(含实测读数与「要支持本机目的地请先设计,不要往表里加行」)。 - 新判据:表内不得出现任何 loopback 目的地。反向臂实跑 —— 加回一行 ⇒ 三条红,新判据带完整理由。 - 连带修正三处把该行当期望值钉着的测试(初值清单、授权语义正样本、代理默认 authorize 正样本): 正样本换成 github.com:443,负样本换成同 host 的 22 端口。**每一处都是正确的红,改的是测试不是判据。** - gate-files.tsv:registry 条数 4→5 并补新判据说明;proxy 那行描述里已删的地址改成实际用的。 门:typecheck rc=0 · ui-mac 全量 4996 pass / 0 fail / 359 files · north-star rc=0 · assert-gate-files `✓ 202 个闸门文件…精确一致` rc=0。 Refs #1073 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HE3Jmp5ZDzEvnCNhgCDreN
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1337
Refs #1073
REQ-137 的强制半场:围栏的网络面从
(allow default)变成「只放行 loopback 上策略代理那一个端口」,代理在 Electron main 进程内起、跨 respawn 复用,sidecar 的八个代理变量由 main 整份改写指向它。负责父票 AC1 强制半场 · AC3 · AC4 · AC5;#1336的代理本体与注册表没改(只加了 owner 裁决二那一行)。改了什么(每一行都能在树上指到)
packages/ui-mac/src/main/process-fence-profile.ts#1334Q1.2 那四行逐字(N1(deny network*)· N2network-bind localhost:*· N3network-inbound localhost:*· N4network-outbound localhost:<port>);egressProxyPort成为ProcessFenceProfileInput的必填项,非 1..65535 整数即拒渲染;只写加法,DNS 刻意不放行trimUntilCompilesexceeds maximum那道字节墙 ⇒ 一个工作区都不丢当场抛,消息点名「dropping workspaces cannot fix it, so none were dropped」;字节墙照旧丢尾process-fence-plan.tsprocess fence planned:日志多egressProxyPort=<n>server.tsensureEgressPolicyProxy/spawnLocalServerObject.assign(sidecarEnv, sidecarEgressProxyEnv(port))→ 再计划围栏;代理起不来 ⇒ 拒 fork(与围栏计划失败同一条 fail-closed 路);代理记录network egress {json}进 main 日志(deny 走 warn)。非 darwin:不起代理、env 照旧(没有强制层的平台不装策略层)sidecar-env.tssidecarEgressProxyEnvHTTP(S)_PROXY/ALL_PROXY(大小写)=http://127.0.0.1:<port>,NO_PROXY/no_proxy=127.0.0.1,localhost,::1—— 用户自己的代理与 NO_PROXY 名单不存活(唯一通路);main 的process.env一字不动network-egress-registry.tsrelease-assets.githubusercontent.com:443(出处:#1334Q4 实拍,shell 工具子进程;本票出货复跑第一轮它被 403 ×2,加表后 allow ×1)。example.com按裁决一不加,AC3 语料改用registry.npmjs.orgrenderer/i18n/{zh,en}.tsalpha.terminal.sandboxHoverBodydocs/architecture/2026-09-10-network-egress-on-process-fence.md§落地 + §覆盖面声明(锚点段落)2026-09-09-req159-process-fence.md§7 与2026-08-25-network-egress-seam.md§2.2 落地记录同步改指向四条 AC 各自的判据与反向用例实际输出
AC1 强制半场 ——
network-egress-fence.test.ts(4 条,darwin:真 .node + 真 seatbelt + 真代理 + Electron 内嵌 node)profile 由生产渲染器渲染(Q1.2 四行 + 可写集),env 由生产
sidecarEgressProxyEnv给出,靶站是只有代理那一侧解得开的名字。判据实际输出:listen()成功且本进程(围栏外)连进去拿到SERVED:ping。http.setGlobalProxyFromEnv()后的fetch与裸CONNECT都 200 到达靶站;代理记录里 ≥2 条 allow。[::1]:<其它端口>逐条 EPERM,DNS ENOTFOUND,每条带 pid 与耗时。directTcp should be EPERM, got {"ok":true,"connected":true,...}; udp should be EPERM ...。bare 臂读数:直连连上、UDP 发出、DNS 解出198.18.7.158。process-fence-profile.test.ts+4):N1–N4 逐字等于独立字面量、deny 在前 allow 在后、N4 端口 = 传入端口、没有 mDNSResponder 行;控制组:去掉 N3(= 老勘破 §5 那两行)或加一条减法 deny ⇒ 红。AC3 不误伤 —— 出货形态六格(
docs/verification/2026-09-10-req137-1337-packaged-egress/,47 pass / 0 fail / 3 obs,rc 0)签名包
com.tide.alphacode0.1.12(flags=0x10000(runtime),TeamIdentifier=RQX6X6A635,lipo=x86_64 arm64,codesign --verify --deep --strictexit 0;asar 里network egress policy proxy listening×1、ALPHA_AC1334_NETWORK×0)。runner 不设任何代理 env、不起外部代理。network egress policy proxy listening on 127.0.0.1:55088→process fence planned: … egressProxyPort=55088, profile=2531B→process fence applied … profile=2531BHTTPS_PROXY=HTTP_PROXY=ALL_PROXY=http://127.0.0.1:55088,NO_PROXY=no_proxy=127.0.0.1,localhost,::1<XDG_CONFIG_HOME>/opencode/node_modules、<userDataPath>/alpha-engine-config/node_modules),~/.npm/_cacache在;registry.npmjs.org:443经闸门 allow 15 / deny 0;引擎日志零NpmInstallFailedError$HTTPS_PROXY=http://127.0.0.1:55088;界外zsh: operation not permittedalpha.jsonc/alpha-engine-config//alpha_register)workspaces=3,profile=2715B,第二代egressProxyPort=55088同一端口,proxy listening全程 1 条;重算后第一条 shell 命令 100 ms(#1334deny 臂 120 s)误伤语料 12/12:文件轴九条通;
curl https://registry.npmjs.org/semver/latestCURL=200;npm view semver version7.8.5;git ls-remote https://github.com/git/git HEADb8242b0…;node dns.lookup("example.com")ENOTFOUND(预期,解析在代理侧)。逃逸语料(出货形态,每条带
AC1337-STARTED)实际输出:最后一行是
#1334Q4 那条探针的升级:引擎自己的出网经闸门,而且被拒的原因是「不在注册表」(403),不再是「解析不了」。AC4 fail-closed 且要快 ——
network-egress-fence.test.tsA3代理
close()之后同一 profile 再跑一遍,实际输出:断言:errno 是
ECONNREFUSED(不是ETIMEDOUT/ 探针超时)、消息点名代理地址127.0.0.1:<port>、绕代理直连仍 EPERM(不回退直连)、监听不受影响。「快」由 errno 结构上给出(loopback 上没人听的端口是内核即时 RST),耗时只打印不断言(#1300棘轮:不新增「机器有多闲」的判据)。接线侧(
process-fence-wiring.test.ts+3):代理起不来 ⇒network egress policy proxy failed to start — sidecar fork refused: …EADDRINUSE,fork 0 次、计划器 0 次;缺省提供者真起监听、明文GET405alpha egress policy、两次 fork 同一端口;fork 的 env 八个变量整份改写,用户导出的HTTPS_PROXY=http://127.0.0.1:7897/NO_PROXY=github.com,.internal不存活,而 main 的process.env原值不变。归因(硬要求四,
process-fence-profile.test.ts/process-fence-plan.test.ts):unbound variable: host那种失败 ⇒ 只试编 1 次、0 dropped 即抛:… not the 65535-byte data-object wall — dropping workspaces cannot fix it, so none were dropped (4 workspaces, 0 dropped, 1 attempt): sandbox-exec: unbound variable: host;字节墙仍丢尾至(1 workspace, 3 dropped, 4 attempts)。AC5 覆盖面如实声明 ——
network-egress-disclosure.test.ts(4 条,全平台)三个落点(终端悬停卡 zh / en + 勘破文档锚点段落)都要逐字含独立字面量(已登记地址 / 其它地址被拒 / 应用自身联网不在其内 / Electron main 与 renderer 不在覆盖内),且不许出现过度声明。反向实际输出:
overclaims a larger protection surface than exists: "所有联网都被"+missing: "…不在这道沙箱之内"。不在这道沙箱之内/is outside this sandbox;锚点整段删除 ⇒ 红。terminal-rail.cases.ts加一条toContain("Code Puppy 应用自身的联网(模型目录、登录、检查更新)不在这道沙箱之内")(9/9)。本地门(worktree
.worktrees/ac-1337,bootstrap 过;electron dist 软链自主 checkout 只读)✓ zero upstream package edits,rc 0bun run typecheck(tsgo -b)rc 0bun test src@origin/alpha@59b26f136,同一棵树改动前:4977 pass / 0 fail,357 files,291.55 s,rc 0bun test src@ head(同一棵树):4995 pass / 0 fail,359 files,279.06 s,rc 0 —— 相对 base +18 pass / +2 files = 本票的 18 条(profile +4 · plan +2 · wiring +3 · sidecar-env +1 · fence 4 · disclosure 4),新增红 0bash scripts/assert-gate-files.sh:✓ 202 个闸门文件全部在位且真的跑过(条数与登记精确一致),GATE_RC 0(第一轮把 plan 测试登记成 11 被抓:实际 10 —— 改写 1 + 新增 2;改回 10 后重跑全量)check-doc-links.py四个改动的 md:✓ 15 relative link(s) resolve across 4 file(s),rc 0(控制:一个坏链接的探针文件 rc 1)packages/ui-mac/src/main实测 55,726 = 基线(从 55,592 抬 +134,全部是本票的,TSV 那行写明;#1336已把前几张票遗留的 +247 抬进实测值)。脚本 rc 2:两处「改动文件超 800 行」告警是renderer/i18n/{zh,en}.ts(1717 / 1755 行,单文件字典是既有形态,本票各加一句,不在本票拆)主动没做的
127.0.0.1:11434(ollama)在注册表里但围栏不放行localhost:11434,且NO_PROXY含127.0.0.1⇒ 围栏下不可达。本票没有 ollama 负载,未测;是注册表与 profile 之间一条已知张力,归#1073裁(加一行 N 规则或删表行),没自作主张。renderer.home.catalog_ready/catalog_liveness.confirmed时序没取到(main.log 里没有那两个键);替代读数「重算后首条 shell 命令 100 / 98 ms」只两轮,按《时序门需 ≥3 轮采样》不构成结论。models.opencode.ai那条 fetch 仍没发生(与#1334同)。docs/design/2026-09-10-req159-sandbox-disclosure/design.md§3.1 文案表(悬停层「三句」)没有回填第四句 —— 那是 design-loop 的事,请主 session 裁。#1334的fixture/profile-byte-budget.ts现在调renderProcessFenceProfile会缺egressProxyPort抛错(它是历史取证夹具,里面手工追加网络行的两条臂已被生产渲染器替代),没改。$?经引擎 shell 工具读回恒 0(两轮实测)没追根;harness 改判 curl 自己的错误文本。🤖 Generated with Claude Code
https://claude.ai/code/session_01HE3Jmp5ZDzEvnCNhgCDreN