fix(download): disable netrc auto-auth to avoid Authorization header conflict (#1070)

With trust_env=True, aiohttp auto-loads credentials from ~/.netrc (e.g. a
'machine civitai.red' or 'default' entry) and refuses to combine them with
the explicit Authorization: Bearer header, aborting every authenticated
CivitAI request with 'Cannot combine AUTHORIZATION header with AUTH
argument or credentials encoded in URL'.
This commit is contained in:
Will Miao
2026-08-22 19:33:01 +08:00
parent 41e9883daa
commit 25e72b43ce
2 changed files with 45 additions and 0 deletions
+20
View File
@@ -156,6 +156,25 @@ class DownloadStalledError(Exception):
"""Raised when download progress stalls beyond the configured timeout.""" """Raised when download progress stalls beyond the configured timeout."""
def _disable_netrc_auth(session: aiohttp.ClientSession) -> None:
"""Prevent the session from loading credentials from netrc files.
``trust_env=True`` is kept so system-level proxies still work, but aiohttp
would also auto-apply netrc entries (e.g. ``machine civitai.red``) as
BasicAuth. aiohttp refuses to combine those with the explicit
``Authorization: Bearer`` header set for CivitAI requests, raising
"Cannot combine AUTHORIZATION header with AUTH argument or credentials
encoded in URL" before the request is even sent. Subclassing ClientSession
is discouraged by aiohttp (emits a DeprecationWarning), so the private
hook is patched on the instance instead.
"""
def _no_netrc_auth(*args: Any, **kwargs: Any) -> Optional[aiohttp.BasicAuth]:
return None
setattr(session, "_get_netrc_auth", _no_netrc_auth)
class Downloader: class Downloader:
"""Unified downloader for all HTTP/HTTPS downloads in the application.""" """Unified downloader for all HTTP/HTTPS downloads in the application."""
@@ -370,6 +389,7 @@ class Downloader:
trust_env=not app_proxy_active, trust_env=not app_proxy_active,
timeout=timeout, timeout=timeout,
) )
_disable_netrc_auth(self._session)
# Store proxy URL for per-request use. Stays None for SOCKS because the # Store proxy URL for per-request use. Stays None for SOCKS because the
# ProxyConnector already tunnels everything; passing proxy= for SOCKS # ProxyConnector already tunnels everything; passing proxy= for SOCKS
+25
View File
@@ -272,3 +272,28 @@ async def test_download_file_retries_redirected_url_when_range_not_honored(tmp_p
assert _session(downloader).requests[0]["headers"]["Range"] == "bytes=3-" assert _session(downloader).requests[0]["headers"]["Range"] == "bytes=3-"
assert _session(downloader).requests[1]["url"] == redirected_url assert _session(downloader).requests[1]["url"] == redirected_url
assert _session(downloader).requests[1]["headers"]["Range"] == "bytes=3-" assert _session(downloader).requests[1]["headers"]["Range"] == "bytes=3-"
@pytest.mark.asyncio
async def test_disable_netrc_auth_ignores_netrc_file(tmp_path, monkeypatch):
"""netrc entries must not be auto-applied as BasicAuth.
Regression test for "Cannot combine AUTHORIZATION header with AUTH
argument or credentials encoded in URL": with trust_env=True aiohttp
loads credentials from netrc files, which conflict with the explicit
Authorization: Bearer header used for CivitAI requests.
"""
import aiohttp
from py.services.downloader import _disable_netrc_auth
netrc_file = tmp_path / ".netrc"
netrc_file.write_text("machine civitai.red\nlogin user\npassword pass\n")
monkeypatch.setenv("NETRC", str(netrc_file))
async with aiohttp.ClientSession(trust_env=True) as session:
# Premise: a plain session would pick up the netrc credentials.
assert session._get_netrc_auth("civitai.red") is not None
_disable_netrc_auth(session)
assert session._get_netrc_auth("civitai.red") is None