fix: route authed urllib paths through a shared NoRedirectHandler opener (BE-3274) - #530
Conversation
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 54 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (20)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
There was a problem hiding this comment.
🔍 Cursor Review — Consolidated panel
Triggered by @mattmillerai.
Found 2 finding(s).
| Severity | Count |
|---|---|
| 🟢 Low | 2 |
Panel: 6/8 reviewers contributed findings.
Reviewers that did not contribute: kimi-k2.5:adversarial (empty), kimi-k2.5:edge-case (empty)
|
Re-reviewed this PR after the CI went red. Both failures are pre-existing repo-wide breaks, not caused by this PR — the diff here touches only 1. It only shows up in CI because 2. Both are being tracked as separate follow-ups rather than fixed here, to keep this auth-hardening PR scoped. On this PR itself: both review threads are resolved (the http(s) opener pinning landed in 651aa76; the |
|
Correction to my previous comment: both CI breaks already have fixes in flight — I've confirmed neither needs a new ticket, and both are still unrelated to this PR.
So this PR just needs those to land (or a rebase once they do); nothing to do here. The substance of my previous comment stands — this PR's own diff is clean, both review threads are resolved, and the 132 affected tests pass locally. |
…ner (BE-3274) Three authed call paths opened via plain urllib.request.urlopen, which follows 30x redirects with X-API-Key/Authorization still attached — bypassing the auth-leak guard NoRedirectHandler already provides for the rest of the codebase (comfy_client, cql/engine, cql/loader, transfer, cloud/oauth all open through a guarded opener). Consolidate request-building + opening into http.py behind build_authed_request/authed_urlopen over a shared _AUTHED_OPENER, and migrate the three unguarded paths: - command/workflow.py: _userdata_request + _http_request - command/models/search.py: _http_get_json - command/jobs.py: _cloud_cancel A refused redirect raises HTTPError, which every migrated call site already handles, so no caller changes are needed. Behavior is otherwise preserved: headers are not gated on is_cloud (local targets are simply uncredentialed), and byte caps / error mapping are untouched.
`build_opener()` implicitly installs FileHandler/FTPHandler/DataHandler
alongside the HTTP(S) ones. Every call site builds its URL from a trusted
`target.base_url`, so this isn't reachable today, but `_AUTHED_OPENER` is
the opener that attaches `X-API-Key` / `Authorization: Bearer`, so pin it
to http(s) and let anything else fall to UnknownHandler ("unknown url
type") rather than leaving a future caller one bad URL away from a
`file://` read. Proxy handling is unchanged (ProxyHandler still installed
when the env configures one).
Also drops the unrelated uv.lock churn that a `uv run` had swept into the
previous commit; main's lockfile is stale independently of this PR.
Raised by gemini-3.1-pro in the cursor-review panel.
651aa76 to
d8afd02
Compare
…tp(s) handlers only (BE-3298) (#542) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The merge of main in 517b9bc hand-reverted the docstring on test_find_pr_by_branch_rate_limit to a stale form ("no PR found\" """), which both origin/main and the pre-merge branch tip d8afd02 already had in the ruff-canonical form. That tripped `ruff format --diff` in CI: "1 file would be reformatted". No semantic change — whitespace inside a docstring on a test this PR does not otherwise touch. Restores the byte-identical blob from main. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Resolver pass — one real CI break found and fixed; it was not one of the pre-existing repo-wide failures called out above.
Root cause is the merge commit 517b9bc, not either parent — both
Verification
Self-review of the diff. The one behavior-denying edge I chased is that
No capability is lost; the pinning is pure defense-in-depth. Both review threads remain resolved (http(s) opener pinning landed in 651aa76; the |
|
CI update — all 16 checks now pass, including the two that earlier comments on this PR wrote off as pre-existing infra breaks.
Worth correcting the record from my two earlier comments: the tomlkit State: base is Ready for a human reviewer — I don't merge. |
skishore23
left a comment
There was a problem hiding this comment.
One condition before merge: now that #597 is approved, build_authed_request should delegate to target_auth_headers instead of its inline ungated copy, else this re-opens the is_cloud gate at workflow.py/search.py/jobs.py. PR body also needs a refresh — the diff now touches the five files the acceptance criteria list as untouched.
|
This PR currently conflicts with Please rebase (or merge |
…sult test breaks (BE-3274) build_authed_request built its credential header inline instead of via target_auth_headers, re-opening the is_cloud gate main's #597 closed elsewhere (a local Target with a stray token would get it attached). Delegate to target_auth_headers so every authed_urlopen call site shares one gate. Flagged by skishore23's review. Also repoints test_pretty_print_sanitize.py's mock at execution.no_redirect_urlopen — main added the test against the old request.urlopen() call site after this branch had already migrated it, so the merge left it silently connecting for real instead of hitting the mock.
|
Resolver pass — merge conflict with Merge conflict (main had moved). @skishore23's condition — fixed. Also fixed: the merge exposed a real semantic break — PR body refreshed to match (the "not gated on is_cloud" line was accurate for the pre-merge version but wrong now). Verification: full suite green — 3474 passed, 37 skipped ( Ready for a human reviewer — I don't merge. |
|
@skishore23 addressed — see above. |
ELI-5
When our CLI calls the cloud with your API key attached and the server answers "go look over there instead" (a redirect), plain
urlopencheerfully follows — and hands your credential to wherever "over there" points. We already have a guard for exactly this (NoRedirectHandler), but three authed code paths were never wired up to it. This wires them up: a redirect on those paths now fails loudly instead of quietly replaying your key somewhere else.What changed
Adds a shared authed-request layer to
comfy_cli/http.pyand migrates the three unguarded paths onto it.comfy_cli/http.py— below the existingNoRedirectHandler:target_auth_headers(target)— the singleis_cloud-gated header builder (landed onmainindependently via refactor(http): shared is_cloud-gated target_auth_headers builder; migrate 5 call sites (BE-4362) #597/BE-4362 while this branch was in flight; merged in here).build_http_only_opener(*handlers)/_AUTHED_OPENER/_PLAIN_OPENER— shared openers pinned to http(s) only (nofile:///ftp:///data:).build_authed_request(url, target, *, method, data, content_type)— attaches the header fromtarget_auth_headers(so it inherits theis_cloudgate), plus optionalContent-Type.authed_urlopen(...)/plain_urlopen(...)/no_redirect_urlopen(...)— open a request through the appropriate shared opener.Migrated call sites (each drops its own private
_authed_request/_auth_headers-inline pair):command/workflow.py—_userdata_request,_http_requestcommand/models/search.py—_http_get_jsoncommand/jobs.py—_cloud_cancelcommand/transfer.py,cql/engine.py— already ontarget_auth_headersvia refactor(http): shared is_cloud-gated target_auth_headers builder; migrate 5 call sites (BE-4362) #597; now paired withbuild_http_only_opener/_AUTHED_OPENERfor the no-redirect guard too.A refused redirect surfaces as
HTTPError, which every migrated site's existing error handling already covers — no caller changes.Headers ARE gated on
target.is_cloud. An earlier revision of this PR shippedbuild_authed_requestwith its own inline, ungated header copy (a stray credential on a local Target would have been attached). Reconciling withmain's paralleltarget_auth_headersrefactor (#597) surfaced that gap during review —build_authed_requestnow delegates totarget_auth_headers, so a local Target can never carry a credential through this path, matching every other call site.Why this is safe
NoRedirectHandlerand its "none of our authenticated endpoints redirect under normal operation" premise already ship onmainand are already applied to this same cloud API by other existing openers (comfy_client.py,cql/engine.py,cql/loader.py,command/transfer.py,cloud/oauth.py). This PR extends an existing, reviewed guard to three paths that were missed — it does not invent a new denial.install_openeris called nowhere in the codebase, so plainurlopenwas using the stock default opener.build_http_only_opener(...)yields the same default handler set (proxy handling included, http(s)-only) with the redirect handler swapped — the established pattern here._cloud_cancelpayload preserved: stillmethod="POST", data=b"", soContent-Length: 0is sent exactly as before.Verification
main:uv run pytest tests/passes (3473 passed, 37 skipped).ruff check/ruff format --checkclean on every file this PR touches; the handful of repo-wide pre-existing findings (17UP038, one docstring reformat intests/comfy_cli/command/github/test_pr.py) are byte-identical to theorigin/mainbaseline and untouched by this diff.mainin to resolve a real conflict (both branches had independently refactoredcomfy_cli/http.py— this PR's no-redirect opener work vs.main'starget_auth_headersextraction in refactor(http): shared is_cloud-gated target_auth_headers builder; migrate 5 call sites (BE-4362) #597). Reconciled by keeping both: header building goes throughtarget_auth_headers, request opening goes through the shared http(s)-only, no-redirect opener.tests/comfy_cli/command/test_pretty_print_sanitize.py(added onmainafter this branch diverged): it mockedcomfy_cli.command.run.execution.request.urlopen, butexecution.pyon this branch already routes/promptsubmission throughno_redirect_urlopen— repointed the mock atcomfy_cli.command.run.execution.no_redirect_urlopen.Tests added/updated (
tests/comfy_cli/test_http.py)X-API-Keyprecedence when both credentials are set; Bearer-only; no auth header when uncredentialed; no auth header for a local Target even with stray credentials set (the gap closed above);Content-Typepassthrough (and absence by default);method/datapassthrough;authed_urlopenopens via_AUTHED_OPENERwith the right Request/timeout; a refused 302 propagates asHTTPError; an end-to-end check that a migrated caller (search's_http_get_json) propagates a refused redirect; plusmain's owntarget_auth_headersunit tests, merged in unchanged.Judgment calls
workflow.pyintentionally keeps urllib out of its top-level imports, soauthed_urlopenis imported lazily inside the functions there, preserving that style.search.pyandjobs.pyalready import urllib at module top level, so the helper is imported at top level there to match each module's existing convention.install.py,models/models.py, andgenerate/client.pyattachAuthorization: Bearerbut use a different HTTP stack (not urllib), so they are outside this urllib-focused pass.NoRedirectHandlerand is already relied upon against the same API by the openers listed above. Flagging explicitly so a reviewer with cloud access can veto if any of/userdata,/api/experiment/models,/api/assets, or/api/jobs/<id>/cancelis expected to 30x in normal operation.