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.
### 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
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.
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_TYPE = "table"
@@ -1449,7 +1457,18 @@ CELERY_CONFIG: type[CeleryConfig] | None = CeleryConfig
# within the app
# OVERRIDE_HTTP_HEADERS: sets override values for HTTP headers. These values will
# 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] = {}
HTTP_HEADERS: dict[str, Any] = {}

View File

@@ -37,9 +37,17 @@ def health() -> FlaskResponse:
@talisman(force_https=False)
def version() -> FlaskResponse:
"""
Return comprehensive version information including Git SHA
and branch when available.
Return version information for the running Superset instance.
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
return jsonify(get_version_metadata())

View File

@@ -312,3 +312,28 @@ def test_full_setting(
assert dttm_col.is_dttm
assert dttm_col.python_date_format == "epoch_s"
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