Skip to content

Commit efb30b4

Browse files
authored
Merge pull request #138 from Yif-Yang/fix/sleep-openai-compat-safety
fix(sleep): harden OpenAI compatibility boundaries
2 parents be21846 + 8b39394 commit efb30b4

2 files changed

Lines changed: 27 additions & 3 deletions

File tree

‎skillopt_sleep/backend.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,8 +1311,12 @@ def _read_chat_extra_body() -> Optional[Dict[str, Any]]:
13111311

13121312
def _is_azure_host(self) -> bool:
13131313
from urllib.parse import urlparse
1314-
host = (urlparse(self.endpoint).hostname or "").lower()
1315-
return host.endswith(self._AZURE_HOST_SUFFIXES)
1314+
parsed = urlparse(self.endpoint)
1315+
host = (parsed.hostname or "").lower()
1316+
return (
1317+
parsed.scheme.lower() == "https"
1318+
and host.endswith(self._AZURE_HOST_SUFFIXES)
1319+
)
13161320

13171321
def _get_client(self):
13181322
if self._client is None:
@@ -1383,7 +1387,7 @@ def _call(self, prompt: str, *, max_tokens: int = 1024, retries: int = 5) -> str
13831387
kwargs["max_tokens"] = self.compat_max_tokens
13841388
else:
13851389
kwargs["max_completion_tokens"] = 16384
1386-
if self.chat_extra_body:
1390+
if self._compat_mode() and self.chat_extra_body:
13871391
kwargs["extra_body"] = self.chat_extra_body
13881392
resp = client.chat.completions.create(**kwargs)
13891393
text = (resp.choices[0].message.content or "").strip()

‎tests/test_azure_openai_compat.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,17 @@ def test_managed_identity_refuses_non_azure_endpoint(self):
108108
be._get_client()
109109
self.assertIn("openai_compatible", str(ctx.exception))
110110

111+
def test_managed_identity_refuses_insecure_azure_endpoint(self):
112+
# A matching Azure hostname is insufficient: AAD bearer credentials
113+
# must never be sent over plaintext HTTP.
114+
env = {"AZURE_OPENAI_ENDPOINT": "http://foo.openai.azure.com"}
115+
with mock.patch.dict(os.environ, env, clear=True):
116+
be = AzureOpenAIBackend(deployment="some-model")
117+
self.assertFalse(be._is_azure_host())
118+
with self.assertRaises(ValueError) as ctx:
119+
be._get_client()
120+
self.assertIn("openai_compatible", str(ctx.exception))
121+
111122
def test_azure_host_detection(self):
112123
with mock.patch.dict(os.environ, {}, clear=True):
113124
be = AzureOpenAIBackend(deployment="gpt-5.5") # table endpoint
@@ -189,6 +200,15 @@ def test_azure_mode_sends_max_completion_tokens(self):
189200
self.assertEqual(call["max_completion_tokens"], 16384)
190201
self.assertNotIn("max_tokens", call)
191202

203+
def test_azure_mode_ignores_compat_extra_body(self):
204+
body = {"thinking": {"type": "enabled"}}
205+
env = {"SKILLOPT_SLEEP_CHAT_EXTRA_BODY": json.dumps(body)}
206+
be = _backend_with(["hi"], env)
207+
with mock.patch.dict(os.environ, env, clear=True):
208+
be._call("p", retries=1)
209+
(call,) = be._client.chat.completions.calls
210+
self.assertNotIn("extra_body", call)
211+
192212

193213
class TestErrorState(unittest.TestCase):
194214
def test_recovered_retry_clears_last_call_error(self):

0 commit comments

Comments
 (0)