Compare commits

...

3 Commits

Author SHA1 Message Date
Evan
0052909f10 fix(version): check EXPOSE_VERSION_INFO before calling get_version_metadata
- Skip get_version_metadata() when the flag is False: read VERSION_STRING
  directly from app config so git subprocesses are never invoked on
  unauthenticated /version requests when version details are redacted
- Add missing build_number assertion in the positive-flag test case
- Update the disabled-flag test to match new code path (no longer mocks
  get_version_metadata; sets VERSION_STRING directly in app config)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 02:32:31 -07:00
Claude Code
898b3c498f feat(config): add Cross-Origin-Resource-Policy default header
Adds a conservative `Cross-Origin-Resource-Policy: same-site` default to
DEFAULT_HTTP_HEADERS as a defense-in-depth response-header hardening. The
header is applied through the existing DEFAULT_HTTP_HEADERS mechanism, so it
is only set when a response does not already carry the header and operators
can override it via config.

`same-site` is used rather than the stricter `same-origin` so documented
same-site embedding flows (e.g. the Embedded SDK) keep working unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 02:32:31 -07:00
Claude Code
81a4665813 feat(config): add EXPOSE_VERSION_INFO to control /version detail
The unauthenticated /version endpoint returns the version string along with
the Git SHA, full SHA, build number, and branch name when available. Add an
EXPOSE_VERSION_INFO config option (default True, preserving existing behavior)
that, when set to False, reduces the response to just the version string and
omits the build-specific details.

The gating is applied in the endpoint itself so the change is opt-in and
non-breaking for existing deployments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 02:32:30 -07:00
5 changed files with 144 additions and 3 deletions

View File

@@ -34,6 +34,25 @@ The embedded dashboard page now validates the origin of incoming `postMessage` e
Enforcement only applies when the Allowed Domains list is non-empty. If the list is empty (the default), any origin is accepted, so there is no behavior change for embeds that did not configure Allowed Domains. Enforcement only applies when the Allowed Domains list is non-empty. If the list is empty (the default), any origin is accepted, so there is no behavior change for embeds that did not configure Allowed Domains.
### New `EXPOSE_VERSION_INFO` config to control `/version` detail
A new `EXPOSE_VERSION_INFO` config option controls how much detail the unauthenticated `/version` endpoint returns. It defaults to `True`, which preserves the existing behavior: the endpoint returns the full version metadata, including the Git SHA, full SHA, build number, and branch name when available.
Operators who prefer not to expose build-specific details to unauthenticated callers can set the following in `superset_config.py`:
```python
EXPOSE_VERSION_INFO = False
```
When disabled, `/version` returns only the human-readable `version_string` and omits the Git SHA, full SHA, build number, and branch name. Because the default is `True`, this change is non-breaking for existing deployments.
As an additional defense-in-depth hardening, Superset now sends a `Cross-Origin-Resource-Policy: same-site` response header by default via `DEFAULT_HTTP_HEADERS`. `same-site` is deliberately chosen over the stricter `same-origin` so that documented same-site embedding flows (e.g. the Embedded SDK, where a Superset subdomain is framed by a sibling application subdomain) continue to work unchanged. Deployments that serve Superset responses or static assets as subresources to a _cross-site_ origin may need to relax or remove this header. Because it is applied through `DEFAULT_HTTP_HEADERS`, the header is only set when a response does not already carry one, so it can be overridden per-response or by replacing the config value:
```python
# Relax to permit cross-site consumers, or set to "same-origin" to harden further.
DEFAULT_HTTP_HEADERS = {"Cross-Origin-Resource-Policy": "cross-origin"}
```
### Dataset import validates catalog against the target connection ### Dataset import validates catalog against the target connection
Importing a dataset now validates the `catalog` field against the target database connection. When the connection has multi-catalog disabled (`allow_multi_catalog` off) and the dataset's catalog is not the connection's default catalog, the import fails instead of silently persisting the non-default catalog. This matches the validation already enforced on the dataset update path and prevents imported datasets from querying an unintended database. Importing a dataset now validates the `catalog` field against the target database connection. When the connection has multi-catalog disabled (`allow_multi_catalog` off) and the dataset's catalog is not the connection's default catalog, the import fails instead of silently persisting the non-default catalog. This matches the validation already enforced on the dataset update path and prevents imported datasets from querying an unintended database.

View File

