Skip to content

✨ 新增 sctl 本地守护进程与 MCP 桥接 - #3

Open
CodFrm wants to merge 21 commits into
mainfrom
feat/sctl-daemon
Open

✨ 新增 sctl 本地守护进程与 MCP 桥接#3
CodFrm wants to merge 21 commits into
mainfrom
feat/sctl-daemon

Conversation

@CodFrm

@CodFrm CodFrm commented Jul 21, 2026

Copy link
Copy Markdown
Member

说明

将原 main 分支中的 sctl 守护进程实现通过 PR 重新引入;仓库 main 已重置为仅包含简单 README 的干净基线。

此 PR 替代因分支历史被重建而无法重新打开的 #2

关联开发与讨论:

主要内容

  • sctl CLI 与本机守护进程
  • WebSocket 桥接、认证、配对、授权与限流
  • MCP server 与本机控制 API
  • 协议、威胁模型、测试及 CI/发布配置

CodFrm added 14 commits July 21, 2026 10:26
Replace per-client pairing/scope/revocation with a flat model: enrollment establishes the long-term key K once, and the CLI and every MCP agent inherit it over the enrolled channel. clientId becomes an audit-only label.

- remove client store, cached MCP identity, per-client pairing/sync/revoke
- rename `sctl pair` -> `sctl connect`; control /pair-ext -> /enroll
- `sctl mcp` drops the pair subcommand; --name is now an audit label
- mcpserver exposes all tools (no scope filter)
- add an Origin whitelist (chrome-extension:// etc.) as a cheap web pre-filter; the handshake stays the real gate
- source reads are no longer CLI-exempt (source-read policy gates both)
- rewrite protocol.md / threat-model.md / architecture.md / README / AGENTS
…pair/client

Mirror the extension-side protocol change byte-for-byte: envelopeTypes becomes {session, bridge} and the flat-trust-dead pair.*/client.* types are removed. protocol.go's EnvelopeTypes field becomes a nested struct so Load() parses the new shape. protocolVersion unchanged (no wire-format change).
The SDK gains full support for protocol revision 2026-07-28: a stateless
model with per-request _meta, server/discover replacing the initialize
handshake, MRTR, subscriptions/listen, and cacheable list results.

No source change is required. sctl serves MCP over stdio, so the v1.7.0
constraint that streamable HTTP only accepts 2026-07-28 when
StreamableHTTPOptions.Stateless is set does not apply, and no API used by
internal/client/mcpserver broke.

Verified by driving the real binary over stdio with raw JSON-RPC, since
the package tests put go-sdk on both ends and therefore cannot observe
which revision is actually served: server/discover advertises
2026-07-28 and tools/list succeeds with no handshake, while a legacy
initialize client still negotiates 2025-06-18 and gets the same tools.
…l.json

Add scripts:edit:request scope and the scripts.edit.request (write,
approval) / scripts.source.grep (reuses scripts:source:read, disclosure)
actions to the bridge protocol mirror. Declarations only; no handling
wired up yet.
…output)

