Skip to content

fix(acp): 选择认证方法时不判别 AuthMethod.type,可能把 terminal 方法传给 authenticate(规范禁止) #147

Description

@YoungSx

结论

挑选认证方法时只判 Id 非空、完全不读 Type,因此一旦 agent 广告了 type: "terminal" 的认证方法,我们会把它当普通方法塞进 authenticate 调用——而规范明确写着 client MUST NOT 这么做。这是一处 fail-open。

证据链

1. 规范:终端认证方法禁止传给 authenticate

v1 schema ## AuthMethodTerminal/tmp/acp-v1-schema.md:1963)原文:

Terminal-based authentication method.
The client runs the configured agent program as a separate interactive process for the user to authenticate via a TUI. Agents MUST advertise this method only when the client enabled its terminal authentication capability.
A zero exit status signals success; any other termination signals failure.
The client MUST NOT pass this method to authenticate.

同节说明 AuthMethod 是判别联合,type 缺省视为 agent

AuthMethod

Describes an available authentication method.
The type field acts as the discriminator in the serialized JSON form.
When no type is present, the method is treated as agent.

2. 我们的选择逻辑不看 Type

src/SalmonEgg.Presentation.Core/ViewModels/Chat/ChatAuthenticationCoordinator.cs:176-177

private AuthMethodDefinition? GetPrimaryAuthMethod()
    => _advertisedAuthMethods?.FirstOrDefault(static method => !string.IsNullOrWhiteSpace(method.Id));

调用点 ChatAuthenticationCoordinator.cs:118-141——挑到就直接发 authenticate,中间没有任何 Type 过滤:

var method = GetPrimaryAuthMethod();
if (method == null || string.IsNullOrWhiteSpace(method.Id))
{
    // ... 标记需要认证后返回
    return false;
}
// ...
await chatService
    .AuthenticateAsync(new AuthenticateParams(method.Id), cancellationToken)
    .ConfigureAwait(false);

3. Type 字段本身是存在的——只是没人读

src/SalmonEgg.Acp/Protocol/AuthMethodTypes.cs:25

public string? Type { get; init; }

序列化侧也已按规范处理缺省(InitializeTypes.cs:1141):

writer.WriteString("type", string.IsNullOrWhiteSpace(authMethod.Type) ? "agent" : authMethod.Type);

即:建模层做对了,消费层漏了判别

$ grep -rn 'AuthMethodTerminal\|terminalAuth' src/ | wc -l
0

4. 为什么现在还没炸:靠对端自律,不是靠我们

规范说 agent 只应在 client 广告了终端认证能力时才发 terminal 方法。我们的 ClientCapabilities 没有 auth 字段(src/SalmonEgg.Acp/Protocol/InitializeTypes.cs:107-126fs / terminal / session),所以守规矩的 agent 不会发。

但这构成 fail-open 而非 fail-closed:

  • 规范里 client 侧 AuthCapabilities.terminal 默认 false,缺省语义等价于「不支持」,因此不广告本身没错;
  • 问题在于我们没有对应的接收侧防线。任何行为不合规、或把 terminal 当默认值发的 agent,都能让我们把不该传的 methodId 传出去。按项目既有原则(AGENTS chore: add Ko-fi sponsorship link #81「禁止 legacy root 字段、未声明能力执行」),这种「对端不违规我们才安全」的写法不能留。

影响

  • 对端广告 terminal 方法时,我们发出协议禁止的 authenticate 调用。最好的情况是 agent 回错误、用户看到一条无可操作性的失败提示;坏的情况是走进未定义行为。
  • 用户完全没有终端认证这条路可走(需要 TUI 交互登录的 agent 无法在本产品内完成登录)。

建议范围

两件事,可分两步做,第一步是止血:

  1. 止血(小)GetPrimaryAuthMethod() 增加判别——只接受 Type 为空或 "agent" 的方法。遇到 terminal 或未知非 _ 前缀类型时,不得塞进 authenticate,改为走「需要认证但当前无可用方法」分支(该分支已存在,见 :121-127)并给出可操作提示。
  2. 补齐能力(大,可独立排期):实现终端认证——ClientCapabilities.auth.terminal 广告、按 AuthMethodTerminalargs / env 在配置好的 agent 启动命令上追加参数并以交互式终端拉起、以退出码 0 判定成功。这一步依赖交互式终端承载能力,与 issue 中的 _interaction / 终端族现状有关联,宜单独评估。

验证要求

  • 构造广告 type: "terminal" 的假 agent:断言我们没有发出 authenticate(断言线上报文,不只断言返回值)。
  • 构造广告未知 type(如 "_vendor_x""future_thing")的假 agent:同样不得发出 authenticate
  • 缺省 type(无该字段)仍按 agent 正常走通,避免修过头把正常路堵死。
  • 反向验证:临时把判别拆掉,上述断言必须转红——否则是假绿。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions