diff --git a/sdk/agentserver/azure-ai-agentserver-ghcopilot/CHANGELOG.md b/sdk/agentserver/azure-ai-agentserver-ghcopilot/CHANGELOG.md index c5242a31a831..2e932973eb42 100644 --- a/sdk/agentserver/azure-ai-agentserver-ghcopilot/CHANGELOG.md +++ b/sdk/agentserver/azure-ai-agentserver-ghcopilot/CHANGELOG.md @@ -1,5 +1,11 @@ # Release History +## 1.0.0b3 (Unreleased) + +### Bugs Fixed + +- `_derive_resource_url_from_project_endpoint` now matches the project endpoint host by suffix instead of substring, so a look-alike host (e.g. `.services.ai.azure.com.example`) no longer derives an attacker-controlled resource URL. + ## 1.0.0b2 (2026-04-24) ### Breaking Changes diff --git a/sdk/agentserver/azure-ai-agentserver-ghcopilot/azure/ai/agentserver/githubcopilot/_copilot_adapter.py b/sdk/agentserver/azure-ai-agentserver-ghcopilot/azure/ai/agentserver/githubcopilot/_copilot_adapter.py index f82e5e9ef8c6..af536b47a366 100644 --- a/sdk/agentserver/azure-ai-agentserver-ghcopilot/azure/ai/agentserver/githubcopilot/_copilot_adapter.py +++ b/sdk/agentserver/azure-ai-agentserver-ghcopilot/azure/ai/agentserver/githubcopilot/_copilot_adapter.py @@ -128,8 +128,9 @@ def _derive_resource_url_from_project_endpoint(project_endpoint: str) -> str: (".services.ai.azure.cn", ".cognitiveservices.azure.cn"), (".services.ai.azure.us", ".cognitiveservices.azure.us"), ]: - if project_pat in hostname: - return f"https://{hostname.replace(project_pat, resource_pat)}" + if len(hostname) > len(project_pat) and hostname.endswith(project_pat): + resource_host = hostname[: -len(project_pat)] + resource_pat + return f"https://{resource_host}" raise ValueError(f"Cannot derive RESOURCE_URL from: {project_endpoint}") diff --git a/sdk/agentserver/azure-ai-agentserver-ghcopilot/tests/unit_tests/test_replat_features.py b/sdk/agentserver/azure-ai-agentserver-ghcopilot/tests/unit_tests/test_replat_features.py index 069773579952..ebc53364e330 100644 --- a/sdk/agentserver/azure-ai-agentserver-ghcopilot/tests/unit_tests/test_replat_features.py +++ b/sdk/agentserver/azure-ai-agentserver-ghcopilot/tests/unit_tests/test_replat_features.py @@ -268,6 +268,20 @@ def test_derive_resource_url_invalid(self): with pytest.raises(ValueError, match="Cannot derive"): _derive_resource_url_from_project_endpoint("https://unknown.example.com/foo") + def test_derive_resource_url_rejects_lookalike_host(self): + """A look-alike host that only contains the suffix is rejected.""" + with pytest.raises(ValueError, match="Cannot derive"): + _derive_resource_url_from_project_endpoint( + "https://victim.services.ai.azure.com.attacker.example/api/projects/myproject" + ) + + def test_derive_resource_url_rejects_bare_suffix_host(self): + """A host that is exactly the suffix (no resource label) is rejected.""" + with pytest.raises(ValueError, match="Cannot derive"): + _derive_resource_url_from_project_endpoint( + "https://.services.ai.azure.com/api/projects/myproject" + ) + def test_get_project_endpoint_new_var(self): """Prefers FOUNDRY_PROJECT_ENDPOINT over legacy name.""" with patch.dict(os.environ, {