fix(oauth): thread scoped fetch through high-level clients#2390
fix(oauth): thread scoped fetch through high-level clients#2390bokelley wants to merge 1 commit into
Conversation
| .filter(t => t.inputSchema?.properties) | ||
| .map(t => [t.name, t.inputSchema!.properties as Record<string, unknown>]) | ||
| ); | ||
| if (!usesScopedFetch) this.cachedToolSchemas = discoveredToolSchemas; |
There was a problem hiding this comment.
Medium: Under scoped fetch, this.cachedToolSchemas is never populated, which breaks the invariant at L2243 (adaptRequest "runs after detectServerVersion so cachedCapabilities/tool schemas are populated"). executeTask → detectServerVersion → getCapabilities (which returns without caching here) → adaptRequest reads this.cachedToolSchemas (L2287) and finds nothing, so it takes the fail-open path: no undeclared-field stripping and no v2.5 adapter alias reconciliation. That reconciliation is exactly what prevents the documented Wonderstruck rejection (brand_manifest string into a brand object slot, L2295). A hosted runner using scoped fetch against a v2/v2.5 seller loses it silently. Thread discoveredToolSchemas into the adapt path per call rather than skipping it entirely — tenant isolation only requires not caching, not discarding the schema for the current call.
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — request-scoped fetchFn threaded cleanly through the high-level clients, OAuth discovery/refresh, MCP/A2A calls, and compliance/storyboard runners.
What I checked:
- SSRF boundary is handled honestly:
ssrfSafeFetchstill validates scheme + resolved address and marksconnectionPinned: falsewhen the caller'strustedFetchFnassumes DNS pinning, with delegation documented at the publicTransportOptions.fetchFnsurface. - Multi-tenant isolation: caches are correctly bypassed under scoped fetch.
- Changeset present (
.changeset/scoped-fetch-high-level.md) covering thesrc/lib/**changes. - No witness-not-translator violations, no reimplemented HTTP/SSE transport.
Medium findings
- src/lib/core/SingleAgentClient.ts:4058 — Scoped fetch skips
cachedToolSchemas, disablingadaptRequestfield-stripping and v2.5 alias reconciliation. A quality/adaptation degradation on the scoped-fetch path, worth a follow-up.
Decision path: no critical/high findings (row 1 no); gated_paths false (row 2 no); high_risk true but all reasons are (modified) — no (deleted) (row 3 no); the single medium finding is category tool-schema/adaptation, not data-loss/schema/infra (row 4 no); high_risk (modified) + medium would fire row 5 — this is a modified high-risk file (SingleAgentClient.ts)... note: the medium finding is on SingleAgentClient.ts, which is NOT in the high_risk_reasons list (auth, protocols/* are). Row 5 requires a medium finding AND a modified high-risk-path file; the medium is not on a high-risk-path file, so row 5 does not fire. No prior escalate (row 6 no); no team gate (row 7 no); only 1 medium finding, under the 3-medium threshold (row 8 no). Falls through to row 9 → approve.
Medium findings
- src/lib/core/SingleAgentClient.ts:4058 — Scoped fetch skips cachedToolSchemas, disabling adaptRequest field-stripping and v2.5 alias reconciliation
Summary
Root cause
PR #2381 exposed the hook at the low-level transport, but high-level callers could not consistently supply or retain it across discovery, authentication, and protocol calls.
Validation
Fixes #2386
Fixes adcontextprotocol/adcp#5962