--- id: review-001-forward-response-fidelity name: Upstream response decode — vendored JSON, size caps, loud auth errors, error bodies (FWD-07, FWD-08, FWD-10, FWD-12) status: pending depends_on: [] scope: moderate risk: medium impact: component level: implementation tags: [adapters, review-001] --- ## Description Review 001 findings on `src/adapters/forward.rs`'s response-decode paths (`forward`, `forward_stream`), minus the SSE parser (its own task): - **FWD-07**: `content_type.contains("application/json")` (`forward.rs:236`) misses `application/vnd.api+json`, `application/problem+json`, etc. → vendor JSON decoded as a `Value::Array` of one `Number` per byte. No response size limit on any read path (`json()/text()/bytes()`) — a hostile upstream controls caller memory. `forward_stream` never checks content-type (200 HTML → empty stream, no error). Fix: mime-essence matching (`application/.*+json` suffix semantics), response size caps on all read paths, and a loud error for non-SSE content on a Sub op. - **FWD-08** (`forward.rs:104-126`, `:87-94`): invalid credential values (control characters, typo'd header name) silently drop the header — the request goes out unauthenticated and the caller only sees the eventual upstream 401. Fail loudly at call time. (No leak risk today — nothing is logged; keep it that way.) - **FWD-10** (`forward.rs:215-227`, `:333-346`): upstream error bodies discarded — surfaced message is only `"HTTP {status}: {reason}"`; upstream diagnostics never reach the caller and unconsumed bodies hinder connection reuse. Surface a bounded error-body echo on the error path; keep the `HTTP_` mapping as is (correct). - **FWD-12**: byte-identical duplicates `value_to_path_segment`/ `value_to_query` (`:133-151`); `forward`'s `op_type` parameter only toggles `ACCEPT` and its `text/` branch would buffer an entire SSE stream if a Sub were ever routed here (currently unreachable, inviting misuse — remove or make safe); the `unwrap_or_else(|_| "null")` at `:197` masks an unreachable serialization failure with a null body; `SharedHttpClient::reload` performs two separate `ArcSwap::store` calls. ## Acceptance Criteria - [ ] `application/vnd.api+json` (and `application/problem+json`) decode as JSON, not byte arrays (test) - [ ] Response size cap enforced on JSON/text/binary read paths (test) - [ ] Non-SSE content-type on a Sub op produces a loud error, not an empty stream (test) - [ ] Invalid credential values fail loudly at call time (test), no credential material logged - [ ] Upstream 4xx/5xx bodies surface bounded in the error envelope (test) - [ ] Duplicate `value_to_*` helpers collapsed; `text/` buffering branch removed or safe; reload atomic - [ ] `forward.rs` COV-01 uncovered regions (binary branch, auth arms, non-2xx mapping, content-type branches) substantially covered - [ ] `cargo test` and `cargo clippy --all-targets -- -D warnings` pass ## References - docs/reviews/001-initial-implementation-review.md (Part D, FWD-07, FWD-08, FWD-10, FWD-12; Part I, COV-01) ## Notes > Agent fills during implementation. Shares forward.rs with > review-001-forward-url-safety (request side) and > review-001-sse-parser — sequence those first or coordinate. ## Summary > Filled on completion.