Commit 882b3b6
committed
agent-loop: sanitize upstream errors + cap Brave response
PR #144 round 3:
1. Inline SSE error chunks were forwarded verbatim. The chunk transform
only encrypts `delta`/`message` fields, so `error.message` stayed
plaintext on the wire — and backends sometimes echo input/validation
details in those messages, which under E2EE is data we decrypted
inside the CVM. Fix:
- Unified the existing `emit_upstream_error_chunk` into a more general
`emit_synthetic_error_chunk(message, error_type, code)` that always
emits a controlled message string — no upstream-provided text is
passed through.
- `run_iteration` now detects `outcome.upstream_error.is_some()`
immediately after `ingest_chunk_metadata`, replaces the offending
chunk with a sanitized synthetic, and breaks the iteration. The
original upstream chunk is never forwarded.
- Both the mid-loop HTTP-non-2xx path and the inline SSE-error path
funnel through the same helper.
2. Brave response was read with `response.text().await` (unbounded) and
`format_context_response` walked all entries with no size cap. A
misconfigured or compromised search backend could allocate
arbitrarily inside the proxy.
- `BRAVE_MAX_RESPONSE_BYTES = 2 MiB` enforced via streaming
`bytes_stream` read; oversize → `BraveError::Other` → tool-error
chunk, loop continues.
- `MAX_FORMATTED_OUTPUT_BYTES = 32 KiB` enforced inside
`format_context_response`; truncates on a UTF-8 boundary with a
`\n[truncated]` marker. Same string is both emitted downstream and
fed back to the model on the next iteration, so this also bounds
prompt growth across iterations.
3. The earlier disconnect regression test created a new app for the
`/v1/signature/...` check — fresh cache, so 404 was guaranteed
regardless of what the loop did. Now clones the `Router` (and
therefore its `AppState`/`ChatCache`) and queries the same instance,
so the 404 actually proves no signature was written.
New regression tests:
- `upstream_error_message_does_not_leak_prompt_or_query`: upstream emits
an error chunk whose `message` echoes the user's prompt; the
proxy's response body contains the sanitized synthetic message and
does NOT contain the prompt fragment.
- `brave_response_oversized_body_is_rejected`: Brave returns 3 MiB body
(above the 2 MiB cap); the loop surfaces it as `status:"error"` and
continues; no body bytes leak into the stream.
- `brave_formatted_output_is_truncated`: Brave returns 200 entries with
~600 B snippets each (~120 KB formatted); the emitted
`nearai_tool_result.output` is capped at 32 KiB + marker.
Existing `mid_stream_error_chunk_skips_done_and_signature` updated:
now asserts the sanitized message is on the wire and the original
`"BadRequestError"` / `"request was aborted"` strings are NOT.
Totals: 302 unit + 1 main + 15 agent_loop (was 12) + 134 integration =
452 tests, all pass. Clippy + fmt clean.1 parent ac2e749 commit 882b3b6
2 files changed
Lines changed: 397 additions & 44 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
41 | 53 | | |
42 | 54 | | |
43 | 55 | | |
| |||
338 | 350 | | |
339 | 351 | | |
340 | 352 | | |
341 | | - | |
342 | | - | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
343 | 360 | | |
344 | 361 | | |
345 | 362 | | |
346 | 363 | | |
347 | 364 | | |
348 | 365 | | |
349 | | - | |
350 | | - | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
351 | 369 | | |
352 | 370 | | |
353 | 371 | | |
| |||
722 | 740 | | |
723 | 741 | | |
724 | 742 | | |
| 743 | + | |
| 744 | + | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
725 | 770 | | |
726 | 771 | | |
727 | 772 | | |
| |||
947 | 992 | | |
948 | 993 | | |
949 | 994 | | |
950 | | - | |
951 | | - | |
952 | | - | |
953 | | - | |
954 | | - | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
| 998 | + | |
| 999 | + | |
| 1000 | + | |
955 | 1001 | | |
956 | | - | |
| 1002 | + | |
957 | 1003 | | |
958 | 1004 | | |
959 | 1005 | | |
960 | 1006 | | |
961 | 1007 | | |
962 | 1008 | | |
963 | | - | |
964 | | - | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
965 | 1012 | | |
966 | | - | |
967 | | - | |
968 | | - | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
969 | 1020 | | |
970 | 1021 | | |
971 | 1022 | | |
972 | 1023 | | |
973 | | - | |
974 | | - | |
975 | | - | |
976 | | - | |
977 | | - | |
| 1024 | + | |
978 | 1025 | | |
979 | 1026 | | |
980 | 1027 | | |
981 | 1028 | | |
982 | 1029 | | |
983 | 1030 | | |
984 | 1031 | | |
985 | | - | |
986 | | - | |
987 | | - | |
| 1032 | + | |
| 1033 | + | |
| 1034 | + | |
| 1035 | + | |
988 | 1036 | | |
989 | 1037 | | |
990 | 1038 | | |
| |||
1101 | 1149 | | |
1102 | 1150 | | |
1103 | 1151 | | |
1104 | | - | |
1105 | | - | |
1106 | | - | |
| 1152 | + | |
| 1153 | + | |
| 1154 | + | |
| 1155 | + | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
| 1160 | + | |
| 1161 | + | |
| 1162 | + | |
| 1163 | + | |
| 1164 | + | |
| 1165 | + | |
| 1166 | + | |
| 1167 | + | |
| 1168 | + | |
1107 | 1169 | | |
1108 | | - | |
| 1170 | + | |
1109 | 1171 | | |
1110 | 1172 | | |
1111 | 1173 | | |
| |||
1162 | 1224 | | |
1163 | 1225 | | |
1164 | 1226 | | |
| 1227 | + | |
| 1228 | + | |
| 1229 | + | |
| 1230 | + | |
| 1231 | + | |
1165 | 1232 | | |
1166 | 1233 | | |
1167 | 1234 | | |
1168 | | - | |
| 1235 | + | |
| 1236 | + | |
1169 | 1237 | | |
1170 | 1238 | | |
1171 | 1239 | | |
| |||
1195 | 1263 | | |
1196 | 1264 | | |
1197 | 1265 | | |
1198 | | - | |
1199 | | - | |
| 1266 | + | |
| 1267 | + | |
| 1268 | + | |
| 1269 | + | |
| 1270 | + | |
| 1271 | + | |
| 1272 | + | |
| 1273 | + | |
| 1274 | + | |
| 1275 | + | |
| 1276 | + | |
| 1277 | + | |
| 1278 | + | |
| 1279 | + | |
| 1280 | + | |
| 1281 | + | |
1200 | 1282 | | |
1201 | | - | |
1202 | | - | |
| 1283 | + | |
| 1284 | + | |
| 1285 | + | |
1203 | 1286 | | |
1204 | 1287 | | |
1205 | 1288 | | |
| |||
0 commit comments