Fold `scripts list/info/source` into a single kubectl-style `get`
command and make the leading `scripts|script|sc` resource word
optional on get/delete/enable/disable. Replace the global --json
bool with -o/--output (table default / json / source), remove `rm`
(del is now delete's only alias), and wire --lines through to
scripts.source.get for get <uuid> -o source.
… in RunE

The verb commands validated arg count through stripResourceWord but read the
raw args[0], so `sctl delete sc u1` dispatched uuid="sc" — deleting the wrong
script (or NOT_FOUND) instead of u1.

Arg-count errors now return ExitError{exitError}: a bare error is folded into
exit 1 by main, and 1 is reserved for "user rejected in the browser", so a
caller could not tell a typo from a rejection.

printScriptSource decodes code into a pointer so an empty window (--lines over
blank lines) prints nothing rather than falling back to dumping the result
envelope into a redirected .user.js.

The CLI tests grew real guards: the leaves that assert a rejection now stub the
daemon and pin the dispatched action and uuid, so they no longer pass merely
because an unreachable daemon also exits 3, and parseLinesFlag is covered
directly.
spawnServeProcess exec'd os.Executable() with "serve", assuming the running
program is sctl. It is not whenever the client packages are linked into another
binary. Under `go test` os.Executable() is the test binary, so every leaf that
reached ensureDaemon without a live daemon re-ran the whole test package
detached — and each of those re-runs spawned again. Running `go test ./...`
grew to thousands of processes and exhausted the system's fork limit.

The children also inherited SCTL_BRIDGE_ADDR, so they issued /control/call
requests against the parent's stub server and overwrote the captured request
other tests assert on, which is why TestGrepInputMapping and TestGrepOutput
failed intermittently. With the guard in place internal/cli drops from 81s
(failing) to 0.4s.

`go run ./cmd/sctl` builds a temporary binary that is still named sctl, so
auto-launch during development is unaffected.
`sctl edit <uuid>` requests a content-anchored edit: the edits travel alone, so
the source is never read first and never uploaded. Edits come either as a JSON
array via -f (- reads stdin) or as repeated --replace/--with pairs, whose values
accept @path to read from a file and @@ to escape a literal leading @ — user
script metadata lines start with @, so the escape is not hypothetical.
--replace uses StringArray rather than StringSlice: the latter splits on commas
and would tear an anchor like "f(a, b)" into two edits.

`sctl grep <uuid> <query>` searches one script's source. The query is a literal
substring unless -E is given; -i, -C and -m map to ignoreCase, contextLines and
maxMatches, and are only sent when actually given so the defaults live on the
extension side alone. Output follows grep -n, with truncation and skipped-line
notices on stderr so stdout stays pipeable. No match is not an error: exit stays
0, because exit 1 already means the user rejected the request.

The MCP side registers scripts_edit_request and scripts_source_grep and gives
scripts_source_get its line-window fields, closing the tool-count assertion that
went red when protocol.json declared the two actions. The grep tool description
spells out that the default mode matches literally, since a model will otherwise
assume the query is a pattern and be puzzled by `.*` finding nothing.
Command help and flag descriptions, stdout/stderr output, error and log
messages, and the MCP tool descriptions and input schemas are now English.
Code comments and Convey test titles stay Chinese; the test assertions on
the affected CLI output are updated to match.
The command table still advertised `sctl scripts list / info / source` and a
top-level `rm`, and the global-flag line still documented `--json` — all of
them removed when the command surface went resource-oriented. docs/architecture.md
carried the same stale diagram plus an internal/cli listing naming a scripts.go
that no longer exists, and docs/verification.md's `./sctl scripts list` was an
executable verification step that now hits "unknown command" instead of the
exit 3 it claims to demonstrate.

protocol.md gains the two new actions (scripts.source.grep, scripts.edit.request),
the line window on scripts.source.get, and the constraints a client actually
hits: literal-by-default grep, overlap-counted anchor uniqueness, the 100-edit
batch cap. Also mirrors that cap into the MCP edit schema as maxItems so an
agent learns the bound locally instead of from a rejected request.

README is translated wholesale rather than left half-Chinese: its command table
and flag section had to be rewritten anyway, and docs/ plus AGENTS.md are
already English.

Two stale facts found while checking the rest: `sctl status` reports no
per-client pairing under flat trust, and the extension's audit component is
external-access, not local-access.

@cyfung1031 cyfung1031 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

综合技术审查:ScriptCat PR #1573 + sctl PR #3

结论

建议修改后再合并。 当前方案的核心方向合理:Go 单二进制守护进程、仅监听 loopback、双向 HMAC 认证、由扩展负责授权和实际变更、源码读取使用独立策略、用精确内容哈希防止 TOCTOU、以结构化数据返回不可信内容、支持取消传播,并保持两份协议文件逐字节一致。但在本次审查的两个提交头上,仍有一个已确认的 CI 阻塞项,以及多个涉及用户安全或阻塞调用契约的缺陷。影响最大的是:一次“本会话允许”可授权后续不同代码、多个独立调用被去重后只有一个调用能收到响应、正则搜索可让扩展的 Service Worker 远超标称时间预算地卡死,以及文档声称删除需长按 1.5 秒而实际只需一次点击。

本审查合并五份相互独立、均采用深度模式的审查结果,分别覆盖:安全;跨仓库协议与生命周期;CLI/MCP 功能正确性;审批 UX、无障碍与本地化;测试、发布与文档。每位审查者都独立检查了两个 PR 的精确提交头及 scriptscat/scriptcat#1573 的全部 14 条讨论。不评分:这里只有一个由两个仓库共同组成的候选方案。

审查目标

  • 候选提交: scriptscat/scriptcat@0a7b89da9cd1093ba13b5ad3ebf2aa55edaf1377scriptscat/sctl@60772196ddc37b6eb77515d7f5021c24865a5d8b
  • 系统范围: ScriptCat MV3 扩展、offscreen WebSocket 客户端、负责审批和实际变更的 Service Worker、loopback 上的 sctl serve、本机 /control/* API、CLI 与 stdio MCP Server。
  • 目标: 在不引入浏览器可直接访问的未认证控制面的前提下,支持脚本清单、源码读取、安装、编辑、启用/停用和删除,并确保 agent 不能在用户实际授权范围之外修改脚本。
  • 验收条件: 必需 CI 全绿;同意语义真实;危险内容必须按精确内容授权;每个已接受的阻塞请求都有唯一终态;取消或断线后不能再产生副作用;执行量有硬边界;协议与版本一致;跨平台、跨仓库行为有发布级证据。
  • 明确不处理: v1 的远程/WSS、同一系统用户账号已完全失陷的情况,以及当前扁平信任模型下的逐客户端隔离。只有在这些边界被准确披露、且不被用来掩盖可避免的可用性或同意问题时,才可作为已接受风险。
  • 基线说明: PR 讨论记录了从 Native Messaging 到 WebSocket/扁平信任的大幅重构。因此,本审查以精确提交头和维护者的当前架构说明为准,不以已经过时的 PR 正文为准。

已纳入的讨论观点

合并结果保留了完整讨论脉络:CodFrm 倾向 Go、扩展作为 WebSocket 客户端、不增加 nativeMessaging、v1 仅 loopback、写操作阻塞等待,以及后续真实浏览器验证;cyfung1031 指出 WebSocket 只是传输方式而不是安全模型,并主张远程能力必须独立设计;daiaji 提供 Firefox/工作流视角并关注向 agent 开放过多能力;维护者还报告,真实联调发现了 mock 未覆盖的请求关联、CLI 合成身份和策略状态持久化问题。这些报告为正常路径提供了正面证据,同时也说明跨仓库边界必须使用更高等级的验证。

审查发现

严重程度 发现 证据标签 + 定位 影响 建议修改 验证方式 置信度 剩余风险
blocker sctl#3 必需的 protocol-drift 作业仍拉取已删除的 src/app/service/service_worker/mcp/protocol.json,当前因此收到 HTTP 404;实际两份协议镜像内容相同。 observed — sctl/.github/workflows/test.yaml:49-74;扩展实际路径 src/app/service/service_worker/external_access/protocol.json;Actions run 30823425365protocol-drift。本地 cmp 返回 0,两份 SHA-256 均为 b224f0ec9fe45a65ed7059772962f401bd77c1302304c7ab78102e2b58a7c059 必需检查为红色且根本未执行比较,所以既不能允许合并,也没有证明兼容性。只修改 EXT_PROTOCOL_REF 无法修复错误路径。 改为拉取 external_access/protocol.json;在 ScriptCat 合并前让临时 ref 指向精确的配套提交;输出 ref 与摘要;发布验证优先使用提交 SHA。 T2:在 6077219 上重跑工作流,要求拉取成功、字节差异为零、sctl 整体工作流全绿。 high 可变分支可能在审查后移动;应在作业摘要记录两个提交 SHA。
high 安装/编辑的“本会话允许”只按脚本身份或操作类别建立授权,没有绑定用户实际审查的代码;后续内容完全不同的代码可在不再显示审批页的情况下执行。 observed — ScriptCat approval.ts:198,284,433-442,615-639approval.test.ts:333-348 明确在 v2 获得会话授权后自动批准后续 v3 编辑;src/pages/install/App.tsx:149-162 为安装/更新提供该选项。 一次对安全内容的批准会变成当前扩展会话内修改任意后续代码的权限;界面仍表现为“需要审批”,但提示词注入防线已被绕过。 不应对安装/更新提供会话授权。如必须保留可复用的代码写入授权,至少将 action、精确 contentHash、目标身份、原代码哈希、启用状态和过期时间组成规范化授权凭据;升级时清除旧的未绑定授权。 T2:对代码 A 选择本会话允许,再请求同身份但不同的代码 B;真实扩展+daemon 链路必须让 B 保持待审批并打开精确审查页。 high 全局“直接允许”仍是明确的完整绕过,必须持续标记为安全降级。
high 安装、编辑和源码披露的去重会把多个独立阻塞请求合并为一个 operation,但只保存第一个 requestId;只有第一个调用能收到最终响应。 inferred — ScriptCat approval.ts:176-182,259-267,382-392,548-563;现有测试创建 r1/r2 却只断言 operation 数量;sctl 在 internal/daemon/bridge/call.go:36-53 为每个请求建立独立 pending call。 后续调用会一直等到五分钟超时,取消语义也变得不明确,违反“每个已接受请求都必须有一个终态”的契约。 不要合并不同的非空 requestId。若必须合并提示页,应持久化有上限的 waiter 集合,将一次副作用的结果分发给所有仍有效的 waiter;取消时只移除对应 waiter,直到集合为空才取消 operation。 T2:并发发起两个相同调用,批准一次并断言二者都返回;再覆盖只取消一个、拒绝、过期和断线。 high(源码关联缺口);仍需 T2 fan-out 必须有明确幂等契约,确保多个响应绝不意味着执行多次副作用。
high 正则搜索的两秒执行预算无法中断一次同步的灾难性 RegExp.test;时间只会在行与行之间检查。 measured — ScriptCat source.ts:105-109,202-220;使用 /(a+)+$/ 对仅 28 个字符的单行做 Node 引擎探测,实测 2,153,780 ms,而声明预算为 2,000 ms 该逻辑运行在 ScriptCat 的消息总线 Service Worker 中。一个很短且通过校验的查询即可让其他扩展功能长时间冻结,使取消或审批都无法工作。 将正则匹配移到可强制终止的 worker/进程,或使用线性时间正则引擎。不能依赖同步回溯外层的计时检查,也不能只靠启发式规则过滤危险表达式。 T2:通过所选可中断边界运行 grepLines 的对抗与正常用例,断言能在文档预算内硬终止。 high 不同引擎行为不同;输出大小和 worker 生命周期仍需独立限制。
high 文档所述“长按 1.5 秒确认删除”并未实现:删除与其他操作共用单击处理器,而且危险的批准按钮会自动获得焦点。 observed + external-cited — ScriptCat external_access_confirm/App.tsx:164-192App.test.tsx:118-125、指南 external-access-guide_zh-CN.md:151-160,229-235;PR 评论 5024413197 声称已验证 1.5 秒长按。 一次点击,甚至页面打开后按 Enter,都可能立刻删除脚本。这既违背承诺的危险操作防误触设计,也使已有 E2E 声明失真。 实现可访问的审慎确认控件:显示进度、松开即取消、支持键盘等价操作与减少动态效果设置,并取消批准按钮自动聚焦;否则合并前必须删除所有长按声明。 T1:假定时器与焦点测试;T2:真实 Chrome 鼠标、触摸、键盘和减少动态效果录制。 high 长按对部分运动能力受限用户不友好,因此必须提供仍具审慎性的非指针替代方式。
high 将任一全局策略从“需要审批”切换为“直接允许”时,只需点击一次分段控件;警告在权限已经变更后才出现,持久化失败也不会回滚界面。 observed — ScriptCat ExternalAccessSection.tsx:78-119,157-169ExternalAccessSection.test.tsx:135-148、直接执行路径 bridge.ts:294-395 一次误触即可让所有调用方跳过人工审批;源码可直接披露,写操作可立即执行。保存失败时,界面显示还可能与实际持久化状态不一致。 保存前用操作类别明确的确认对话框说明后果;等待持久化完成;保存期间禁用控件;明确显示成功/错误;失败时恢复原值。从直接允许切回审批可保持一次点击。 T1:取消确认时不得调用 setter,保存失败时恢复状态;T2:确认成功前不得有请求绕过审批。 high 用户仍可主动选择直接允许,但安全降级状态必须持续可见。
high WebSocket 断线没有批量取消扩展中已持久化的待审批操作;同时取消与批准通过非原子的读取/检查/写入序列竞争。 inferred — ScriptCat external-access-connect.ts:371-380controller.ts:52-58,139-142approval.ts:484-535、非 CAS 更新 repo.ts:320-341;契约 sctl/docs/protocol.md:211-222 已消失的请求方可能留下仍可批准的操作;取消与批准并发时可能违反“首个终态生效”,甚至在取消后仍执行变更。这类并发结论不能只靠源码检查判定已复现。 将 operation 绑定 daemon session epoch;断线或连接替换时批量取消旧 session 的操作;对终态迁移使用串行协调器或 CAS,并在执行实际变更前先原子取得明确的 executing 状态。 T2:用确定性 barrier 分别测试取消/批准两种顺序;真实执行“断线→重连→尝试批准”,断言零变更且新 session 不收到旧响应。 medium-high 若承诺真正 exactly-once,副作用完成与日志持久化之间的崩溃仍需幂等/恢复设计。
high 审批或拒绝失败时,确认页仍会在 finally 中关闭,错误上下文消失且用户无法恢复。 observed — ScriptCat external_access_confirm/App.tsx:81-90;当前测试只覆盖成功路径,没有覆盖 decideOperation 拒绝。 TOCTOU 冲突、过期或瞬时消息错误发生时,用户可能只看到一闪而过的提示,然后失去唯一能证明“没有执行变更”的界面,并误以为操作成功。 仅在操作成功后关闭。失败时保留持久的页内错误;可重试错误应重置单次保护;冲突/过期等终态应禁用旧按钮并提供重新加载或关闭。 T1:Promise 拒绝后页面保持打开;T2:强制制造目标哈希冲突,验证页面明确显示未发生变更。 high 服务层必须返回带类型的终态/可重试状态,UI 才能正确选择重试、重新加载或关闭。
high Windows 凭据保护使用 POSIX 0600/0700 语义描述,但实现没有设置或验证 Windows DACL。 external-cited — sctl docs/threat-model.md:65-91internal/pkg/fsutil/atomic.go:12-35internal/pkg/paths/paths.go:16-29;Go 官方 os.Chmod 文档说明 Windows 除 0200 外不使用其他 mode bit。 control token 是 /control/* 唯一授权闸门,pairing key 用于认证扩展。POSIX mode 数字并不能建立文档声称的 Windows 访问边界,尤其是使用 SCTL_DATA_DIR 时。 在 Windows 创建/验证仅当前用户可访问的 DACL,或使用系统凭据存储;对不安全的覆盖目录 fail closed;在验证完成前将威胁模型改为平台限定说明。 T3/Windows T2:在干净目录与故意宽松的目录检查有效 DACL,证明其他用户 SID 无法读取两项凭据。 high Administrator/SYSTEM 与同用户账号已完全失陷仍属于明确不处理范围。
high 两个仓库的 CI 都没有驱动实际发布的跨仓库进程边界;同时 sctl 合并前只在 Ubuntu 测试,尽管存在平台专用的启动与文件系统代码。 observed — ScriptCat 的一致性/controller 测试使用本地文件或 fake;sctl docs/verification.md:68-81 将完整链路留给跨仓库工作;sctl .github/workflows/test.yaml:20-47 仅 Ubuntu;存在 spawn_unix.go/spawn_windows.go;六平台构建只在 tag 发布时执行。 维护者已经报告过仅在真实联调中暴露的缺陷。Windows/macOS 问题会在准备发布后才出现,而且交叉编译不能验证启动、ACL/路径、取消和生命周期。 增加按精确 SHA 组合的跨仓库集成门禁,覆盖握手、列表、批准、拒绝、取消和重连;合并前增加 Linux/macOS/Windows 构建测试及本机 daemon 启动 smoke test。 T2:先植入一个已知请求关联回归,证明门禁会失败,再对最终提交头在三个 hosted OS 上执行。 high arm64 初期可仅编译验证,但必须明确记录,并要求 amd64 原生 smoke 通过。
medium 版本、存活检测与接入提交语义不完整:daemon 握手未拒绝错误 envelope v,扩展忽略 hello.protocolVersion,双方都不主动发 heartbeat,新 pairing key 的交付也没有提交确认。 observed/inferred — sctl conn.go:54-92,116-176;ScriptCat external-access-connect.ts:250-277,335-352;契约 sctl/docs/protocol.md:45-48,114-118 兼容性错误会延迟或局部出现;半开连接可能长期显示为已连接;存储失败可让 daemon 与扩展保留不同密钥。 在阶段分发前校验共享 envelope schema;要求 hello 协议版本一致;指定 heartbeat 发起方并在丢失 pong 时关闭;为接入增加分阶段持久化、确认与旧密钥回滚。 T2:错误 v、错误 hello 版本、丢失 pong、daemon/扩展存储失败和提交确认丢失的故障注入。 medium 浏览器定时器节流与双密钥宽限状态需要严格边界。
medium CLI 与 MCP 边界契约发生漂移:普通 Cobra 参数错误可能使用本应代表“用户拒绝”的退出码 1;MCP schema 没有表达安装参数二选一和源码行窗口必须成对出现。 observed — sctl cmd/sctl/main.go:19-28internal/cli/write.go:15-19、退出码契约 internal/cli/cli.go:40-47;schema internal/client/mcpserver/tools.go:17-25;扩展校验 bridge.ts:79-89,117-128 自动化无法区分错误调用与人工拒绝;MCP 客户端会把实际无效的请求结构当作有效,直到扩展端才收到 INVALID_REQUEST 将所有非决策失败统一为退出码 3;用 JSON Schema 表达跨字段约束,同时保留扩展端最终校验。 T2:真实二进制退出码矩阵;内存 MCP 无效调用测试,并断言没有请求被转发到 bridge。 high 不同 MCP 客户端对 JSON Schema 支持不一,因此服务器和扩展校验仍是最终权威。
medium 所谓“按客户端限流”实际使用调用方可伪造的审计标签作为 key;轮换 X-Sctl-Client/--name 即可绕过配额,并让 limiter 长期保存更多 key。 observed — sctl controlapi.go:105-114bridge/call.go:25-33ratelimit.go:10-48;威胁模型明确该标签未经认证。 该限制只是按标签的礼貌性限流,不是针对 token 持有者的安全或硬资源边界;仍可刷屏提示页并导致内存增长。 增加以已认证 daemon session 为维度的全局配额;标签只用于经过长度/字符限制的审计归因或公平性;清理不活跃 key。 T2:在同一 token 下轮换标签,断言总量限制仍生效且 key 数量有界。 high 全局配额可能让一个客户端影响其他客户端;真正隔离需要真实的逐客户端凭据。
medium 审查与发布文档不真实或不完整:ScriptCat PR 正文仍主要描述废弃的 Native Messaging;两个仓库的合并/发布/回滚顺序互相依赖;部分 sctl 语言和审计声明与代码不符;日志保留策略也没有由仓库明确负责。 observed/inferred — ScriptCat PR 正文与 docs/pull-request.md:54-72;sctl drift 默认读取 ScriptCat main,ScriptCat 指南却链接尚未合并的 sctl maindocs/development.md:10-15 规定 README/CLI 使用简体中文,但当前 README 和 CLI 文案为英文;docs/verification.md 的 daemon 审计清单与代码不一致;日志调用点没有明确保留配置。 审查者必须从评论中重建真实设计;任一仓库先合并都可能留下坏链接或红色门禁;运维人员缺少可靠的兼容、回滚与日志边界。 直接重写 PR 正文;发布带两个精确 SHA 的双仓库合并/发布/回滚顺序;逐项核对文档;满足简体中文 CLI 约定或明确修改约定;配置并测试日志轮转与保留。 T1:对全部已跟踪文档做链接/事实检查与中英文关键事实一致性检查;T2:发布 dry-run 和有界日志轮转测量。 high(已确认漂移);medium(当前日志增长行为) 未检查外部日志库默认值,因此没有将“当前一定无限增长”表述为已复现缺陷。

修改组合

合并前必须修改

  1. 修正 protocol-drift 的路径/ref,并让 sctl CI 全绿。
  2. 删除安装/编辑的会话授权,或将其绑定到精确内容;升级时清除旧授权。
  3. 恢复每个阻塞请求各自的最终结果;删除跨请求去重,或正确实现 waiter fan-out。
  4. 将正则执行放到可硬中断的边界之后。
  5. 实现文档所述的审慎删除交互,并移除批准按钮自动聚焦。
  6. 全局切换“直接允许”前必须确认;决策失败时保留错误页面。
  7. 将 operation 绑定 session epoch,并串行化终态迁移;完成断线与取消竞态的 T2 复现。
  8. 真正执行 Windows 凭据保护,或准确收窄平台安全承诺。
  9. 增加跨仓库与跨平台原生门禁,再根据真实设计重写 PR/发布交接说明。

高杠杆改进

  • 从一个机器可读来源生成或校验 protocol action、MCP schema 和跨字段请求契约,但不要形成第三份手写协议权威。
  • 在 ScriptCat 中增加“当前信任状态”摘要:是否已接入、关闭功能后是否保留密钥、全局策略、活跃会话授权,以及清除/撤销入口。
  • 生成经过脱敏的兼容性记录,包含两个仓库 SHA、协议摘要、daemon/扩展版本、操作系统和集成结果。
  • 让限流身份与日志保留语义真实、有界且可观测。

创新方案及拒绝条件

可在 protocol v2 引入 sessionId、显式幂等 key、持久化 waiter/副作用日志,以及可选的逐客户端认证能力。这能精确定义重连、fan-out、按请求方授权和撤销。但如果它会延误当前 v1 的较小安全修复,或导致两套含糊的信任模型共存,就不应放进当前 PR;应先让扁平信任和单次审批正确且诚实。

应保留的设计

  • 保留仅 loopback 监听及拒绝非 loopback 地址。
  • Origin 过滤只作为纵深防御;真正认证仍由双向 HMAC 完成。
  • 保留方向分离的 HMAC 上下文、新 nonce、HKDF 密钥分离、AES-GCM 交付、恒定时间比较和逐字节一致的协议文件。
  • 保留由扩展执行变更/披露、默认需要审批、源码与写入策略分离、staged/target 精确哈希检查、结构化 contentTrust、原始源码 stdout 与 MCP stdout 隔离。
  • 保留 draft release、校验和/来源证明、基于实际后置状态的验证,以及明确把远程 WSS 留到后续设计。

验证与证据

层级 证据 结果
T0 两个精确提交头、仓库说明、protocol/threat/architecture/verification/design/translation 文档、安全/CLI/UI/CI 源码与测试、PR #1573 全部 14 条评论、PR #3 上下文 五位审查者分别独立完成;GitHub 返回 #1573 没有正式 review,也没有 inline review thread
T1 协议镜像比较 cmp 退出码 0;两份 SHA-256 均为 b224f0ec9fe45a65ed7059772962f401bd77c1302304c7ab78102e2b58a7c059
T1 静态产物检查 五份独立审查均通过 InsightForge lint;协议和 locale JSON 可解析;合并时重新核对了源码定位
T2 当前 GitHub Actions ScriptCat run 30825284020:lint、两组单测/合并、四组 E2E 均成功。sctl run 30823425365:lint/build/vet/race-test 成功;整体仅因 protocol-drift 拉取旧路径收到 404 而失败
T2 对抗性正则探测 28 字符输入配合 /(a+)+$/ 实测 2,153,780 ms,证明外层两秒计时无法限制一次同步 match
external-cited T2 维护者报告的真实扩展+daemon 联调 报告已验证 list、disable、delete 及后置状态;本审查未独立复现,也没有覆盖重复调用、断线后审批、取消/批准竞态或当前删除交互
not-evaluated 本地 Go/Vitest、Windows DACL 与真实浏览器 UX 当前环境没有 Go runtime,也没有已安装的 Vitest 依赖;Windows/浏览器检查仍是明确的合并门禁

回滚、迁移、可观测性与风险接受条件

  • 回滚/迁移: External Access 保持默认关闭。升级时清除旧的未绑定会话授权;如增加 session/waiter 状态,应将旧 pending operation 视为已取消。记录上一个可用的 sctl 产物及校验和,定义扩展+daemon 成对回滚;所有在途写操作都必须作废。
  • 可观测性: 使用固定字段记录 session/operation/request 状态迁移、策略变化、授权创建/使用/清除、孤儿响应、取消结果、丢失 heartbeat 和接入回滚。不得记录源码、token、原始凭据或含凭据 URL;不得把调用方标签当成已认证身份。
  • 可接受风险: 扁平信任、固定端口可探测、明文 loopback 传输、只能全局停止撤销,可以在远程绑定不可能且界面明确披露范围时接受。不能接受以下情况:审批模式授权后续不同代码;请求方断线后 operation 仍可执行;已接受调用永久卡住;危险操作防误触与 CI 证据被错误描述。

审计交接

  • 原始仓库内容未被修改,只生成了审查产物。
  • 每项实质性发现均带证据标签和源码定位。
  • 并发、重连和崩溃一致性问题在相应 T2 复现通过前均保持为有边界的 inferred 结论。
  • 五份独立审查及本合并审查均必须通过 scripts/insightforge_review_lint.py 后才可发布。

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.

2 participants