Updates the pinned FastAPI/Starlette versions on release_25.1 to match
upstream/dev, closing CVE-2026-48710 ("BadHost"). The vulnerability lets
an attacker inject a path into the HTTP Host header and have
``request.url.path`` reflect that path, bypassing path-based access
control in middleware. Starlette 1.0.1+ rejects Host headers with
invalid characters; we pin starlette==1.1.0 to match dev.
Drops Python 3.9 support, matching upstream/dev (commit eb9fde1dcd).
Starlette dropped 3.9 in 0.50.0, so the BadHost fix (starlette >= 1.0.1)
cannot be installed on 3.9. Bumping the floor to 3.10 is the only way
to ship the security fix; the alternative -- marker-splitting the pins
to keep 3.9 on starlette 0.49.x -- would leave 3.9 users exposed to the
CVE we are trying to close.
Actual exposure on release_25.1 (pre-fix)
-----------------------------------------
Galaxy was running a vulnerable starlette but is **not exploitable for
the headline auth-bypass scenario**. Auth runs through FastAPI
dependencies (``get_user``, ``get_trans``, ``AdminUserRequired``), not
through path-string checks in middleware. ``AccessLoggingMiddleware``
already uses ``scope["path"]``; ``add_galaxy_middleware`` /
``GalaxyCORSMiddleware`` / ``RawContextMiddleware`` /
``SentryAsgiMiddleware`` do not branch on ``request.url.path``.
Lower-severity findings that this upgrade also closes:
- ``lib/galaxy/webapps/base/api.py`` ``get_error_response_for_request``
picks an error schema with ``"ga4gh"/"drs"/"trs" in
request.url.path`` -- spoofable Host nudges error payload shape
(info-disclosure / shape confusion only).
- ``lib/galaxy/webapps/galaxy/api/drs.py`` and
``lib/galaxy/webapps/galaxy/services/datasets.py`` derive DRS
``service_info`` / ``self_uri`` from ``request.url`` -- spoofable
Host poisons the advertised DRS identity and client-visible URIs,
not access.
- ``lib/tool_shed/webapp/api2/tools.py`` -- same shape, TRS service
info.
Bumping the pin closes the parser flaw at source and downgrades all of
the above to non-issues, so no separate ``request.url.path ->
scope["path"]`` sweep is needed.
Pin bumps (cherry-picks the relevant ranges from PRs #21526, #22206,
#22754):
- fastapi 0.118.0 -> 0.136.3
- starlette 0.48.0 -> 1.1.0
- starlette-context 0.4.0 -> 0.5.1
- python-multipart 0.0.20 -> 0.0.29
- anyio 4.11.0 -> 4.13.0
Required code changes backported from upstream/dev:
- ``lib/galaxy/webapps/openapi/_compat/v2.py``: import
``GenerateJsonSchema`` and ``get_flat_models_from_fields`` from
``fastapi._compat.v2`` and adopt the new ``get_definitions()``
implementation for FastAPI 0.128.8+ (PRs #21384, dev commits
0800c025ce, c8ccc7f44d, b3bfb45884).
- ``lib/galaxy/webapps/openapi/utils.py``: route ``GenerateJsonSchema``
through ``_compat.v2`` and drop the now-unreachable
``get_compat_model_name_map`` fallback (dev commit b3bfb45884).
- ``lib/galaxy/schema/generics.py``: drop ``CustomJsonSchema`` here
(moved to fast_app.py so it can use the patched ``GenerateJsonSchema``
from ``_compat.v2``).
- ``lib/galaxy/webapps/galaxy/fast_app.py``: relocate
``CustomJsonSchema`` and switch its base to the ``_compat.v2``
``GenerateJsonSchema``; replace the @app.middleware("http") X-Frame
Options handler with a pure ASGI ``XFrameOptionsMiddleware`` class
(dev commit 67eea395ab) since ``BaseHTTPMiddleware`` semantics
changed in starlette 1.0.
- ``lib/galaxy/webapps/base/api.py`` and
``lib/galaxy/webapps/galaxy/api/datasets.py``: drop the ``method``
argument of ``FileResponse`` which starlette 1.0 removed (dev commit
63954cb42e).
Also bumps the minimum FastAPI requirement to ``>=0.133.0`` (first
version compatible with starlette>=1.0.0) and adds an explicit
``starlette>=1.0.1`` floor in ``pyproject.toml`` and the
``packages/web_apps`` / ``packages/tool_shed`` setup.cfg files so
source installs cannot resolve to a vulnerable combo.
Co-authored-by: Nicola Soranzo <nicola.soranzo@gmail.com>
`get_last_workflow_invocation_step_update_time()` returns UTC time (it's
set by galaxy.model.orm.now.now()) while datetime.now() is your local
time.
If your system is not on UTC time this caused 5 minute scheduling delays
for steps depending on expression tools that only produce parameters.
Fixes the delay observed in https://github.com/galaxyproject/tools-iuc/pull/7314
Use datetime.fromisoformat() instead of strptime with a rigid format
that requires microseconds. When datetime.isoformat() produces
timestamps without microseconds (e.g. "2026-04-02T14:28:40"), the
strptime format "%Y-%m-%dT%H:%M:%S.%f" fails silently, leaving
update_time/create_time as None on the restored model object.
Also guard against None timestamps on the export side to prevent
AttributeError when update_time or create_time is None.
The try/except: pass was silently swallowing parse errors for 5 years,
making it impossible to debug when timestamps failed to restore. Just
check for key presence instead — if the value exists but is malformed,
let it fail loudly.
These fields are required by downstream code, so silently skipping
them just delays the failure. Let KeyError or ValueError surface
at the actual point of failure.
Fixes https://github.com/galaxyproject/galaxy/issues/22371
The job search HDCA signature comparison was non-deterministic because
`func.array_agg(column, order_by=column)` silently drops the `order_by`
keyword argument in SQLAlchemy, generating `array_agg(col)` instead of
`array_agg(col ORDER BY col)`.
This meant both the reference and candidate HDCA signatures were
aggregated in whatever scan order PostgreSQL happened to use. When the
query planner chose different scan orders for the reference and
candidate CTEs (which depends on table statistics and query plan), the
resulting arrays had different element orderings, causing the equality
comparison to fail — even for the exact same HDCA.
The fix uses `aggregate_order_by` from SQLAlchemy's PostgreSQL dialect,
which correctly generates `array_agg(col ORDER BY col ASC)`.
Diagnostic output from CI confirming the root cause:
reference full signature=['data0;251', 'data1;252', 'data2;253']
candidate full signatures=[(75, ['data2;253', 'data1;252', 'data0;251'])]
equivalent HDCA ids=[]
Same HDCA (id=75), same elements, different array ordering → no match.
Investigation details: https://gist.github.com/mvdbeek/a3bd1528be0985e4a7d36e929a502bd2Fixes#21230
Switch from legacy controller axios calls to the typed GalaxyApi() fetcher
for both loading and reloading tool data tables. Remove the now-unused
tool_data_table_info, tool_data_table_info_1, and reload_tool_data_tables
methods from the data_manager controller.
Fixes https://github.com/galaxyproject/galaxy/issues/22227
When passing the all_histories param we do not pass a history_id and instead of disabling link access for each history.id we were setting it for history_id, which produced a "Wrong id (None) specified" error.
Fixes https://github.com/galaxyproject/galaxy/issues/22173
The error inner function was used as a WSGI sub-application but returned
a bare string. WSGI requires response bodies to be iterables of byte
strings. With a2wsgi converting to ASGI, h11 rejects the str, causing a
TypeError in starlette's BaseHTTPMiddleware.
Two bugs prevented navigating to edit a subworkflow:
1. In `onNavigate`, when neither `isNewTempWorkflow`, `hasChanges`,
nor `forceSave` conditions were met (e.g., no unsaved changes),
`proceed` stayed `false` and navigation was silently blocked.
This also broke the "Don't Save" path in the SaveChangesModal.
Fix: add `else { proceed = true }` for the no-changes case.
2. In `WorkflowEditor.vue`, `parseInt(undefined, 10)` produced `NaN`
when no version query parameter was present (e.g., navigating to
`/workflows/edit?workflow_id=X`). This `NaN` was passed to the
`getWorkflowInfo` API call, potentially causing validation errors.
Fix: only parse version when the query parameter exists.
Also enables the existing `test_edit_subworkflow` e2e test by removing
`@pytest.mark.xfail` and `@selenium_only` decorators, and adding a
save step after auto-layout to clear unsaved changes before testing
the edit subworkflow flow.
Fixes https://github.com/galaxyproject/galaxy/issues/21998
When metadata_strategy="extended" (used with Pulsar), discovered output
datasets were randomly assigned to object store backends instead of
respecting the user's preferred_object_store_id. This happened because:
1. Job.object_store_id was not serialized/deserialized when exporting
the job for the set_metadata subprocess
2. SessionlessJobContext.job returned None (inherited from parent),
causing override_object_store_id() to return None
3. DistributedObjectStore._create() then randomly selected a backend
Fix by serializing object_store_id in Job._serialize, deserializing it
in _set_job_attributes, and passing the job object to SessionlessJobContext.
Fixes https://github.com/galaxyproject/galaxy/issues/22035https://claude.ai/code/session_018PPfqFNBsoYUSTPgfyFCiX
HistoryDatasetCollectionAssociation.copy() was internally calling
copy_tags_from(), but both callers (History.copy() and
DatasetCollectionManager.copy()) also called copy_tags_from() on the
returned HDCA, resulting in every tag being duplicated.
Move the tag-copying responsibility entirely into HDCA.copy() via a new
target_user parameter (falling back to source history user), and remove
the redundant copy_tags_from() calls from both callers. This is safer
than the reverse approach since any future caller of HDCA.copy() will
get correct tag copying automatically.
Fixes https://github.com/galaxyproject/galaxy/issues/21872
to_model() checked "bool" but production expression tools (pick_value,
param_value_from_file) declare type="boolean". Fix to_model() to match
canonical "boolean" and fix lone test tool using non-canonical "bool".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>