summary
_validate_remote_url in src/specify_cli/bundler/services/adapters.py accepts host-less catalog URLs like https://:8080 and https://user@. its docstring says it "mirrors specify_cli.catalogs URL validation", but that site was fixed to use hostname in #3210/#3227 while this bundler twin still checks netloc.
root cause
if not parsed.netloc:
raise BundlerError(f"Catalog '{source_id}' URL must be a valid URL with a host: {url}")
urlparse("https://:8080") gives netloc=":8080" (truthy) but hostname=None. so the guard passes even though there is no host. same for https://user@, https://user:pw@.
reproduction
from specify_cli.bundler.services.adapters import _validate_remote_url
_validate_remote_url("team", "https://:8080") # accepted; should reject
_validate_remote_url("team", "https://user@") # accepted; should reject
impact
this is a pre-flight validation that runs before any network fetch. a host-less URL slips past the "must be a valid URL with a host" guarantee the error message promises, deferring the failure to a less clear point (or a malformed request). the sibling specify_cli.catalogs validator already rejects these since #3210; the bundler path should match.
proposed fix
check parsed.hostname (None for host-less URLs) instead of parsed.netloc, mirroring the catalogs.py fix.
happy to open a PR (have one ready with regression tests).
note: i used an ai assistant to help investigate and write this up.
summary
_validate_remote_urlinsrc/specify_cli/bundler/services/adapters.pyaccepts host-less catalog URLs likehttps://:8080andhttps://user@. its docstring says it "mirrorsspecify_cli.catalogsURL validation", but that site was fixed to usehostnamein #3210/#3227 while this bundler twin still checksnetloc.root cause
urlparse("https://:8080")givesnetloc=":8080"(truthy) buthostname=None. so the guard passes even though there is no host. same forhttps://user@,https://user:pw@.reproduction
impact
this is a pre-flight validation that runs before any network fetch. a host-less URL slips past the "must be a valid URL with a host" guarantee the error message promises, deferring the failure to a less clear point (or a malformed request). the sibling
specify_cli.catalogsvalidator already rejects these since #3210; the bundler path should match.proposed fix
check
parsed.hostname(None for host-less URLs) instead ofparsed.netloc, mirroring the catalogs.py fix.happy to open a PR (have one ready with regression tests).
note: i used an ai assistant to help investigate and write this up.