Conversation
Adds a GET /fetch?url= handler that fetches the given URL and streams the response back, for checking what a downstream service sees when whoami calls out to it.
There was a problem hiding this comment.
Blocking
fetch.go:18— SSRF: the/fetch?url=endpoint fetches an arbitrary, user-supplied URL server-side with no validation (scheme, private/loopback/link-local ranges, cloud metadata IPs, or redirect targets). This lets any caller use the service as an open internal-network proxy. Needs a human decision on whether/how this endpoint should be scoped (allowlist? disable in prod? restrict to non-private destinations?) before merge.
Should-fix
fetch.go:22—resp.Bodyis never closed (connection/fd leak).fetch.go:26— no timeout on the outbound request and no size limit on the copied response body; combined with the SSRF issue this enables trivial resource-exhaustion/hang. Theio.Copyerror is also silently discarded.
Nits
- None beyond what's folded into the should-fix items above.
No missing-test concerns beyond the security gap itself — the added test only covers the happy path, not any of the unsafe-target cases that should exist once validation is added.
Generated by Pull Request Reviewer for #3 · claude · agent · 39 AIC · ⌖ 7.82 AIC · ⊞ 5.1K
| return | ||
| } | ||
|
|
||
| resp, err := http.Get(target) |
There was a problem hiding this comment.
Blocking — SSRF. target comes directly from the url query parameter and is passed to http.Get with no validation. Any caller can make the server issue requests to internal/private addresses, loopback, or cloud metadata endpoints (e.g. 169.254.169.254), and can follow redirects to the same. Since this is deployed as a network service, this endpoint effectively turns whoami into an open internal-network proxy.
Even for a debug/test utility, this needs at minimum: restrict to http/https schemes, resolve and block private/loopback/link-local/metadata IP ranges (including after redirects), and consider disabling redirect-following or capping redirect count.
| if err != nil { | ||
| http.Error(w, err.Error(), http.StatusBadGateway) | ||
| return | ||
| } |
There was a problem hiding this comment.
Should-fix — resource leak. resp.Body is never closed. Add defer resp.Body.Close() right after the error check, otherwise every call to this handler leaks a connection/file descriptor.
|
|
||
| w.Header().Set("Content-Type", resp.Header.Get("Content-Type")) | ||
| w.WriteHeader(resp.StatusCode) | ||
| io.Copy(w, resp.Body) |
There was a problem hiding this comment.
Should-fix. http.Get uses no timeout, and this io.Copy has no size or deadline limit — a slow or huge upstream response (attacker-controlled via the SSRF above) can hang the handler or exhaust memory/bandwidth. Consider an http.Client with a timeout and wrapping resp.Body in an io.LimitReader. Also, the io.Copy error is silently discarded — at least log it (nit-level on its own, but worth fixing alongside the above).
There was a problem hiding this comment.
Blocking
fetch.go:18— SSRF: theurlquery param is passed straight tohttp.Getwith no allowlist/denylist for private/internal/metadata addresses. Already flagged in a prior review comment on this line; still unaddressed.
Should fix
fetch.go:22—resp.Bodyis never closed (resource/connection leak). Already flagged in a prior review comment.fetch.go:26— no timeout onhttp.Getand unboundedio.Copy; a slow/huge upstream response can hang the handler or exhaust memory. Already flagged in a prior review comment.fetch.go:18/26—http.Getfollows redirects by default (up to 10). Even if an allowlist is added for the blocking SSRF issue above, an externally-allowed URL can 302 to an internal address and bypass it — worth handling together with the SSRF fix (e.g. customCheckRedirector disabling redirects).fetch_test.go— only the happy path is covered. No test for the missing-url400 branch or the upstream-error 502 branch.
Needs a human decision
- This endpoint is effectively "fetch any URL on behalf of the server," which is SSRF-as-a-feature. The PR body frames it as a debugging convenience ("useful for checking what a downstream service sees"), but as implemented it has no restriction to intentionally-testable targets. A maintainer should decide whether this needs an allowlist/opt-in flag before merging, or whether the risk is accepted given this project's purpose (whoami is itself a debug/test tool typically run in trusted environments) — I can't verify that intent from the diff alone.
Coverage: Reviewed 4 files, skimmed 0, skipped 0 of 4 (skipped: none). Prior review comments read: 3.
Generated by Pull Request Reviewer for #3 · claude · agent · 42.8 AIC · ⌖ 10.3 AIC · ⊞ 6.9K
….sha Live-tested the previous version on #3: the agent correctly called get_file_contents for .claude/skills/review/SKILL.md, but at github.event.pull_request.base.sha — a payload snapshot from whenever the PR was last synced, not the actual current tip of main. Since main moved (this very SKILL.md was added) after the PR was opened, the call 404'd and the skill was silently skipped (the agent's own fallback for a missing file, working as designed, just against a stale ref). github.event.pull_request.base.ref isn't in gh-aw's allowed-expressions list for the prompt body (only base.sha is), so switch to a small pre-agent-steps shell fetch instead: a plain `gh api .../contents/...` call with no `ref` defaults to the repo's default branch, which can't go stale the same way. Confirmed pre-agent-steps from our own frontmatter and from the imported shared/pr-diff-data-fetch.md both land in the compiled job (concatenated, not overridden). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…locks Live-tested run on #3 posted zero inline comments: the agent read the dedup-against-prior-comments instruction too broadly and folded every finding (including one genuinely new one) into the review body as prose instead of posting create_pull_request_review_comment for any of them. Per feedback: detailed, line-anchored comments are the point of this review, not one consolidated summary. - Narrow the dedup rule: prior comments only suppress an exact repeat at the same path/line, never a reason to skip inline comments altogether. - Require a GitHub suggestion block (```suggestion fenced code) on inline comments whenever the fix is a small, mechanical, safe patch (defer Close(), add a timeout, a nil check) — skip it only when the real fix needs a design decision. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Blocking
fetch.go:18— SSRF:targetis taken verbatim from theurlquery parameter and passed straight tohttp.Getwith no scheme/host allowlist or private-range blocking. Any caller can make the server reach internal services or cloud metadata endpoints (e.g.169.254.169.254). Already flagged inline by a prior review comment on this PR; restating because it blocks merge. The PR body's framing ("useful for checking what a downstream service sees when whoami calls out to it") doesn't address who can call this endpoint or from where — author's claim, not verified.
Should fix
fetch.go:22—resp.Bodyis never closed, leaking a connection per request. Already flagged inline by a prior comment; adddefer resp.Body.Close()right after the error check.fetch.go:26—http.Gethas no timeout andio.Copyhas no size/deadline bound, so a slow or huge upstream response (attacker-controlled given the SSRF issue above) can hang the handler or exhaust memory. Already flagged inline by a prior comment.fetch_test.go:9— only the happy path is tested; no coverage for the missing-url400 case, the upstream-error 502 case, or (once fixed) SSRF rejection. New inline comment added.
Needs a human decision
- The concrete SSRF mitigation (allowlist of schemes/hosts, blocking RFC1918/link-local ranges, or requiring the endpoint to be admin-only/behind auth) is a policy call, not a mechanical patch — needs a maintainer decision on the intended trust model for this endpoint before a fix is merged.
Coverage: Reviewed 4 files, skimmed 0, skipped 0 of 4 (skipped: none). Prior review comments read: 3.
Generated by Pull Request Reviewer for #3 · claude · agent · 43.8 AIC · ⌖ 8.57 AIC · ⊞ 7.3K
| "testing" | ||
| ) | ||
|
|
||
| func TestFetchHandler(t *testing.T) { |
There was a problem hiding this comment.
Should-fix — missing tests. Only the happy path is covered. There's no test for the missing-url case (expect 400) or for an unreachable/erroring upstream (expect 502), and no test asserting the handler rejects or restricts non-http(s) targets / internal addresses once the SSRF fix (flagged elsewhere on fetch.go) lands — without that, a future refactor could silently reopen the hole.
There was a problem hiding this comment.
Blocking
- fetch.go:18 —
targetfrom theurlquery parameter is passed unvalidated tohttp.Get, letting any caller make the server issue requests to internal/private addresses (classic SSRF). Already flagged by an existing inline comment at this exact location; not duplicating it.
Should fix
- fetch.go:22 —
resp.Bodyis never closed (missingdefer resp.Body.Close()), leaking a connection per request. Already flagged inline. - fetch.go:26 —
http.Gethas no timeout andio.Copyhas no size/deadline limit, so a slow or huge (attacker-controlled, given the SSRF above) upstream response can hang the handler or exhaust memory. Already flagged inline. - fetch_test.go:9 — only the happy path is tested; the missing-
url(400) and upstream-error (502) branches infetch.gohave no coverage. Already flagged inline.
All three code-level findings above and the test-coverage gap already have inline review comments at the same file/line from a prior pass, so no new inline comments were added — reposting identical findings at the same anchors would just be noise.
Needs a human decision
- Design-level: this handler makes
test-whoamiact as an open HTTP fetch/proxy primitive (arbitrary URL in, response streamed out). Fixing the leak/timeout/validation issues makes it safer but doesn't change that the feature itself is an SSRF-by-design capability. Maintainers should decide whether this belongs in the tool at all, or whether it needs an allowlist / opt-in flag rather than being on by default — that's a product decision, not a mechanical fix. - The README text ("Useful for checking what a downstream service sees when whoami calls out to it") frames this as an intentional debugging feature — noted as the author's stated intent, not verified as sufficient justification for the missing hardening above.
Coverage
Reviewed 2 files, skimmed 2, skipped 0 of 4 (skipped: none). Prior review comments read: 4.
Generated by 🔍 AI code review for #3 · claude · agent · 40.9 AIC · ⌖ 10.2 AIC · ⊞ 7.4K
Add this agentic workflow to your repo
To install this agentic workflow, run
gh aw add traefik/ai/workflows/test-whoami.ai-review.md@main
Adds a
GET /fetch?url=handler that fetches the given URL and streamsthe response back to the caller — useful for checking what a downstream
service sees when whoami calls out to it.
fetchHandlerinfetch.go, registered on/fetch