@@ -156,6 +156,14 @@ VERSION_SHA = _try_json_readsha(VERSION_INFO_FILE, VERSION_SHA_LENGTH)
# can be replaced at build time to expose build information. # can be replaced at build time to expose build information.
BUILD_NUMBER = None BUILD_NUMBER = None
# Controls how much detail the unauthenticated ``/version`` endpoint returns.
# When True (default, preserves existing behavior) the endpoint returns the full
# version metadata, including the Git SHA and branch name when available. Set to
# False to return only the human-readable version string and omit the Git SHA,
# full SHA, build number, and branch name, so deployment-specific build details
# are not exposed to unauthenticated callers.
EXPOSE_VERSION_INFO = True
# default viz used in chart explorer & SQL Lab explore # default viz used in chart explorer & SQL Lab explore
DEFAULT_VIZ_TYPE = "table" DEFAULT_VIZ_TYPE = "table"
@@ -1449,7 +1457,18 @@ CELERY_CONFIG: type[CeleryConfig] | None = CeleryConfig
# within the app # within the app
# OVERRIDE_HTTP_HEADERS: sets override values for HTTP headers. These values will # OVERRIDE_HTTP_HEADERS: sets override values for HTTP headers. These values will
# override anything set within the app # override anything set within the app
DEFAULT_HTTP_HEADERS: dict[str, Any] = {} #
# As a defense-in-depth default, Superset sends a conservative
# `Cross-Origin-Resource-Policy` header on its responses. `same-site` is used
# (rather than the stricter `same-origin`) so that same-site embedding patterns
# such as the Embedded SDK, where a Superset subdomain is framed by a sibling
# application subdomain, keep working out of the box. Because this is set through
# DEFAULT_HTTP_HEADERS, the value is only applied when the response does not
# already carry the header, so operators can override it (per-response or by
# replacing this config value) to suit their cross-origin requirements.
DEFAULT_HTTP_HEADERS: dict[str, Any] = {
"Cross-Origin-Resource-Policy": "same-site",
}
OVERRIDE_HTTP_HEADERS: dict[str, Any] = {} OVERRIDE_HTTP_HEADERS: dict[str, Any] = {}
HTTP_HEADERS: dict[str, Any] = {} HTTP_HEADERS: dict[str, Any] = {}

View File

@@ -37,9 +37,17 @@ def health() -> FlaskResponse:
@talisman(force_https=False) @talisman(force_https=False)
def version() -> FlaskResponse: def version() -> FlaskResponse:
""" """
Return comprehensive version information including Git SHA Return version information for the running Superset instance.
and branch when available.
When ``EXPOSE_VERSION_INFO`` is True (default) this returns the full
version metadata, including the Git SHA and branch name when available.
When it is False, only the human-readable version string is returned and
build-specific details (Git SHA, full SHA, build number, branch name) are
omitted so they are not exposed to unauthenticated callers.
""" """
if not app.config.get("EXPOSE_VERSION_INFO", True):
return jsonify({"version_string": app.config.get("VERSION_STRING", "unknown")})
from superset.utils.version import get_version_metadata from superset.utils.version import get_version_metadata
return jsonify(get_version_metadata()) return jsonify(get_version_metadata())

View File

@@ -312,3 +312,28 @@ def test_full_setting(
assert dttm_col.is_dttm assert dttm_col.is_dttm
assert dttm_col.python_date_format == "epoch_s" assert dttm_col.python_date_format == "epoch_s"
assert dttm_col.expression == "CAST(dttm as INTEGER)" assert dttm_col.expression == "CAST(dttm as INTEGER)"
def test_expose_version_info_defaults_to_true() -> None:
"""
The /version endpoint preserves its existing behavior by default. Operators
can set EXPOSE_VERSION_INFO = False to omit build-specific details.
"""
from superset import config
assert config.EXPOSE_VERSION_INFO is True
def test_default_cross_origin_resource_policy_header() -> None:
"""
Superset ships a conservative `Cross-Origin-Resource-Policy: same-site`
default through DEFAULT_HTTP_HEADERS. `same-site` (rather than `same-origin`)
is chosen so documented same-site embedding flows, such as the Embedded SDK,
keep working while still providing a defense-in-depth default that operators
can override.
"""
from superset import config
assert (
config.DEFAULT_HTTP_HEADERS.get("Cross-Origin-Resource-Policy") == "same-site"
)

View File

@@ -0,0 +1,70 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Unit tests for the ``/version`` endpoint and ``EXPOSE_VERSION_INFO`` gating."""
from typing import Any
from pytest_mock import MockerFixture
FULL_METADATA = {
"version_string": "1.2.3",
"version_sha": "abcd1234",
"full_sha": "abcd1234ef567890",
"build_number": "42",
"branch_name": "master",
}
def test_version_exposes_full_metadata_by_default(
client: Any,
mocker: MockerFixture,
) -> None:
"""With EXPOSE_VERSION_INFO True (default) the full metadata is returned."""
mocker.patch(
"superset.utils.version.get_version_metadata",
return_value=dict(FULL_METADATA),
)
client.application.config["EXPOSE_VERSION_INFO"] = True
response = client.get("/version")
assert response.status_code == 200
body = response.get_json()
assert body["version_string"] == "1.2.3"
assert body["version_sha"] == "abcd1234"
assert body["full_sha"] == "abcd1234ef567890"
assert body["branch_name"] == "master"
assert body["build_number"] == "42"
def test_version_redacts_details_when_flag_disabled(
client: Any,
mocker: MockerFixture,
) -> None:
"""With EXPOSE_VERSION_INFO False only VERSION_STRING from config is returned."""
client.application.config["EXPOSE_VERSION_INFO"] = False
client.application.config["VERSION_STRING"] = "1.2.3"
response = client.get("/version")
assert response.status_code == 200
body = response.get_json()
assert body == {"version_string": "1.2.3"}
assert "version_sha" not in body
assert "full_sha" not in body
assert "branch_name" not in body
assert "build_number" not in body