This is an automated email from the ASF dual-hosted git repository.

FreeOnePlus pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-mcp-server.git


The following commit(s) were added to refs/heads/master by this push:
     new b8d3143  fix(semantic): isolate MetricFlow sidecar environment (#210)
b8d3143 is described below

commit b8d3143f2dae73a573e8756d810c3bd52182d737
Author: Yijia Su <[email protected]>
AuthorDate: Thu Aug 13 02:25:43 2026 +0800

    fix(semantic): isolate MetricFlow sidecar environment (#210)
---
 CHANGELOG.md                             |  3 +++
 docs/integrations/metricflow.md          |  5 +++-
 doris_mcp_server/semantic/metricflow.py  |  9 +++++++
 test/semantic/test_metricflow_runtime.py | 40 +++++++++++++++++++++++++++++---
 4 files changed, 53 insertions(+), 4 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17f16f1..c0f8787 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -65,6 +65,9 @@ under **Unreleased** until a new version is selected and 
published.
 
 ### Fixed
 
+- Prevented MetricFlow sidecar processes from inheriting Doris credentials,
+  bearer tokens, OAuth/JWT secrets, and unrelated MCP Server environment
+  configuration by launching each provider with a fixed minimal environment.
 - Accepted `+` revision metadata in exact Ossie and MetricFlow model
   references across binding loading, runtime lookup, and public Child schemas.
 - Enforced the canonical read-only SQL allowlist again at the production
diff --git a/docs/integrations/metricflow.md b/docs/integrations/metricflow.md
index 81afe47..d98de5d 100644
--- a/docs/integrations/metricflow.md
+++ b/docs/integrations/metricflow.md
@@ -72,7 +72,10 @@ compiled SQL and send it through the same bounded runtime as
 
 The Server starts the configured executable without a shell, sends one JSON
 object on stdin, reads one bounded JSON object from stdout, and then waits for
-the process to exit. The protocol version is `doris-mcp-metricflow/v1`.
+the process to exit. The Server supplies only a fixed locale and unbuffered-I/O
+environment, so Doris credentials, bearer tokens, OAuth/JWT secrets, and
+unrelated server configuration are not inherited. The protocol version is
+`doris-mcp-metricflow/v1`.
 
 Request:
 
diff --git a/doris_mcp_server/semantic/metricflow.py 
b/doris_mcp_server/semantic/metricflow.py
index 49a5684..9feb042 100644
--- a/doris_mcp_server/semantic/metricflow.py
+++ b/doris_mcp_server/semantic/metricflow.py
@@ -24,6 +24,7 @@ import re
 import uuid
 from collections.abc import Mapping, Sequence
 from pathlib import Path
+from types import MappingProxyType
 from typing import Any, Protocol, cast
 
 from ..utils.query_runtime import DorisQueryRuntime, ReadOnlySQLGuard
@@ -43,6 +44,13 @@ _PROVIDER_OPERATIONS = frozenset(
         "compile_query",
     }
 )
+_SIDECAR_ENVIRONMENT: Mapping[str, str] = MappingProxyType(
+    {
+        "LANG": "C.UTF-8",
+        "LC_ALL": "C.UTF-8",
+        "PYTHONUNBUFFERED": "1",
+    }
+)
 
 
 def _bool_config(section: Any, name: str, default: bool) -> bool:
@@ -145,6 +153,7 @@ class MetricFlowSidecarProvider:
                 stdout=asyncio.subprocess.PIPE,
                 stderr=asyncio.subprocess.PIPE,
                 cwd=self._project_directory,
+                env=dict(_SIDECAR_ENVIRONMENT),
             )
         except (OSError, ValueError) as exc:
             raise MetricFlowProviderFailure(
diff --git a/test/semantic/test_metricflow_runtime.py 
b/test/semantic/test_metricflow_runtime.py
index ec58146..c6cfc5b 100644
--- a/test/semantic/test_metricflow_runtime.py
+++ b/test/semantic/test_metricflow_runtime.py
@@ -226,11 +226,24 @@ async def 
test_metricflow_disabled_and_provider_failures_are_sanitized() -> None
 
 
 @pytest.mark.asyncio
-async def test_metricflow_sidecar_protocol_round_trip(tmp_path: Path) -> None:
+async def test_metricflow_sidecar_protocol_round_trip(
+    tmp_path: Path,
+    monkeypatch: pytest.MonkeyPatch,
+) -> None:
+    secret_names = (
+        "DORIS_PASSWORD",
+        "MCP_STATE_HANDLE_SECRET",
+        "TOKEN_ADMIN",
+        "OAUTH_CLIENT_SECRET",
+        "JWT_SECRET",
+    )
+    for name in secret_names:
+        monkeypatch.setenv(name, f"secret-{name.lower()}")
     script = tmp_path / "provider.py"
     script.write_text(
         """
 import json
+import os
 import sys
 
 request = json.loads(sys.stdin.read())
@@ -238,7 +251,10 @@ response = {
     "protocol_version": request["protocol_version"],
     "request_id": request["request_id"],
     "ok": True,
-    "data": {"items": [{"model_ref": "sales/main"}]},
+    "data": {
+        "items": [{"model_ref": "sales/main"}],
+        "environment": dict(sorted(os.environ.items())),
+    },
 }
 sys.stdout.write(json.dumps(response))
 """.strip(),
@@ -251,7 +267,25 @@ sys.stdout.write(json.dumps(response))
 
     result = await provider.request("list_models", {})
 
-    assert result == {"items": [{"model_ref": "sales/main"}]}
+    assert result["items"] == [{"model_ref": "sales/main"}]
+    environment = result["environment"]
+    assert {
+        "LANG": environment["LANG"],
+        "LC_ALL": environment["LC_ALL"],
+        "PYTHONUNBUFFERED": environment["PYTHONUNBUFFERED"],
+    } == {
+        "LANG": "C.UTF-8",
+        "LC_ALL": "C.UTF-8",
+        "PYTHONUNBUFFERED": "1",
+    }
+    assert set(environment) <= {
+        "LANG",
+        "LC_ALL",
+        "PYTHONUNBUFFERED",
+        "__CF_USER_TEXT_ENCODING",
+    }
+    for name in secret_names:
+        assert name not in environment
 
 
 @pytest.mark.asyncio


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to