fix(webfetch): flag non-2xx responses with a status banner (keep the body)#69
Merged
ysyneu merged 2 commits intoJun 15, 2026
Merged
Conversation
The runner's webfetch read and converted the response body regardless of status code, so a 401/403 auth wall, a redirect-to-login, or any 4xx/5xx error page was turned into markdown and returned/spilled as if it were real page content. fc-safari's localWebFetch already rejects non-2xx; the runner path (used by ai-sre cloud sandboxes) did not. Add a status-code guard in fetchBody right after Do() succeeds and before the body is read/converted. The client already follows redirects via safeCheckRedirect (max 5 hops), so the checked response is terminal.
Dropping the entire body on non-2xx loses valuable content: APIs commonly
return non-2xx with a useful JSON error body (400/422/404 with {"error":...}).
The 123K login page from the audit was HTTP 200, so it never reached this
path anyway — on the non-2xx path the body is almost always worth keeping.
Make it lossless instead of rejecting:
- fetchedPage gains a statusCode field; fetchBody always reads the body and
records resp.StatusCode (no early return on non-2xx).
- WebFetch prepends an [HTTP <code> <text>] banner to the processed content
when the status is non-2xx, so the agent sees BOTH the error status AND the
body. The banner is added after Process so it stays visible even when the
body spilled to a file.
Test now asserts the opposite of before: a 403/404/500 response is preserved
(no error, body returned, statusCode matches).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
On a non-2xx final HTTP response,
webfetchnow keeps the body and prepends a status banner to the result content:The agent sees BOTH the error status AND the body, instead of either being misled into reading an error/login page as legitimate content (old behavior) or losing the body entirely (the first revision of this PR).
Why lossless (revision)
The first version of this PR dropped the body on non-2xx and returned an error. That loses valuable content: APIs commonly return non-2xx with a useful JSON error body (400/422/404 with
{"error":...}), and an agent debugging an API call needs that message. Note the original 123K login page from the audit was HTTP 200, so it never hit the non-2xx path anyway — on the non-2xx path the body is almost always worth keeping. So we keep it and just flag the status.Root cause
From prod audit
audit-2026-06-15(sessionsess_iBhNqeVErwQ4vYXKsVKxLw, steps 54/56/57): the runner'sfetchBody(environment/webfetch.go) read/converted the response body with no indication of the HTTP status. A401/403auth wall, a redirect-to-login, or any 4xx/5xx error page was returned to the model as if it were real page content, with nothing signaling that the fetch had failed at the HTTP layer.fc-safari's own
localWebFetchrejects non-2xx outright, but the runner path — used by AI-SRE cloud sandboxes — surfaced nothing. This PR makes the runner status-aware without throwing away the (often useful) body.Implementation
environment/webfetch.gofetchedPagegains astatusCode intfield.fetchBodyalways reads the body (no early return on non-2xx) and recordsresp.StatusCode.WebFetchprepends the[HTTP <code> <text>]banner toresult.Contentwhen the status is non-2xx, afterprocessor.Processso the banner stays visible even when the body spilled to a file.environment/webfetch_test.goTestFetchBody_PreservesBodyAndStatusOnNon2xx(renamed from the earlier reject test): a 403/404/500 response is preserved — no error, body returned,page.statusCodeequals the served status.WebFetchruns a pre-flightvalidateURLSSRF check with no test bypass hook, so it refuses the loopbackhttptestserver; adding a hook would be net-new scaffolding. ThefetchBodytest verifiesstatusCodeis captured correctly, and the banner is a trivial deterministic string prepend over it.)Scope honesty
The banner fires on true 4xx/5xx. It does NOT specially handle GitHub private-repo pages that return HTTP 200 with a login body — those look successful at the HTTP layer and would need fragile content heuristics, which we deliberately do not add. That 200-login case is handled separately by a prompt-side change in fc-safari (P2-1).
Verification