elky
579f2c7cc1
feat(security): harden gateway boundaries and usage policies
...
Consolidate subscription usage policy enforcement, privacy-safe persistence, and gateway security hardening into one reviewable change.
Includes bounded HTTP and execution envelopes, header and protocol guards, DNS and relay validation, authentication and secret projection hardening, secure backup/install paths, and regression coverage.
2026-09-04 03:45:52 +08:00
ZheFox
654f798d25
fix(ws): harden Responses continuation state
2026-08-20 08:51:16 +08:00
ZheFox
c8118edf36
fix(ws): harden Responses connection lifecycle
...
Revalidate control policy per turn, isolate downstream credentials, and make planner/turn ownership cancellation-safe.
Preserve opaque protocol events, align configurable timeout semantics, and extend end-to-end security and settlement coverage.
2026-08-17 18:50:29 +08:00
AAEE86
4a0775c4ea
style: apply cargo fmt across gateway and aether-ai crates
2026-08-17 14:53:53 +08:00
AAEE86
6fc02dad3e
fix(ws): restore redacted PII in provider frames before client delivery
...
Responses WebSocket 只实现了脱敏的一半:请求侧 mask 之后,provider 事件帧在推给
客户端之前没有还原,于是 session 映射内的占位符以 <AETHER:EMAIL:...> 的形式直接
透给客户端。这里补齐响应侧,语义与 HTTP 路径对齐。
- 还原点是 relay loop 的最后一跳(send_client_message 之前、capture_client_frame
之前),对应 HTTP 的 restore_sync_response_body / StreamingResponseRestorer 所在
位置。审计与终态观测继续消费脱敏态事件,只有发往客户端的那一份拷贝被还原。
- 复用 privacy::restore_json_strings(改为 pub(crate))与
RedactionSession::restore_text,不复制任何还原逻辑:只还原本 session mask 过的
映射,未映射的占位符原样保留;type / model / id 等协议字段不可能命中 sentinel,
因此不受影响。批量 {"chunks":[...]} 帧一并递归还原。
- session 生命周期:mask 仍然是 per-turn(slot 依旧每轮新建),但 session 改由连接
持有,按有界 FIFO 留最近 8 轮。理由是 WS 的会话历史留在上游,continuation 只发
增量输入,per-turn 释放会漏还原后续响应里回显的更早轮次占位符;HTTP 不会漏,是
因为它每次重发整段历史、重新 mask 会派生出同一个 sentinel。被挤出窗口的轮次退回
「占位符原样透传」,不会错误还原成别的值。
- 未命中还原时不改写字节;连接上没有任何 mask session 时(未启用脱敏)连事件 clone
都不做。
测试:redaction.rs 新增 8 条单测(还原命中/批量帧/未映射占位符原样/未命中不改写/
无 session 不介入/空 session 不留存/审计侧入参不被改写/跨轮还原/窗口有界);
responses_websocket_e2e 新增一条用例,mock 上游回显收到的 input,断言上游只看到
占位符而客户端拿到真实邮箱。
2026-08-17 14:53:46 +08:00
AAEE86
1c5ee5228c
refactor(ws): 用 ResponsesTurnState 收敛连接 turn 状态
...
评审第 2 条:BoundResponsesConnection 用 response_in_flight、active_turn、
active_response_create 三个可独立变化的字段编码同一件事,8 种组合里只有 3 种
合法,非法组合只能靠调用点的 if 和「记得同时改另外两个字段」来避免。
三字段合并为一个 ResponsesTurnState:
Idle 没有进行中的 logical turn
Responding { logical, attempt } logical 与 attempt 必须同时存在
Replanning { logical } attempt 已取走去结算/重绑,logical 仍在
Replanning 不是新概念:配额透明重试期间现状就处于这个状态,只是靠
Option::take 意外得到。转换只能走 begin / detach_attempt / resume / end,
response_in_flight 与「是否接受新 response.create」都由变体推导。
由此消除的运行时不变量(原来全靠调用点自觉):
- 有 attempt 必有 logical turn
- response_in_flight 与 attempt 同生共死(原来 client 写失败后
active_turn=None 而 response_in_flight 仍为 true)
- logical turn 结束时必须清 attempt:原来 `active_response_create = None`
在 connection.rs 里手写 13 处,漏一处就残留;现在只有 end() 一个出口
- 上游绑定返回的连接不再自带 response_in_flight=true 的半成品状态
同时删除 update_response_in_flight:Started 帧把已经是 true 的字段再设一次,
Close 帧因为没有解析出的 frame 而根本不触发,是纯冗余写;它在 Idle 态收到
Started 帧时还会把 response_in_flight 置真,从而永久阻塞后续 response.create。
行为等价。ActiveResponsesWebSocketRequest 改名 LogicalTurn 并随状态机移入
新的 turn_state.rs;状态机对 attempt 类型泛型化,测试用轻量替身驱动同一套
转换逻辑,无需 AppState 或真实 socket。
2026-08-17 14:52:57 +08:00
AAEE86
9d80281b53
fix(ws): route WS planning and continuation through PII redaction
2026-08-17 14:52:49 +08:00