标签: 权限边界

  • 开发日志工具的三个权限等级:转储、音频导出与状态刺激

    结论:四个开发脚本的权限差异很大,不应统称「开发插件」。dump.py 与 filter_log_message.py 主要读取消息/日志,extract_audio.py 会导出含乘员信息的音频,而 cycle_alerts.py 会主动发布核心状态主题(selfdriveState、deviceState、pandaStates)。至少两个反直觉的事实:--json 对 Cap’n Proto 原始字节调用 json.loads 通常无法解析,真正转 JSON 的是 --dump-json;cycle_alerts.py 先声明九个告警却随即重赋值为只剩两项,实际只循环 cameraMalfunction 与 cameraFrameRate。远端 --addr 不能当作权限、脱敏或租户隔离策略。

    四个脚本,三种权限等级

    把数据流和副作用放在一起看,权限差异一目了然:前三个工具的输出主要是「读出来的内容」,第四个则会争用发布入口。

    工具 数据流 实际输出或副作用
    dump.py raw_live_logreader() 订阅指定地址主题,默认为 SERVICE_LIST 全部服务;每条原始字节先按 log.Event 解码 可打印 Event、十六进制或字段;--addr 非环回时底层切换 ZMQ 并重置消息上下文
    filter_log_message.py 有路段参数时用排序后的 LogReader 查 logMessage、errorLogMessage、operatingSystemLog;无路段时实时订阅前者与 OS 日志 打印 JSON 结构化日志或 OS 日志;实时模式未订阅 errorLogMessage
    extract_audio.py ReadMode.AUTO_INTERACTIVE 选路段来源,LogReader.filter("rawAudioData") 得载荷,拼成 int16 样本 经 write_wav_file() 导出单声道 WAV,或经 play_audio() 本机播放
    cycle_alerts.py 构造 Honda 参数与随机化 SubMaster 状态,用 Events.create_alerts()、AlertManager 生成告警 每 200 个控制周期发布 selfdriveState、deviceState、pandaStates,循环不停

    dump.py 与 filter_log_message.py 的失效点

    dump.py 即使指定 --no-print 仍逐条解析 Event;--pipe 用 str(msg) 写标准输出,得到的是 Python 字节表示文本而不是原始二进制流;--json 对原始字节调用 json.loads 通常失败。--values 只检查路径第一段存在,后续 getattr 缺失可直接抛错。顶层 codecs.register_error("strict", ...) 还会影响进程内使用该错误处理器的解码行为,插件封装时不应默许这种全局修改。

    filter_log_message.py 的坑集中在时间与字段两处。等级过滤只作用于结构化日志,不影响 OS 日志;离线非 --absolute 的时间从第一条读取到的消息起算,而不是第一条匹配日志,且多个路段共享该时间原点、段间不重排序;实时模式则用原始 logMonoTime 纳秒换算秒数,两者语义不同。print_logmessage() 只捕获 JSON 解析异常,若 JSON 缺 levelnum、filename 或 msg 会抛键错误。

    工具 易踩边界 原因
    dump.py --json 通常解析失败 对 Cap’n Proto 原始字节调用 json.loads;转换应使用 --dump-json
    dump.py 解码行为被全局改写 顶层注册了 "strict" 错误处理器
    filter_log_message.py 相对时间原点不是首条匹配日志 离线时间从第一条读取到的消息起算,多路段共享
    filter_log_message.py 实时模式拿不到错误日志 实时订阅未包含 errorLogMessage

    两个工具都会打印原始事件或日志内容,可能包含标识符、位置、进程路径及异常栈,因此远端 --addr 不能作为生产环境的权限、脱敏或租户隔离策略。

    extract_audio.py:WAV 连续性与隐私

    extract_audio.py 把所有 rawAudioData.data 按 NumPy 原生端序的 int16 视为连续样本,再原样写成 WAV,未显式转换字节序。它只取第一条的 sampleRate,不检查之后的采样率变化、通道数、消息时间戳或缺包间隔,因此导出文件不承诺与原车时间轴严格连续。它先把所有消息和样本拼成内存数组,长路段可能占用较大内存。

    隐私侧还有一个关键前提:rawAudioData 缺省 should_log=False,loggerd 只有在启动时读取 RecordAudio 为真才订阅该主题。所以没有音频消息不等于麦克风硬件故障;而一旦录制,导出的语音可能含高度敏感的乘员信息。使用上,输出文件已存在时会交互确认,--play 会使用本机音频设备;只传 --play 或 -o 而不传路段名,主程序仍执行 args.route_or_segment_name.strip(),会报 AttributeError。

    cycle_alerts.py:发布核心状态的边界

    cycle_alerts.py 先声明九个可听告警,但随后把 alerts 重赋值为仅 cameraMalfunction 与 cameraFrameRate 两项,本快照实际只循环这两项。它随机改写 SubMaster 内的设备状态、模型速度、进程列表和主题 alive/valid/freq_ok,没有对这些订阅数据调用 sm.update(),由此产生的告警文本是随机夹具结果,不是车辆健康检查。

    发送消息由 new_message() 构造,默认 Event valid=False,脚本没有覆写。即便如此,在实际设备上争用 selfdriveState、deviceState、pandaStates 的发布入口仍会干扰 UI 和状态消费者,不能作为行车调试插件。脚本没有发送 sendcan 或直接控制 ECU,但这不等于发布核心状态没有风险。

    硬件与插件化约束

    建议把原始消息/日志检索、音频导出、状态刺激拆成三个权限等级,而不是统称「开发插件」:观测插件仅获指定主题、时间范围及字段脱敏后的只读视图;音频导出需要单独授权、留审计并限定本地文件生命周期;状态刺激仅允许仿真进程或隔离测试总线,不能发布到生产 deviceState/pandaStates。源文档明确这些是重构要求,不是当前工具已有的隔离保证。

    常见问题

    dump.py 的 –json 为什么常常解析失败?

    因为它对 Cap’n Proto 原始字节直接调用 json.loads(msg),通常无法作为 JSON 解析。源文档指出真正把 Event 转为 JSON 文本的是 --dump-json;--pipe 得到的也只是 Python 字节表示文本,不是原始二进制流。

    导出的音频能当作和原车同步的时间轴证据吗?

    不能。extract_audio.py 只取第一条的 sampleRate,不检查之后的采样率变化、通道数、消息时间戳或缺包间隔,而且是按 NumPy 原生端序把 int16 视为连续样本原样写 WAV,因此导出文件不承诺与原车时间轴严格连续。

    cycle_alerts.py 会循环几个告警?

    本快照实际只循环两个:cameraMalfunction 与 cameraFrameRate。脚本先声明九个可听告警,随后把 alerts 重赋值为这两项,且告警文本来自随机夹具结果,不是车辆健康检查。

    这些开发工具能当作「只读」吗?

    不能一概而论。前三个工具主要读取数据,但远端 --addr 不是权限或脱敏策略;cycle_alerts.py 则主动发布三个核心状态主题,会干扰 UI 与状态消费者。音频导出还涉及高度敏感的乘员信息,应单独授权与审计。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/DEV_LOG_AUDIO_ALERT_TOOL_BOUNDARIES_DEEP_DIVE.md(取用了四个开发脚本的数据流与副作用、转储与日志过滤失效点、音频导出连续性与隐私门、告警刺激的真实范围)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • Athena 远程 RPC 与 WebRTC:设备 JWT 不等于逐方法授权

    结论:设备身份 JWT 只是建立 Athena 长连接的「门票」,不是本机每个 RPC 方法的授权。本地方法映射没有逐方法角色/权限检查,getMessage() 只要求服务名存在于 SERVICE_LIST;而 WebRTC 本机服务默认监听 127.0.0.1:5001,其 /stream 请求体可自选数据通道名单。因此远程 RPC 与直播应归入独立、非实时的服务域,并把 sendcan 等控制路径排除在通用数据通道之外。

    连接、调度与故障路径

    manager_thread() 在 DongleId 缺失或为 UnregisteredDevice 时,不启动 manage_athenad/uploader。athenad.main() 用本地私钥签发身份 JWT,作为 jwt cookie 连接 ATHENA_HOST(默认 wss://athena.comma.ai),断开后以带随机抖动的退避重连。网络会话的服务端授权规则不在本地仓库中。

    组件 关键函数 边界
    接收 ws_recv() 文本/二进制帧进入共享 recv_queue;收到 ping 写 LastAthenaPingTime,超过 70 秒无 ping 触发重连。
    RPC jsonrpc_handler()、rpc.handle() 以方法名查全局 dispatcher,异常转 JSON-RPC 错误;本地方法映射没有逐方法角色/权限检查,也不验证 jsonrpc 版本字段。
    发送 send_queue_push()、ws_send() 回复高优先级、日志转发低优先级;按 4096 字符分 WebSocket 帧。共享队列在重连时仍是进程级对象,不能当成精确一次的 RPC 事务。
    活性 ws_manage() 根据 IsOffroad 调整 TCP keepalive;仅是连接活性参数,不决定 RPC 授权范围。

    rpc.handle() 对未识别方法返回 METHOD_NOT_FOUND,参数类型错误返回 INVALID_PARAMS,普通异常返回 SERVER_ERROR;这些错误映射是协议处理,不是权限策略。连接建立后 handle_long_poll() 会启动接收、发送、连接管理、四个上传、日志转发和可配置数量的 RPC worker;worker 将请求原文写事件日志,敏感载荷需另行评估。上传生命周期还有几处状态语义:HTTP 200/201/401/403/412 均结束该队列项,不能把队列移除等同于远端成功存储;cancelUpload() 只标记队列内 ID,正在执行的传输并未由该函数直接中止。此外片段创建在非 PC 平台要求 IsOffroad。

    RPC 能力清单

    能力 关键入口 源码约束与注意点
    设备与网络查询 getVersion()、getPublicKey()、getSimInfo() 查询版本、设备公钥、SIM/网络元数据;公开公钥不同于公开私钥,但返回数据仍须考虑设备隐私。
    消息读取 getMessage() 可订阅任一注册服务并等待一条消息。该 RPC 是广范围读能力,不能直接授予通用第三方插件。
    日志枚举 listDataDirectory()、scan_dir() 从 Paths.log_root() 递归列路径并按前缀筛选;递归时不跟随目录符号链接。
    指定 URL 上传 uploadFilesToUrls()、_do_upload() 队列入口拒绝空、绝对或含 .. 的文件名,检查文件存在,随后向请求中的 URL 发 PUT;没有目的域名 allowlist。
    上传生命周期 upload_handler()、cancelUpload() 过期 31 天丢弃、计费网络可推迟/中断、特定网络错误重试至最多 30 次。队列移除不等于远端成功存储。
    本地代理 startLocalProxy() 仅允许 LOCAL_PORT_WHITELIST={22}(8022 兼容映射到 22),再桥到 127.0.0.1;白名单约束本地目标端口,不是对 remote_ws_uri 的域名校验。
    直播 startStream() 固定宽路摄像头、输出 carState/deviceState;仅在持久 CarParams.notCar 为真时声明入站 testJoystick。

    WebRTC 本机入口与数据桥

    webrtcd.main() 默认监听 127.0.0.1:5001,HTTP handler 提供 /stream、/schema、/notify,但在 WebrtcdHandler._dispatch_request() 中没有独立鉴权检查。不能据此推断外部网络可直接访问默认监听地址;本机其他进程、端口转发或修改 --host 后的暴露面需要单独控制。

    handle_get_stream() 只要求 JSON content type。会话时限很具体:生成 Answer 限时 30 秒,建立连接限时 15 秒,普通会话最长 300 秒;包含 testJoystick 的 body 会话走不限时等待断开的分支。无会话 5 秒后 schedule_teardown() 清除 IsLiveStreaming。

    数据桥的关键不对称在于:远端 Athena 的 startStream() 固定了服务名单,但直接调用本机 /stream 的请求体可自选名单;StreamSession.message_handler() 对入站普通消息仅按本会话 bridge_services_in 名单过滤,代码中没有按主题风险等级的统一许可层。所以必须区分「默认 Athena 调用约束」和「本机服务自身约束」。

    /schema 仅检验字段属于 log.Event 且非废弃字段,/notify 向当前会话广播提供的 JSON;CerealOutgoingMessageProxy.update() 把订阅主题转 JSON 发数据通道。handle_post_notify() 的假通道探针确认:通知按当前会话列表逐个广播,某通道发送异常被忽略,即使没有会话也返回 HTTP 200 OK——该返回值只代表本机 handler 完成遍历,不是远端客户端已收到通知的回执。

    自制硬件与插件化要求

    1. 将远程 RPC 与直播纳入独立、非实时服务域。插件注册清单需声明具体 RPC、日志文件和 cereal 主题读写权限;不能把设备身份 JWT 直接作为所有本机方法的全权令牌。
    2. 本机 /stream、/notify 和桥接写主题应由受信任调用方认证并用固定 allowlist 约束;尤其是 sendcan、carControl 等控制路径不能成为通用数据通道的可写目标。
    3. 上传能力应限制可访问目录的真实路径、目标 URL/域名与重定向策略,并把「服务端接受」「传输完成」「HTTP 拒绝」「重试耗尽」表示为不同状态。
    4. 验证矩阵至少包括:断网/重连时队列隔离,恶意 RPC 参数与超大消息,本机端口被非预期进程访问,symlink/路径边界,计费网络切换,会话竞争与清理,权限拒绝及行车状态切换。

    以上均为建议的重构验收,不是当前源码已经提供的保证;现有测试并未证明服务端授权、HTTP 本机访问控制、敏感主题限制、外发 URL 范围或实车会话时序。

    常见问题

    设备 JWT 等于本机方法的授权吗?

    不等于。JWT 只用于建立 Athena 长连接;rpc.handle() 以方法名查全局 dispatcher,本地方法映射没有逐方法角色/权限检查。因此不能把设备身份 JWT 当作所有本机方法的全权令牌。

    getMessage() 有按主题分级的权限吗?

    没有。它只要求服务名存在于 SERVICE_LIST,没有按主题敏感度进一步分级。这是广范围读能力,不能直接授予通用第三方插件。

    WebRTC 本机服务是默认对外开放的吗?

    不是。它默认监听 127.0.0.1:5001,且 _dispatch_request() 中没有独立鉴权检查。但本机其他进程、端口转发或修改 --host 后的暴露面需单独控制,不能据此断言对外可访问。

    为什么说 /stream 的名单约束与 Athena 的不是一回事?

    因为远端 Athena 的 startStream() 固定了服务名单,而直接调用本机 /stream 的请求体可自选名单;message_handler() 仅按本会话 bridge_services_in 过滤,没有按主题风险等级的统一许可层。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/REMOTE_RPC_WEBRTC_BOUNDARIES_DEEP_DIVE.md(取用了 Athena 连接与调度、RPC 能力清单、WebRTC 本机入口与数据桥、插件化要求)。
    • 项目 02 —— openpilot-research/DEVICE_IDENTITY_REGISTRATION_DEEP_DIVE.md(仅用于确认设备 JWT 的来源与注册前提)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11