行式输入安全(INV-LINE-01)

bufio.Scanner 超限时 Scan() 返回 false——和 EOF 同一个返回值。本文档描述引擎如何防止这类静默截断。


核心问题

scanner := bufio.NewScanner(reader)
for scanner.Scan() {
    line := scanner.Text()
    process(line)
}
// scanner.Scan() 返回 false:是 EOF?还是一行太长?
// 答案:两者返回同一个值。循环正常退出、函数返回 nil。

默认上限 64KB 不是谁决定的,它只是不写 .Buffer() 时手边的那个值。


历史缺陷(同一形状犯了三次)

场景后果
Cell 导入丢 64KB 以上的行数据丢失
邮件头解析停在 Auto-Submitted: 之前安全绕过(防环机制被长 References: 头绕过)
pip list 停在首个长行能力误判(低报已安装包)

解决方案

内存中的输入:bytes.Lines

// 零拷贝、无上限
for line := range bytes.Lines(data) {
    process(line)
}

流式输入:internal/infra/lineio

// 超限报告而非结束流,下一个换行处重新同步
reader := lineio.NewReader(r, maxLineBytes)
for {
    line, truncated, err := reader.ReadLine()
    if err == io.EOF { break }
    if truncated {
        // 显式处理截断
    }
    process(line)
}

网络 body:显式 Buffer

scanner := bufio.NewScanner(body)
scanner.Buffer(make([]byte, 0, client.MaxEventBytes), client.MaxEventBytes)

上限一律命名常量,取自生产者上界。


上限命名规则

同一条流只有一个名字:

流命名常量值来源
SSE 事件行client.MaxEventBytes32MBSSE 行长上限
read 工具单次—10MB工具契约

cmd/wesgine-accept 的 4MB 坐在 read 工具的 10MB 之下——一次大读会在终态 Done 之前中断,然后被报告成引擎缺陷。


截断处置

截断的处置必须显式选择,不能静默跳过:

致命(必须完整)

上游 LLM delta——tool_call 参数是 JSON 片段流,丢一片就拼不回来而流看起来一直健康。

跳过该条继续

wesgine 事件——自包含,丢一条只赔一条。


lineio 内部约束

internal/infra/lineio 内部禁止使用 Scanner:


反模式速查

反模式正确做法
bufio.NewScanner 读外部/无界输入内存用 bytes.Lines;流式用 lineio
对着 socket 用默认上限的 Scanner必须显式 .Buffer(...),上限用命名常量
超限当 EOF 处理截断处置显式选择:致命或跳过继续
上限写成字面量取自生产者上界,一律命名常量
lineio 内部用 Scanner闸门禁止

闸门

scripts/audit-lock/40-bounded-line-reads.sh 扫描全仓 Scanner 用法。