fix(catalogs): validate the port in the shared catalog-URL validator, like its mirrors do - #3804
Open
jawwad-ali wants to merge 1 commit into
Open
fix(catalogs): validate the port in the shared catalog-URL validator, like its mirrors do#3804jawwad-ali wants to merge 1 commit into
jawwad-ali wants to merge 1 commit into
Conversation
`CatalogStackBase._validate_catalog_url()` reads `parsed.hostname` inside its `try/except ValueError` but never reads `parsed.port`. `urlparse()` and `.hostname` do not perform port validation — only `.port` does — so a catalog URL with a non-numeric or out-of-range port passes validation. Every implementation that documents itself as mirroring this function already reads `.port` inside the same try: workflows/catalog.py (4 sites), bundler/services/adapters.py (2), bundler/commands_impl/catalog_config.py, and commands/bundle/__init__.py. The shared base — inherited by ExtensionCatalog and IntegrationCatalog — is the only one without it. The accepted URL then escapes as a raw `http.client.InvalidURL`, which is neither `urllib.error.URLError` nor `json.JSONDecodeError` (the only two the fetcher converts), so it surfaces as an unhandled traceback rather than the validator's normal error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
CatalogStackBase._validate_catalog_url()(src/specify_cli/catalogs.py) readsparsed.hostnameinside itstry/except ValueErrorbut never readsparsed.port.urlparse()and.hostnamedo not perform port validation — only.portdoes — so a catalog URL with a non-numeric or out-of-range port passes validation.This is a parity gap, not a design choice. Every implementation that documents itself as mirroring this function reads
.portinside the same try, with the same comment:workflows/catalog.py313, 511, 996, 1193bundler/services/adapters.py81, 127specify_cli.catalogsURL validation"bundler/commands_impl/catalog_config.py156commands/bundle/__init__.py849The shared base — inherited by
ExtensionCatalogandIntegrationCatalog— is the only one of the five validators without it. The invariant is already maintainer-accepted:tests/test_workflows.py:6401andtests/unit/test_bundler_adapters.py:97both parametrize the exact stringhttps://example.com:notaport/catalog.jsonfor their mirrors.Reproduction on current
main(2e44ed6)The accepted URL is unusable, and it fails late:
Those are the only two exceptions the fetcher converts, so it is not turned into a catalog error — it escapes as an unhandled traceback. The unusable entry is also persisted to
.specify/*-catalogs.ymlfirst, socatalog addexits 0 and the failure appears later on an unrelated command.Fix
Two lines inside the existing
try, matching the mirrors:No new exception type, no new message, no signature change — it routes into the error the function already raises for a malformed authority.
Scope: base only, following #3435 → #3576
PresetCatalog._validate_catalog_urlhas the identical gap, butPresetCatalogis a standalone class, not aCatalogStackBasesubclass, so this fix does not reach it. That split is the repo's documented pattern rather than my judgment call:try/exceptand landed base-only —src/specify_cli/catalogs.py+tests/integrations/test_integration_catalog.py, exactly this PR's shape.src/specify_cli/presets/__init__.py+tests/test_presets.py.I'll follow up on the preset twin separately, same as #3576 did.
Verification
src(DID NOT RAISE) and pass with the fix. Full file: 120 passed.https://example.com:8443,http://localhost:3000,http://127.0.0.1:8080,:0,:65535all still validate.parsed.portreturnsNonewith no port and an int for any in-range port, so every URL that works today still works.tests/integrations/test_integration_catalog.py tests/test_extensions.py tests/test_download_security.py→ 684 passed, 7 skippeduvx ruff@0.15.0 check→ cleanOnly inputs that can never be fetched change behaviour: a non-numeric port (previously a raw
InvalidURLtraceback) and an out-of-range port (previously a DNS/socket failure). Both now raise the existingCatalog URL is malformed: <url>.Note for reviewers
I deliberately did not widen
except ValueErrorto also catchTypeError. The base still leaksTypeErrorfor a non-strurl from hand-edited YAML, but that is a separate concern and a behaviour change for non-str input — happy to do it as a follow-up if you'd prefer.Written with assistance from Claude Code. Bug found, reproduced, and verified by me on current
main; every claim above is from output I ran.