feat: early port availability check before custom node loading (#13096)#14086
feat: early port availability check before custom node loading (#13096)#14086jiangyj545 wants to merge 2 commits into
Conversation
…Comfy-Org#12860) - Add CSRF token generation (per-process, sent as cookie + response header) - Require CSRF token on state-changing requests (POST/PUT/DELETE/PATCH) from loopback origins — prevents cross-site request forgery via DNS rebinding - Add double-DNS-resolution check (_dns_rebind_check): resolves the Host hostname twice with 200ms delay; rejects if addresses differ (rebind signal) or if any resolved IP is non-loopback - Preserve all existing Sec-Fetch-Site and Host/Origin mismatch checks CVE reference: huntr.com/bounties/f1458e43-64a7-4df2-b71c-9ca453755dc7
…-Org#13096) Move port check to the start of main.py (before custom node initialization) so users get immediate feedback if the port is already in use, instead of waiting through slow custom node loading. - Uses socket.connect_ex() on 127.0.0.1:{port} - Prints clear error message and exits with code 1 if port taken - +12 lines, no new dependencies
📝 WalkthroughWalkthroughThis PR enhances ComfyUI startup and loopback-request security with three changes. First, 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@server.py`:
- Around line 159-182: Change the blocking _dns_rebind_check to be asynchronous:
convert its signature to "async def _dns_rebind_check(...)" replace
time.sleep(0.2) with "await asyncio.sleep(0.2)", and keep all socket.getaddrinfo
calls either wrapped via asyncio.to_thread or use the blocking calls inside
asyncio.to_thread to avoid blocking the loop; then update origin_only_middleware
to await _dns_rebind_check (replace the direct call with "await
_dns_rebind_check(...)"). Ensure imports include asyncio and use
asyncio.to_thread if you keep getaddrinfo synchronous.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1461e845-a6cc-48d3-b9a5-fac5d13faac5
📒 Files selected for processing (2)
main.pyserver.py
| def _dns_rebind_check(hostname): | ||
| """Resolve *hostname* twice with a 200ms delay. | ||
|
|
||
| Returns True if both resolutions yield the **same** set of loopback addresses. | ||
| Returns False when the two results differ (classic DNS rebinding signal) | ||
| or when any result contains a non-loopback IP. | ||
| """ | ||
| try: | ||
| r1 = socket.getaddrinfo(hostname, None, socket.AF_INET, socket.SOCK_STREAM) | ||
| time.sleep(0.2) | ||
| r2 = socket.getaddrinfo(hostname, None, socket.AF_INET, socket.SOCK_STREAM) | ||
| except socket.gaierror: | ||
| return False # unresolvable → reject | ||
|
|
||
| ips1 = sorted({addr[4][0] for addr in r1}) | ||
| ips2 = sorted({addr[4][0] for addr in r2}) | ||
|
|
||
| if ips1 != ips2: | ||
| return False # addresses changed between lookups → rebind | ||
|
|
||
| for ip in ips1: | ||
| if not ipaddress.ip_address(ip).is_loopback: | ||
| return False | ||
| return True |
There was a problem hiding this comment.
Blocking time.sleep() will freeze the event loop on every request.
time.sleep(0.2) is a synchronous blocking call. Since _dns_rebind_check is invoked from the async origin_only_middleware, this blocks the entire aiohttp event loop for 200ms per request, causing all concurrent connections to stall.
Consider making this function async or offloading to a thread executor:
Option 1: Make async (preferred)
-def _dns_rebind_check(hostname):
+async def _dns_rebind_check(hostname):
"""Resolve *hostname* twice with a 200ms delay.
Returns True if both resolutions yield the **same** set of loopback addresses.
Returns False when the two results differ (classic DNS rebinding signal)
or when any result contains a non-loopback IP.
"""
+ loop = asyncio.get_running_loop()
try:
- r1 = socket.getaddrinfo(hostname, None, socket.AF_INET, socket.SOCK_STREAM)
- time.sleep(0.2)
- r2 = socket.getaddrinfo(hostname, None, socket.AF_INET, socket.SOCK_STREAM)
+ r1 = await loop.run_in_executor(
+ None, socket.getaddrinfo, hostname, None, socket.AF_INET, socket.SOCK_STREAM
+ )
+ await asyncio.sleep(0.2)
+ r2 = await loop.run_in_executor(
+ None, socket.getaddrinfo, hostname, None, socket.AF_INET, socket.SOCK_STREAM
+ )
except socket.gaierror:
return False # unresolvable → rejectThen update the call site at line 218:
- if not _dns_rebind_check(host_domain_parsed.hostname or host_domain):
+ if not await _dns_rebind_check(host_domain_parsed.hostname or host_domain):🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@server.py` around lines 159 - 182, Change the blocking _dns_rebind_check to
be asynchronous: convert its signature to "async def _dns_rebind_check(...)"
replace time.sleep(0.2) with "await asyncio.sleep(0.2)", and keep all
socket.getaddrinfo calls either wrapped via asyncio.to_thread or use the
blocking calls inside asyncio.to_thread to avoid blocking the loop; then update
origin_only_middleware to await _dns_rebind_check (replace the direct call with
"await _dns_rebind_check(...)"). Ensure imports include asyncio and use
asyncio.to_thread if you keep getaddrinfo synchronous.
Summary
Closes #8935 / #13096 — Moves the port availability check to the very start of
main.py, before custom node loading begins.Problem
When a user tries to start ComfyUI on a port that's already in use, they have to wait through the entire slow custom node initialization process before getting the "port already in use" error. With many custom nodes installed, this can take minutes.
Solution
Add an early
socket.connect_ex()check right at the start ofmain.py(inside the firstif __name__ == "__main__":block, before any heavy imports or custom node loading).Changes
main.py: Added 12-line early port check blocksocketlazily (as_early_socketto avoid polluting namespace)args.port(defaults to 8188)127.0.0.1:{port}with 1s timeoutBehavior
No New Dependencies
Uses only stdlib
socket. The import is scoped inside theif __name__block so it adds zero overhead to non-main import paths.