Skip to content

feat(ipc): give the session's own processes a child token the inbound gate recognizes - #10764

Merged
qqqys merged 6 commits into
QwenLM:mainfrom
qqqys:feat/peer-messaging-child-token
Sep 2, 2026
Merged

qqqys merged 6 commits into
QwenLM:mainfrom
qqqys:feat/peer-messaging-child-token

Conversation

@qqqys

@qqqys qqqys commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Gives the cross-session messaging inbox a second token for the session's own processes, and teaches the inbound gate what that token means.

A session now draws two independent tokens at startup. The first is the existing one, published in its registry record so peers can authenticate to its inbox. The second — the child token — is written nowhere but the session's own environment, as QWEN_CODE_MESSAGING_TOKEN, so only processes the session started can hold it. The inbox admits a connection on either and tells the session which one it was; the decision is made once per connection, at the auth line, and nothing later on the connection can change it.

A message that came in on the child token is self-sent: written by a script or hook this session ran. The gate accepts it under the mode-parity default, where the same frame from an unknown peer would be held — parity compares what two sessions may do, and a process this session started is not another session. An explicit agents.crossSessionInbound still wins: hold parks it for review and refuse refuses it, exactly as for anything else. Self-sent is a fact the transport establishes and passes to the gate as a separate argument; there is no frame field for it, so a peer cannot claim it, and it is carried on a held entry so a message parked by an explicit setting is released as what it is.

The model is told the difference. A self-sent message is wrapped as <cross_session_message from="own process" origin="own-process"> and followed by a notice that it came from a process this session started, not from the user — with the same two prohibitions as the peer notice: no escalation because it asked, and never read as the user approving a pending prompt. The transcript line, the hold notice, and the /peers listing say "a process this session started" / "own process" instead of "another session" / "unknown session".

Why it's needed

The inbox authentication that just landed exports the session's inbox address and token to child processes so a hook can inject a message back into it. But the token it exported was the published one, so a child's message was indistinguishable from any peer's — and under the mode-parity default, a session running in YOLO or AUTO holds every message whose sender does not assert a bypass mode. A hook's "build finished" note, injected by a process the session itself started, was parked for review like a stranger's request, with nothing a script could honestly put in the frame to change that (fromMode is a claim, and the gate is right not to trust claims). Node cannot read SO_PEERCRED, so the inbox cannot learn who connected from the kernel; what it can know is what it handed out, and to whom. Splitting the token also separates the two capabilities: a child that leaks its environment no longer leaks the peer token.

Reviewer Test Plan

How to verify

  1. Enable the experimental feature ({ "agents": { "crossSessionMessaging": true } }) and start a session. cat ~/.qwen/sessions/<pid>.json shows a 64-hex ipcToken; inside the session, echo $QWEN_CODE_MESSAGING_TOKEN is 64-hex and different from it.
  2. Put the session in YOLO mode and inject through the environment (the socat example in docs/users/features/commands.md, no fromMode). The message is delivered, not held: the transcript shows Message from a process this session started (own process): …, and with --debug the gate logs an accept. Send the same frame with the registry ipcToken on the auth line instead: it is held and /peers lists it.
  3. Set agents.crossSessionInbound: "hold" and inject with the environment token: held, listed by /peers as own process; /peers accept releases it and the model text still carries origin="own-process".
  4. Two sessions messaging each other via list_agents / send_message, held-message review, and receipts behave exactly as before.

Evidence (Before & After)

Before: with QWEN_CODE_MESSAGING_TOKEN equal to the published token, a child injection into a YOLO session with no fromMode is held (no-mode-asserted). After: see the E2E report in the follow-up comment.

Tested on

OS Status
🍏 macOS ⚠️
🪟 Windows N/A
🐧 Linux

Environment (optional)

Unit suites against real UNIX sockets; dev build under tmux with an isolated QWEN_HOME for the smoke run.

