fix(guardrails): default a scheme-less Agent 365 authority host to https and treat a null fallback as unset in the YAML preview

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-09-25 19:50:02 +00:00
parent 172b128178
commit 96d79c9135
5 changed files with 29 additions and 4 deletions

View file

@ -178,7 +178,8 @@ class Agent365Guardrail(CustomGuardrail):
self.api_base = api_base.rstrip("/")
self.resource_app_id = resource_app_id
self.agent_id = agent_id
self.authority_host = authority_host.rstrip("/")
authority: Final = authority_host.strip().rstrip("/")
self.authority_host = authority if "://" in authority else f"https://{authority}"
self.request_timeout = request_timeout
self.unreachable_fallback: Literal["fail_closed", "fail_open"] = (
"fail_closed" if unreachable_fallback == "fail_closed" else "fail_open"

View file

@ -307,6 +307,14 @@ class TestInitializeGuardrail:
)
assert explicit.authority_host == "http://127.0.0.1:9"
def test_scheme_less_authority_host_env_gets_https(self, monkeypatch):
monkeypatch.setenv("AZURE_AUTHORITY_HOST", " login.microsoftonline.us/ ")
guardrail: Final = initialize_guardrail(
LitellmParams(guardrail="agent_365", mode="pre_mcp_call", tenant_id="t", client_id="c", client_secret="s"),
{"guardrail_name": "a365-bare-host"},
)
assert guardrail.authority_host == "https://login.microsoftonline.us"
def test_explicit_params_win(self, monkeypatch):
monkeypatch.setenv("AGENT365_TENANT_ID", "env-tenant")
params: Final = LitellmParams(
@ -381,6 +389,13 @@ class TestAllowFlow:
assert handler.calls[0].url == "https://login.microsoftonline.us/tenant-abc/oauth2/v2.0/token"
assert handler.calls[1].url == EVALUATE_URL
@pytest.mark.asyncio
async def test_obo_exchange_adds_https_to_a_bare_authority_host(self):
handler: Final = FakeHandler([_token_response(), _allow_response()])
guardrail: Final = _make_guardrail(handler, authority_host="login.microsoftonline.us")
await _run(guardrail, _mcp_data())
assert handler.calls[0].url == "https://login.microsoftonline.us/tenant-abc/oauth2/v2.0/token"
@pytest.mark.asyncio
async def test_evaluate_payload(self):
handler: Final = FakeHandler([_token_response(), _allow_response()])

View file

@ -150,7 +150,7 @@ function submissionToTeamGuardrail(item: GuardrailSubmissionItem): TeamGuardrail
submittedAt: formatSubmissionDate(item.submitted_at),
submittedBy: item.submitted_by_email ?? item.submitted_by_user_id ?? "—",
mode: params.mode as string | undefined,
unreachable_fallback: params.unreachable_fallback as string | undefined,
unreachable_fallback: params.unreachable_fallback as string | null | undefined,
additionalProviderParams: params.additional_provider_specific_params as Record<string, unknown> | undefined,
guardrailType: params.guardrail as string | undefined,
};

View file

@ -59,6 +59,15 @@ describe("buildEquivalentConfigYaml unreachable_fallback line", () => {
expect(untyped).toContain(" guardrail: generic_guardrail_api");
});
it("treats a null fallback from the API as unset and shows the per-guardrail default", () => {
expect(
fallbackLine(buildEquivalentConfigYaml(guardrail({ guardrailType: "agent_365", unreachable_fallback: null }))),
).toBe("unreachable_fallback: fail_open");
expect(
fallbackLine(buildEquivalentConfigYaml(guardrail({ guardrailType: "akto", unreachable_fallback: null }))),
).toBe("unreachable_fallback: fail_closed");
});
it("keeps an explicit override over the per-guardrail default", () => {
expect(
fallbackLine(

View file

@ -18,7 +18,7 @@ export type TeamGuardrail = {
submittedAt: string;
submittedBy: string;
mode?: string;
unreachable_fallback?: string;
unreachable_fallback?: string | null;
additionalProviderParams?: Record<string, unknown>;
guardrailType?: string;
};
@ -33,7 +33,7 @@ export function defaultUnreachableFallback(guardrailType: string | undefined): "
function unreachableFallbackLine(g: TeamGuardrail): string {
const hint = "fail_closed blocks, fail_open proceeds when the guardrail endpoint is unreachable";
if (g.unreachable_fallback !== undefined) {
if (g.unreachable_fallback) {
return ` unreachable_fallback: ${g.unreachable_fallback} # ${hint}`;
}
return ` unreachable_fallback: ${defaultUnreachableFallback(g.guardrailType)} # ${hint}. Shown value is this guardrail's default.`;