The last 10 GitHub issues filed on galaxyproject/galaxy (#22341–#22350) are all caused by malformed or malicious API inputs that bypass validation and crash deep inside application code, producing 500 errors logged to Sentry. Every one of these should be caught at the API frontier and returned as a 4xx response.
File: lib/galaxy/webapps/galaxy/controllers/history.py:344
isoparse(since) is called on raw input with no guard. Wrap in try/except:
if since:
if not isinstance(since, str):
raise glx_exceptions.RequestParameterInvalidException("'since' must be a date string")
try:
parsed = isoparse(since)
except (ValueError, TypeError):
raise glx_exceptions.RequestParameterInvalidException(f"Invalid date format for 'since': {since!r}")
if history.update_time <= parsed:
returnFile: lib/galaxy/managers/genomes.py:71-78 (_get_index_filename)
ReferenceDataError has status_code = 500 (see exceptions/__init__.py:300), so it gets reported to Sentry. But a bogus ID like "1" is a client error, not a server misconfiguration.
The IndexError at line 77-78 (empty paths list because ID matches nothing) should raise a 4xx, not a 500. Change the IndexError handler to raise RequestParameterInvalidException (400):
except IndexError:
raise RequestParameterInvalidException(f"Data tables not found for {index_type} for {id}")Keep the TypeError case (line 75-76) as ReferenceDataError since that indicates a genuinely broken data table.
Also update test/integration/test_genomes.py test assertions that expect 500 for the invalid-ID case (if any test covers this path — the existing test at line 73-74 tests a valid key with missing ref data, which is a different case and should stay as 500).
File: lib/galaxy/webapps/galaxy/api/tools.py:894
Change payload to have a default of None and validate early (matching workflows.py pattern):
def create(self, trans: GalaxyWebTransaction, payload=None, **kwd):
if payload is None:
raise exceptions.RequestParameterMissingException("A payload is required for tool execution.")File: lib/galaxy/webapps/galaxy/services/wes.py:599
Anonymous user accesses trans.user.id. Add auth guard:
if trans.user is None:
raise AuthenticationRequired("Listing WES runs requires authentication.")AuthenticationRequired is already used in the codebase (import from galaxy.exceptions).
File: lib/galaxy/webapps/galaxy/api/dynamic_tools.py:48
DatabaseIdOrUUID = Union[DecodedDatabaseIdField, str] accepts any string as a UUID. Add UUID validation in the manager layer:
File: lib/galaxy/managers/tools.py — in get_tool_by_uuid and get_unprivileged_tool_by_uuid, validate UUID format before querying:
try:
uuid_module.UUID(tool_uuid)
except ValueError:
raise exceptions.RequestParameterInvalidException(f"Invalid UUID format: {tool_uuid!r}")No existing URI format validation exists for gxfiles:// URIs. Validation is purely by substring-based plugin matching. Three changes needed:
a) lib/galaxy/files/sources/__init__.py:338 — fix score_url_match to use prefix match instead of substring:
def score_url_match(self, url: str) -> int:
root = self.get_uri_root()
return len(root) if url.startswith(root) else 0b) lib/galaxy/managers/remote_files.py:55 — after uri = target, reject URIs with embedded schemes:
if "://" in target:
uri = target
scheme_end = uri.index("://") + 3
if "://" in uri[scheme_end:]:
raise exceptions.RequestParameterInvalidException(f"Malformed URI: {uri}")c) Unit test — add test in test/unit/files/test_posix.py (which already tests gxfiles:// URI matching) to verify that a malicious URI like gxfiles://test1http://evil.com/foo scores 0 and that the manager rejects double-scheme URIs. Test patterns to follow: existing list_root() / assert_realizes_throws_exception() helpers from test/unit/files/_util.py.
File: lib/galaxy/webapps/galaxy/api/tools.py:929-932
_kwd_or_payload does a cast() with no runtime check. Add type validation:
def _kwd_or_payload(kwd: dict[str, Any]) -> dict[str, Any]:
if "payload" in kwd:
payload = kwd.get("payload")
if not isinstance(payload, dict):
raise exceptions.RequestParameterInvalidException(
"Request payload must be a JSON object."
)
kwd = payload
return kwdDelete the endpoint entirely. Investigation shows:
- No frontend caller exists — zero calls to
POST /api/metricsanywhere inclient/src/ - The endpoint is a stub for a never-completed Fluentd bridge (
# TODO: facade or adapter to fluentd) - With
fluent_log: false(the default), it just callslog.debug()and returns{} - No feature development in 3+ years — only automated formatting commits
Files to remove:
lib/galaxy/webapps/galaxy/api/metrics.pylib/galaxy/managers/metrics.pytest/integration/test_fluent_metrics.py- Remove router registration reference
Keep: fluent_log/trace_logger config (used by server-side request tracing elsewhere).
File: lib/galaxy/web/framework/base.py:445
The dict comprehension k.decode() / v.decode() has no error handling. Only quoted cookie values with non-ASCII bytes trigger this — legitimate Galaxy session cookies are always hex-encoded ASCII.
Silently returning empty dict is safe: get_cookie() already has a blanket except Exception: return None, and _ensure_valid_session() treats None as "new anonymous session" (the normal first-visit path). No downstream code assumes the session cookie exists.
Raising 400 would be harsher — could break users behind a mangling proxy. Empty dict = graceful degradation to anonymous session:
try:
galaxy_cookies = {k.decode(): v.decode() for k, v in all_cookies if k.startswith(b"galaxy")}
except UnicodeDecodeError:
galaxy_cookies = {}Two changes:
File: lib/galaxy/webapps/galaxy/api/object_store.py:49 — add a regex pattern constraint:
ConcreteObjectStoreIdPathParam: str = Path(
..., title="Concrete Object Store ID",
description="The concrete object store ID.",
pattern=r"^[\w-]+$",
)File: lib/galaxy/objectstore/__init__.py:1660 — use .get() instead of [] to return None (matching the base class contract) so _model_for returns 404:
return self.backends.get(object_store_id)- Run relevant unit tests via
./run_tests.sh:./run_tests.sh -unit test/unit/files/test_posix.py - Formatting:
make format - Linting:
tox -e lint - Type checking:
tox -e mypy