mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-28 08:52:37 -04:00
The model-allowlist guardrail was merged into one account-wide union
and enforced flat on every request, independent of which policy,
group, or provider actually authorised it. With multiple policies —
especially a mix of guardrailed and un-guardrailed ones — this
produced two defects:
- false-allow: a model allowlisted for one group/provider leaked to
any caller, because the flat union ignored which policy authorised
the request; and
- false-deny: a policy with no guardrail (intended unrestricted) had
its traffic blocked by an unrelated policy's allowlist.
Make enforcement policy/group-aware, mirroring how llm_limit_check
already resolves caps:
- Management (SelectPolicyForRequest) is authoritative. It now uses
the request model (already carried on the wire in
CheckLLMPolicyLimitsRequest.model, previously ignored) to keep only
the applicable policies whose guardrails permit the model; a policy
with no allowlist-enabled guardrail is unrestricted. When policies
govern the (provider, groups) but none permits the model it denies
with llm_policy.model_blocked. No proto change — the field already
existed and the proxy already sends it.
- The proxy llm_guardrail becomes a per-provider fail-closed backstop.
The synthesiser emits a per-provider allowlist only when every
policy authorising that provider restricts models; a provider
reachable by any un-guardrailed policy is omitted (unrestricted).
The middleware keys the allowlist by the resolved provider id and
keeps the unknown/undetermined-model fail-closed behaviour so
path-routed providers stay enforced when management is unreachable.
Unit tests cover the selector gate (block, allow, cross-group no-leak,
un-guardrailed policy unrestricted, undetermined-model fail-closed) and
the per-provider synthesis; an e2e proves all three properties end to
end over the tunnel. Docs updated for the new config shape and the
two-layer enforcement.