fix: imagegen MCP env 必须写死本地网关 base URL
首轮生产验收失败根因:_prepare_tenant 只把 IMAGE_GEN_* 透传 进 MCP env,没写 IMAGE_GEN_BASE_URL,也没带 SUB2API_BASE_URL, 于是 imagegen.py 回落到公网 sub2.diante.me,而它不在 Tinyproxy 白名单里,API 调用 403 Filtered。 修法:worker 在写 MCP env 前用 imagegen.build_mcp_env 算好 base(显式 IMAGE_GEN_BASE_URL > SUB2API_BASE_URL),并把 网关 host 加进 NO_PROXY;组装逻辑抽成可单测 helper,新增 2 项回归测试(本地网关解析、显式 base 优先)。
This commit is contained in:
1 parent
da7b8e3a4f
commit
7a920cee59
3 files changed
+68
-16
No files matched your search
@@ -306,3 +306,36 @@ def ensure_imagegen_mcp_env(codex_home_dir, env):
|
|||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def build_mcp_env(worker_environ, proxy_env=None):
|
||||||
|
"""Build the [mcp_servers.imagegen.env] dict from worker env.
|
||||||
|
|
||||||
|
The MCP process only sees config.toml env, never the worker's
|
||||||
|
own env, so IMAGE_GEN_BASE_URL must be resolved HERE (worker
|
||||||
|
IMAGE_GEN_BASE_URL, else SUB2API_BASE_URL, else absent and
|
||||||
|
imagegen.py falls back to the public relay domain -- which is
|
||||||
|
NOT on the Tinyproxy whitelist, so a missing base here caused
|
||||||
|
a 403 Filtered in the first production verification, 2026-10-30).
|
||||||
|
proxy_env supplies HTTP(S)_PROXY for downloading the returned
|
||||||
|
image URL; NO_PROXY gains the gateway host for direct API calls.
|
||||||
|
Never injects the task user's gateway key.
|
||||||
|
"""
|
||||||
|
env = dict(proxy_env) if proxy_env else {}
|
||||||
|
for key in ("IMAGE_GEN_BASE_URL", "IMAGE_GEN_API_KEY",
|
||||||
|
"IMAGE_GEN_MODEL"):
|
||||||
|
if worker_environ.get(key):
|
||||||
|
env[key] = worker_environ[key]
|
||||||
|
base = (worker_environ.get("IMAGE_GEN_BASE_URL", "")
|
||||||
|
or worker_environ.get("SUB2API_BASE_URL", ""))
|
||||||
|
if base:
|
||||||
|
env["IMAGE_GEN_BASE_URL"] = base
|
||||||
|
host = base.split("://", 1)[-1].split("/", 1)[0]
|
||||||
|
host = host.split(":", 1)[0]
|
||||||
|
parts = [p for p in env.get("NO_PROXY", "").split(",") if p]
|
||||||
|
for item in ("localhost", "127.0.0.1", host):
|
||||||
|
if item and item not in parts:
|
||||||
|
parts.append(item)
|
||||||
|
env["NO_PROXY"] = ",".join(parts)
|
||||||
|
env["no_proxy"] = ",".join(parts)
|
||||||
|
return env
|
||||||
@@ -108,6 +108,31 @@ class ImageGenTests(unittest.TestCase):
|
|||||||
"arguments": {}}})
|
"arguments": {}}})
|
||||||
self.assertEqual(resp["error"]["code"], -32602)
|
self.assertEqual(resp["error"]["code"], -32602)
|
||||||
|
|
||||||
|
def test_build_mcp_env_resolves_local_gateway_base(self):
|
||||||
|
# Regression: first production verification failed 403
|
||||||
|
# Filtered because MCP env had no base URL and imagegen.py
|
||||||
|
# fell back to the public relay domain, which is not on
|
||||||
|
# the Tinyproxy whitelist. The local gateway origin must
|
||||||
|
# be resolved here and land in NO_PROXY.
|
||||||
|
env = imagegen.build_mcp_env(
|
||||||
|
{"SUB2API_BASE_URL": "http://172.17.0.1:28084",
|
||||||
|
"IMAGE_GEN_API_KEY": "k"},
|
||||||
|
{"HTTP_PROXY": "http://10.20.20.1:8888",
|
||||||
|
"NO_PROXY": "localhost,127.0.0.1"})
|
||||||
|
self.assertEqual(env["IMAGE_GEN_BASE_URL"],
|
||||||
|
"http://172.17.0.1:28084")
|
||||||
|
self.assertIn("172.17.0.1", env["NO_PROXY"])
|
||||||
|
self.assertEqual(env["IMAGE_GEN_API_KEY"], "k")
|
||||||
|
self.assertNotIn("SUB2API_API_KEY", env)
|
||||||
|
|
||||||
|
def test_build_mcp_env_explicit_base_wins(self):
|
||||||
|
env = imagegen.build_mcp_env(
|
||||||
|
{"IMAGE_GEN_BASE_URL": "https://relay.example",
|
||||||
|
"SUB2API_BASE_URL": "http://172.17.0.1:28084"})
|
||||||
|
self.assertEqual(env["IMAGE_GEN_BASE_URL"],
|
||||||
|
"https://relay.example")
|
||||||
|
self.assertIn("relay.example", env["NO_PROXY"])
|
||||||
|
|
||||||
def test_ensure_home_seeds_and_env(self):
|
def test_ensure_home_seeds_and_env(self):
|
||||||
home = os.path.join(self._tmp.name, "home")
|
home = os.path.join(self._tmp.name, "home")
|
||||||
os.makedirs(home)
|
os.makedirs(home)
|
||||||
|
|||||||
+10
-16
@@ -2906,22 +2906,16 @@ class Worker:
|
|||||||
# 未配 IMAGE_GEN_API_KEY 时仍写 base/model,工具会
|
# 未配 IMAGE_GEN_API_KEY 时仍写 base/model,工具会
|
||||||
# 明确报“未配置”;绝不把任务自己的分组 2 key 塞进来。
|
# 明确报“未配置”;绝不把任务自己的分组 2 key 塞进来。
|
||||||
try:
|
try:
|
||||||
imagegen_env = dict(proxy_env) if proxy_env else {}
|
# env 组装在 imagegen.build_mcp_env 里(可单测):
|
||||||
for key in ("IMAGE_GEN_BASE_URL", "IMAGE_GEN_API_KEY",
|
# base 必须在这里算好写进 MCP env,MCP 进程只读
|
||||||
"IMAGE_GEN_MODEL"):
|
# config.toml 里的 env,看不到 worker 的
|
||||||
if os.environ.get(key):
|
# SUB2API_BASE_URL;不写的话 imagegen.py 会回落
|
||||||
imagegen_env[key] = os.environ[key]
|
# 到公网 sub2.diante.me,而它不在 Tinyproxy
|
||||||
base = (os.environ.get("IMAGE_GEN_BASE_URL", "")
|
# 白名单里,实测 403 Filtered(2026-10-30 首轮
|
||||||
or os.environ.get("SUB2API_BASE_URL", ""))
|
# 生产验收即因此失败)。本地网关 origin 优先,
|
||||||
host = base.split("://", 1)[-1].split("/", 1)[0]
|
# 因为它走 NO_PROXY 直连,不依赖白名单。
|
||||||
host = host.split(":", 1)[0]
|
from cloud.imagegen import build_mcp_env as _build_mcp_env
|
||||||
no_proxy = imagegen_env.get("NO_PROXY", "")
|
imagegen_env = _build_mcp_env(os.environ, proxy_env)
|
||||||
parts = [p for p in no_proxy.split(",") if p]
|
|
||||||
for item in ("localhost", "127.0.0.1", host):
|
|
||||||
if item and item not in parts:
|
|
||||||
parts.append(item)
|
|
||||||
imagegen_env["NO_PROXY"] = ",".join(parts)
|
|
||||||
imagegen_env["no_proxy"] = ",".join(parts)
|
|
||||||
home = self._tenant_dirs(
|
home = self._tenant_dirs(
|
||||||
task.get("project_id"))["codex_home"]
|
task.get("project_id"))["codex_home"]
|
||||||
_ensure_imagegen_mcp_env(home, imagegen_env)
|
_ensure_imagegen_mcp_env(home, imagegen_env)
|
||||||
|
|||||||
Reference in new issue
Block a user