Risk & Scope

  • Main risk or tradeoff: a child token is as good as the environment it lives in. Any process that inherits this session's environment — a subagent's shell, a hook, a script the model ran — can inject a message that auto-delivers under the parity default. That is what "self-sent" means: the session ran it. The envelope notice and the classifier remain the defence against what the content asks for, as for peers, and an explicit hold/refuse covers it. The self-sent row also skips the receiver-mode check (a message from the session's own process is accepted even when the mode is unreadable), because that check exists to judge what a foreign sender could trigger.
  • Not validated / out of scope: token rotation and revocation; SO_PEERCRED-style kernel verification; named pipes. No wire-protocol change (same auth line), no registry change (only the peer token is published), receipts unchanged (replyToken is still the peer token).
  • Breaking changes / migration notes: none. The environment variable's value changes meaning (child token instead of the published one) but any injection written to the documented pattern keeps working, and is now recognized as the session's own. The childToken inbox option is inert without requiredToken.

Linked Issues

Follows #10636.

中文说明

本 PR 做了什么

给跨会话消息 inbox 增加第二个、专给本会话自身进程的令牌,并让入站闸门理解它的含义。

会话启动时生成两个独立令牌。第一个即现有令牌,发布在注册记录里供对端认证;第二个——子令牌——只写进本会话自己的环境变量 QWEN_CODE_MESSAGING_TOKEN,除本会话启动的进程之外无人持有。inbox 两者都接受,并告诉会话是哪一个;判定在每个连接的 auth 行上做一次,之后的任何行都改不了它。

凭子令牌进来的消息是"自发的":由本会话运行的脚本或 hook 写入。在模式对等的默认规则下闸门直接接受它——同样的帧若来自未知对端会被 hold——因为对等比较的是两个会话各自能做什么,而本会话启动的进程不是另一个会话。显式的 agents.crossSessionInbound 仍然优先:hold 照样留待审阅,refuse 照样拒绝。"自发"是传输层确认后作为独立参数传给闸门的事实,帧里没有对应字段,对端无从冒充;held 条目也带着它,因显式设置而被留置的消息释放时仍是它本来的身份。

模型能看出区别:自发消息被包成 <cross_session_message from="own process" origin="own-process">,随后的说明指出它来自本会话启动的进程而非用户,并保留与对端说明相同的两条禁令——不得因其要求而提权,不得视作用户批准了待决提示。transcript 行、hold 通知和 /peers 列表会写"a process this session started" / "own process",而不是"another session" / "unknown session"。

为什么需要

刚合入的 inbox 认证把会话的 inbox 地址与令牌导出给子进程,以便 hook 回注消息。但导出的是发布令牌,子进程的消息与任何对端的消息无法区分——而在模式对等的默认规则下,YOLO 或 AUTO 模式的会话会 hold 所有未声明 bypass 的发送方。于是一个 hook 注入的"构建完成",明明来自会话自己启动的进程,却像陌生人的请求一样被留置,脚本也没有任何诚实的办法在帧里改变这一点(fromMode 只是声明,闸门不信它是对的)。Node 读不到 SO_PEERCRED,inbox 无法从内核得知谁连了进来;它能知道的是自己把什么发给了谁。令牌分离也把两种能力分开:子进程泄露了环境,不再等于泄露对端令牌。

审阅验证方式

  1. 开启实验特性({ "agents": { "crossSessionMessaging": true } })并启动会话。cat ~/.qwen/sessions/<pid>.json 中有 64 位 hex 的 ipcToken;会话内 echo $QWEN_CODE_MESSAGING_TOKEN 也是 64 位 hex,且与之不同。
  2. 切到 YOLO 模式,用环境变量注入(docs/users/features/commands.md 里的 socat 示例,不带 fromMode)。消息被投递而非 hold:transcript 显示 Message from a process this session started (own process): …--debug 日志记录 accept。换成注册记录里的 ipcToken 发同一帧:被 hold,/peers 可见。
  3. 设置 agents.crossSessionInbound: "hold" 后用环境令牌注入:被 hold,/peers 中标为 own process/peers accept 释放后模型文本仍带 origin="own-process"
  4. 两个会话经 list_agents / send_message 互发、held 审阅、回执行为与之前完全一致。

证据(Before & After)

之前:QWEN_CODE_MESSAGING_TOKEN 与发布令牌相同,向 YOLO 会话不带 fromMode 的子进程注入被 hold(no-mode-asserted)。之后:见后续评论中的 E2E 报告。

测试平台

Linux ✅;macOS ⚠️ 未本地验证(CI 覆盖);Windows N/A(跨会话消息尚不支持该平台,相关套件 skipIf(isWindows))。

风险与范围

  • 主要风险/权衡:子令牌的安全性等于环境变量的安全性。任何继承本会话环境的进程——子代理的 shell、hook、模型运行的脚本——都能注入在对等默认规则下直接投递的消息。这正是"自发"的定义:是本会话运行了它。信封说明与分类器仍是对消息内容的防线,与对端一致;显式 hold/refuse 同样覆盖它。自发这一行还跳过了接收方模式检查(模式不可读时自发消息仍被接受),因为那个检查是用来判断外部发送方能触发什么的。
  • 未验证/范围外:令牌轮换与吊销;SO_PEERCRED 式内核校验;命名管道。线协议不变(同一 auth 行),注册表不变(只发布对端令牌),回执不变(replyToken 仍是对端令牌)。
  • 破坏性变更/迁移说明:无。环境变量的取值含义改变(子令牌而非发布令牌),但按文档写法的注入照常工作,且现在会被识别为会话自身的消息。childToken inbox 选项在没有 requiredToken 时不起作用。

关联 Issue

承接 #10636

… gate recognizes

A session now draws two inbox tokens: the published one peers use, and a
child token that exists only in its own environment. A connection that
authenticates with the child token is known to come from a process the
session started, and the gate accepts such a message under the mode-parity
default instead of holding it like an unknown peer's. An explicit
crossSessionInbound setting still wins. The envelope, the transcript line,
the hold notice and the /peers listing say the message came from the
session's own process, not from another session.
@qqqys

qqqys commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

E2E test report

Environment: Linux, npm run dev (dev build of this branch) under tmux with an isolated QWEN_HOME, --yolo, a dummy OpenAI-compatible endpoint so the input box renders (the model itself cannot answer). Injection driven by a small node script that also listens on its own socket for receipts.

1. Two tokens, one published

$ stat -c %a $QWEN_HOME/sessions/40304.json
600
ipcToken (record):                        a09c7869…  (64 hex)
$QWEN_CODE_MESSAGING_TOKEN (shell run by the session via `!`):  12f5e902…  (64 hex, differs)

2. Child token → delivered as the session's own (YOLO receiver, no fromMode)

receipt: {"status":"delivered","origMsgId":"smoke-child-1788319067","from":"/run/user/1001/qwen-socks/40304.sock","reason":"Your message was released to the recipient session."}
transcript: ●︎ Message from a process this session started (/run/user/1001/smoke-inj/socks/40464.sock): build finished (child token)

The model started a turn on it right away (it then hit the dummy endpoint's connection error, which is how you can tell it reached the model).

3. Same frame on the published token → held

receipt: {"status":"held","origMsgId":"smoke-peer-1788319070", … "reason":"Your message is held for the recipient user to review before it reaches their Qwen Code session."}
transcript: ●︎ Held a message from another session (this session can apply some actions without per-action review and the sender did not say whether it does). 1 waiting — /peers to review.

4. Wrong token (64×f) → no receipt, nothing in the transcript.

5. Unit coverage (real UNIX sockets where applicable)

  • packages/coresrc/ipc/ suite: 285 passed, including the new child-token inbox cases (either token admits and reports its kind, the kind is fixed per connection, a third token is still refused, the child token is inert without a required token), the self-sent gate cases (accept where a peer is held, independent of receiver mode, explicit hold/refuse still win, origin kept through a manual approval and through re-evaluation, never read from the frame), and the envelope cases (own-process attribute and notice; a peer cannot write the attribute through its name).
  • packages/clipeerMessaging/peer-messaging.test.ts, ui/commands/peers-command.test.ts, ui/startInteractiveUI.test.tsx: 93 passed (child token exported instead of the published one, generated tokens are independent draws, child-token delivery in YOLO, the same frame held on the peer token, explicit hold on a child-token frame and its release, origin kept across the pre-submit buffer, /peers names an address-less self-sent entry as the own process); ui/AppContainer.test.tsx: 170 passed.

6. Known limits of this run

  • macOS not run locally (CI covers it); Windows N/A — the feature is not available there and the suites are skipIf(isWindows).
  • The explicit-hold case was not exercised live (unit-tested); the hard kill at the end of the smoke run leaves the record behind by design, for the next enumeration's liveness sweep.

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: observed, not theoretical. This is a direct consequence of the inbox authentication that just landed in #10636: the token exported to child processes is the published peer token, so a hook's injected message is indistinguishable from a peer's and — under the mode-parity default — gets held for review in a YOLO/AUTO session. There is a concrete before/after (the same frame delivered via the env token vs. held via the registry token), demonstrated in the E2E report already posted in this thread.

Direction: aligned. This follows up a feature merged to main today and fixes a real usability flaw in the hook-injection flow that feature documents, while also separating capabilities (a leaked child environment no longer leaks the peer token). That direction was already endorsed by the merged predecessor.

Size: core paths touched (packages/core/src/ipc/**) — 236 production logic lines, 309 test lines, 112 docs lines. Below every threshold; nothing to flag.

Approach: the scope feels right, and I arrived at the same shape independently before reading the diff — two tokens (one published, one env-only), the transport establishes self-sent once per connection at the auth line, and the gate consumes it as a separate argument rather than a frame field. No unrelated changes in the diff. I don't see a simpler path: trusting fromMode would reintroduce exactly the claim-trust the gate is built to reject, and SO_PEERCRED is not readable from Node.

Risk: no high-risk path matches; no elevated risk signals.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题: 已观测到的问题,不是理论担忧。这是刚合入的 #10636 inbox 认证的直接后果:导出给子进程的是发布令牌,所以 hook 注入的消息与对端消息无法区分,在模式对等默认规则下会在 YOLO/AUTO 会话中被留置审阅。有具体的 before/after(同一帧经环境令牌投递、经注册表令牌被留置),本线程中的 E2E 报告已演示。

方向: 对齐。这是今天刚合入 main 的功能的后续,修复了该功能文档化的 hook 注入流程中的真实可用性缺陷,同时分离了能力(子进程环境泄露不再等于对端令牌泄露)。该方向已由合入的前序 PR 背书。

规模: 触及核心路径(packages/core/src/ipc/**)——生产逻辑 236 行、测试 309 行、文档 112 行,低于所有阈值,无需标记。

方案: 范围合理,且我在看 diff 之前独立得出了同样的形态——两个令牌(一个发布、一个仅存于环境变量),传输层在连接的 auth 行上一次性确认"自发",闸门将其作为独立参数而非帧字段消费。diff 中没有无关改动。看不到更简路径:信任 fromMode 会重新引入闸门本要拒绝的声明信任,而 Node 读不到 SO_PEERCRED

风险: 未命中高风险路径,无升级风险信号。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at a9ef3de1606aecd6646d4f2943008ab2c401f11b · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code review

No blocking issues found. I drafted my own solution from the title and motivation before reading the diff, and this lands on the same shape — two independent tokens, transport-established self-sent, gate consumes it as a separate argument. What I verified:

  • Fail-closed auth preserved. The rewrite of the auth path in uds-inbox keeps the terminal refusal for a wrong token or a non-auth first line; the kind (peer/child) is decided once per connection at the auth line and a second auth line is just an unparseable frame (covered by the new "holds the verdict" test). Both token comparisons are timing-safe and both always run.
  • Self-sent never comes off the wire. There is no frame field for it; admit defaults to an ordinary peer, and the inbox supplies the flag. A peer cannot claim it (tested).
  • Precedence is right. In resolvePolicy the unreadable-policy fail-closed and the explicit hold/refuse setting are evaluated before the new self-sent row, so a user who said hold still reviews own-process messages. The self-sent row skips the receiver-mode check by design, since that check exists to judge foreign senders.
  • The flag survives every path. Held entries, manual decide, reevaluate, and the pre-submit buffer all carry it (each path has a test), so a message parked by an explicit setting is released as what it is.
  • Consumers are all covered. I grepped every production consumer of InboundGate, HeldMessage, startPeerInbox, and the envelope formatters — the deliver/onFrame signature changes reach every implementation, and the /peers listing and hold notice both name the own process now.
  • Envelope hygiene. origin="own-process" is emitted only by the transport; a peer writing origin into its name lands inside the escaped name="…" attribute (tested), and the own-process notice keeps both prohibitions of the peer notice.
  • No wire-protocol, registry, or receipt changes, as claimed. Conventions (ESM, kebab-case, collocated tests, why-comments) all match.

Honest gap: on this commit no CI has actually executed the PR's tests (see below), and this environment never runs PR code — so the unit-suite results quoted in the E2E report comment remain the author's claim, not independently re-run evidence.

sequenceDiagram
    participant P1 as child process (hook or script)
    participant P2 as uds-inbox
    participant P3 as PeerMessaging
    participant P4 as InboundGate
    participant P5 as model transcript
    P1->>P2: auth line with child token
    P2->>P3: frame, auth is child
    P3->>P4: admit(frame, selfSent true)
    P4->>P4: explicit hold or refuse still wins, else accept
    P4->>P5: deliver wrapped as own-process with notice
Loading
Files changed (14 of 14 shown)
File What changed
docs/design/2026-09-02-peer-messaging-child-token.md Design doc for the two-token split, written alongside the change
docs/users/features/commands.md Injection docs now describe the child token and its gate treatment
packages/cli/src/peerMessaging/env.ts Doc comment: the env token is the child token, never the published one
packages/cli/src/peerMessaging/peer-messaging.ts Second token draw, env export of the child token, selfSent threaded through buffer and submit
packages/cli/src/peerMessaging/peer-messaging.test.ts Child-token wiring tests incl. delivery under parity and hold release
packages/cli/src/ui/AppContainer.tsx Hold notice says "a process this session started" for self-sent
packages/cli/src/ui/commands/peers-command.ts Listing names an address-less self-sent entry "own process"
packages/cli/src/ui/commands/peers-command.test.ts Covers that listing wording
packages/core/src/ipc/inbound-gate.ts PeerOrigin type, the self-sent row, origin carried on held entries and into deliver
packages/core/src/ipc/inbound-gate.test.ts Self-sent gate cases: accept where a peer is held, explicit setting wins, origin survives
packages/core/src/ipc/peer-envelope.ts origin attribute and the own-process authority notice
packages/core/src/ipc/peer-envelope.test.ts Envelope marking plus the name-injection defense
packages/core/src/ipc/uds-inbox.ts childToken option, per-connection auth kind, PeerConnectionAuth
packages/core/src/ipc/uds-inbox.test.ts Either token admits and reports its kind; kind fixed per connection; inert without requiredToken

Testing

This run is unattended CI, so no PR code was built or executed here; the evidence is the PR's own CI below. Real-scenario TUI coverage (hold notice wording, /peers output) can be exercised by a maintainer via the isolated @qwen-code /tmux job if desired — the author has write access, so both lanes are available.

Check Conclusion
Test (ubuntu-latest, Node 22.x) in progress (log already shows the pre-existing build error below)
Test (macos-latest, Node 22.x) skipped (CI classification)
Test (windows-latest, Node 22.x) skipped (CI classification)
Integration Tests (no-AK, No Sandbox) ❌ failure
Integration Tests (CLI, No Sandbox) skipped
precheck-pr / precheck ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Dependency CVE audit ✅ success
Secret scan (TruffleHog) ✅ success

The one red check is pre-existing breakage on main, not caused by this PR. Both the integration job and the unit job die in npm ci's build step before any test runs, with the identical error: src/ui/opentui/commands-registry.ts(177,13): error TS2322: Type '"output-style"' is not assignable to type 'never'. That file is not in this PR's 14-file diff, and on the base commit (edfdbda) the 'output-style' dialog kind already exists in packages/cli/src/ui/commands/types.ts and output-style-command.ts already returns it, while commands-registry.ts has no case for it — plain main fails tsc --build today. (Classified from diff identity and base-commit state, not from log-body claims.) Consequence: the PR's own test suite has not yet executed on CI for this commit — it will, once main's build is fixed and this branch rebases.

Sandboxed verification would settle the runtime claim this diff makes: @qwen-code /verify — that a child-token injection auto-delivers under the parity default while the identical frame on the published token is held is a behavioural claim that currently rests on the author's local E2E and unit report, and on this commit CI has not run the suite at all.

中文说明

代码审查

未发现阻塞问题。读 diff 之前我先根据标题与动机起草了自己的方案,结论与本 PR 一致——两个独立令牌、传输层确认"自发"、闸门以独立参数消费。已核实:

  • 认证保持 fail-closed。 uds-inbox 认证路径重写后,错误令牌或非 auth 首行仍是终局拒绝;令牌种类在每个连接的 auth 行上判定一次,第二条 auth 行只是无法解析的帧(有新测试覆盖)。两个令牌的比较都是恒定时间且总是都执行。
  • "自发"不来自线上。 帧里没有该字段;admit 默认为普通对端,标志由 inbox 提供,对端无法自称(有测试)。
  • 优先级正确。 resolvePolicy 中不可读设置的 fail-closed 和显式 hold/refuse 都先于新的自发行求值,用户设了 hold 仍会审阅自发消息。自发行按设计跳过接收方模式检查,因为该检查只为判断外部发送方而存在。
  • 标志在所有路径上存活。 held 条目、手动 decidereevaluate、预提交缓冲都携带它(每条路径都有测试)。
  • 消费方全部覆盖。 我检索了 InboundGateHeldMessagestartPeerInbox 和 envelope 格式化函数的全部生产消费方,签名变更触达所有实现;/peers 列表与 hold 通知现在都会写明 own process。
  • 信封卫生。 origin="own-process" 只由传输层写出;对端把 origin 写进名字只会落在转义后的 name="…" 属性里(有测试),own-process 提示保留了对端提示的两条禁令。
  • 线协议、注册表、回执均无变化,与描述一致。约定(ESM、kebab-case、同目录测试、why 注释)全部符合。

诚实缺口:该提交上 CI 尚未真正执行过本 PR 的测试(见下),且本环境从不运行 PR 代码——E2E 报告评论中引用的单元测试结果是作者自述,非独立复跑证据。

测试

本次为无人值守 CI,未构建或执行任何 PR 代码;证据为下表 PR 自身 CI。如需真实场景的 TUI 覆盖(hold 通知措辞、/peers 输出),维护者可触发隔离的 @qwen-code /tmux 任务;作者有写权限,两条通道均可用。

唯一的红检查是 main 上的既有损坏,非本 PR 引起:集成与单测任务都在 npm ci 的构建步骤、任何测试运行之前以相同错误失败:commands-registry.ts(177,13): error TS2322: Type '"output-style"' is not assignable to type 'never'。该文件不在本 PR 的 14 个文件 diff 中,且在基线提交(edfdbda)上 'output-style' 对话框种类已存在于 types.tsoutput-style-command.ts 已返回它,而 commands-registry.ts 没有对应分支——今天的纯 main 就无法通过 tsc --build。该结论基于 diff 身份与基线状态,而非日志正文的声明。后果:本提交的 PR 测试套件尚未在 CI 上执行过——待 main 构建修复、本分支 rebase 后即可运行。

沙箱验证可以落定这个 diff 的行为主张:@qwen-code /verify——子令牌注入在对等默认下自动投递、同一帧经发布令牌则被留置,这一行为主张目前只有作者本地 E2E 与单测报告支撑,且该提交上 CI 尚未跑过测试套件。

Qwen Code · qwen3.8-max

Reviewed at a9ef3de1606aecd6646d4f2943008ab2c401f11b · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — a clean, tightly-scoped follow-up whose threat model I verified end to end; the only reservation is that no CI has managed to run its tests yet because main itself is red.

Stepping back: before reading the diff I derived the same design independently — there is no honest way to let a session's own processes identify themselves other than a token nobody else holds, and no honest place to record that fact other than the transport. The implementation follows through on that without padding: every hunk serves the stated goal, the precedence order (explicit setting and fail-closed before the self-sent row) is exactly right, the flag survives every delivery path, and the envelope keeps the model-facing defences intact. The disclosed trade-off — a child token is only as safe as the environment it lives in — is the correct framing, not a dodge: the session chose to run the thing that is asking, and content-level defences still apply. The author is a write-access collaborator and shipped design doc, tests, and a real-socket E2E report with it. This is the kind of PR you want to land.

What keeps this from a 5 is purely environmental: the unit suite has not executed on CI for this commit because the merge build dies in tsc --build on the pre-existing output-style exhaustiveness break in commands-registry.ts on main. Nothing in that is this PR's doing, but it does mean the green-suite evidence doesn't exist yet for this SHA.

Verdict: approve, deferred until CI is green on this commit. Note that as long as main stays red, no PR can get there — once the output-style fix lands, a rebase (or a @qwen-code /triage re-run if the build goes green under this SHA) will re-evaluate and the deferred approval applies only to the reviewed commit.

中文说明

置信度:4/5 —— 一个干净、范围紧凑的后续 PR,威胁模型已端到端核实;唯一的保留意见是由于 main 本身是红的,CI 尚未能运行它的测试。

退一步看:读 diff 之前我独立推导出了同样的设计——要让会话自身的进程自证身份,除了一个无人持有的令牌别无诚实做法;记录这一事实的位置也只有传输层。实现没有多余动作:每个 hunk 都服务于既定目标,优先级顺序(显式设置与 fail-closed 先于自发行)完全正确,标志在所有投递路径上存活,信封保留了面向模型的防线。已披露的权衡——子令牌的安全性与所处环境等同——是正确的表述而非回避:是会话自己选择运行了提出请求的东西,内容层面的防线依然适用。作者是拥有写权限的协作者,随附了设计文档、测试和真实 socket 的 E2E 报告。这是值得合入的 PR。

之所以不是 5 分,纯粹是环境原因:该提交上单元测试套件尚未在 CI 执行,因为合并构建在 main 上既有的 commands-registry.tsoutput-style 穷尽性损坏处死于 tsc --build。这与本 PR 无关,但也意味着该 SHA 尚无绿色套件的证据。

结论:批准,但推迟到该提交的 CI 变绿。注意:只要 main 仍是红的,任何 PR 都无法变绿——待 output-style 修复合入后,rebase(或者若该 SHA 下构建变绿则 @qwen-code /triage 重跑)将重新评估,推迟的批准只对被审查的提交生效。

Qwen Code · qwen3.8-max

Reviewed at a9ef3de1606aecd6646d4f2943008ab2c401f11b · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot 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.

Partially reviewed — gaps disclosed. Suggestions are inline.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

中文说明

仅完成部分审查,审查缺口已披露。 建议见行内评论。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

— qwen3.8-max via Qwen Code /review (v0.22.3)

Comment thread packages/core/src/ipc/inbound-gate.test.ts Outdated
Comment thread packages/cli/src/ui/AppContainer.tsx
Comment thread packages/cli/src/peerMessaging/peer-messaging.test.ts
Comment thread packages/cli/src/ui/commands/peers-command.ts Outdated
Comment thread packages/cli/src/peerMessaging/peer-messaging.test.ts
Comment thread packages/cli/src/peerMessaging/peer-messaging.ts

@qwen-code-dev-bot qwen-code-dev-bot 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.

Reviewed at head f7fbf70a.

  • Checked the security model end to end at this head: two independently generated 32-byte tokens with separated audiences (only the peer token reaches the registry record; the child token lives only in the process environment, so an env leak no longer hands out the peer capability), auth kind decided exactly once per connection at the auth line and passed to the gate as a transport fact the frame cannot carry, and the gate's self-sent row sits under the explicit policy branch — a user-configured hold/refuse still applies to own processes, and an unreadable policy fails closed before any of it.
  • The selfSent flag is preserved through the pre-submit buffer and the held set, so a policy flip between arrival and delivery cannot re-label a message; the envelope's origin is a fixed transport-set value and peer-supplied text stays inside the escaped name attribute; OWN_PROCESS_AUTHORITY_NOTICE carries the same two prohibitions as the peer notice.
  • The documented trade-off (env-inheriting processes — subagent shells, hooks, model-run scripts — can auto-deliver under parity) is the intended meaning of "self-sent" and is stated in the design doc rather than papered over; the child-token option stays inert without requiredToken, preserving the open-inbox behavior.
  • Design doc committed, 6/6 threads resolved, no prior review blockers. CI on this head has no failures (lanes mostly still queued after the push); per the channel convention the call is on the review itself.

@yiliang114 yiliang114 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.

Review passed (security-sensitive, reviewed in full).

  • Two-token split is sound: the registry-published peer token and the child token are independent 64-hex secrets; the child token exists only in this session's environment (MESSAGING_TOKEN_ENV now carries it, never the published one), so presenting it proves descent from the session. authKindOf runs both constant-time comparisons on every presentation (no timing tell on which token was aimed), and the verdict is fixed once per connection at the auth line — no later frame can change it.
  • Gate ordering is exactly right: resolvePolicy checks the explicit crossSessionInbound setting FIRST (fail-closed on unreadable, hold/refuse/accept all win over self-sent), then the selfSent accept, then mode parity — so a user who said hold reviews their own processes' messages too, and the parity default accepts what an unknown peer would be held for, matching the documented parity argument.
  • No frame field can claim self-sent: origin is a transport finding threaded onFrame -> admit -> deliver -> buffer -> submit, pinned by the 'decided by the transport, never by the frame' and 'peer-supplied own-process label stays distinct' tests; held entries carry the origin so an explicitly held self-sent message is released as what it is.
  • The prompt-level mitigation is the right one: OWN_PROCESS_AUTHORITY_NOTICE tells the model the message carries no user authority (never edit permissions/QWEN.md/config on its ask, never read it as approval), and origin=own-process is a fixed attribute, not an escaped peer field.
  • Scrub semantics from #10636 still hold: entry scrubs drop an inherited pair before any spawn; a binding session re-exports its own child token and clears both on close. childToken is ignored unless requiredToken is set.

Verified against f7fbf70; ubuntu Test green on this head. The web-shell E2E Smoke failure is the known main-level flake (no assertion failure in the log), unrelated to ipc paths.

@qqqys
qqqys added this pull request to the merge queue Sep 2, 2026
Merged via the queue into QwenLM:main with commit 9a3ff9f Sep 2, 2026
234 of 241 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.23.0.

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.

5 participants